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">
|
||||
|
||||
<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>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -25,19 +25,29 @@ namespace Microsoft.AspNetCore.Builder
|
|||
};
|
||||
app.UseWebSockets (webSocketOptions);
|
||||
|
||||
Ooui.UI.ServerEnabled = false;
|
||||
|
||||
app.Use (async (context, next) =>
|
||||
{
|
||||
if (context.Request.Path == jsPath) {
|
||||
var response = context.Response;
|
||||
|
||||
if (context.Request.Path == jsPath) {
|
||||
var clientJsBytes = Ooui.UI.ClientJsBytes;
|
||||
var clientJsEtag = Ooui.UI.ClientJsEtag;
|
||||
if (context.Request.Headers.TryGetValue ("If-None-Match", out var inms) && inms.Count > 0 && inms[0] == clientJsEtag) {
|
||||
response.StatusCode = 304;
|
||||
}
|
||||
else {
|
||||
response.StatusCode = 200;
|
||||
response.ContentLength = clientJsBytes.Length;
|
||||
response.ContentType = "application/javascript; charset=utf-8";
|
||||
response.Headers.Add ("Cache-Control", "public, max-age=3600");
|
||||
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) {
|
||||
if (context.WebSockets.IsWebSocketRequest) {
|
||||
await WebSocketHandler.HandleWebSocketRequestAsync (context).ConfigureAwait (false);
|
||||
|
@ -46,6 +56,21 @@ namespace Microsoft.AspNetCore.Builder
|
|||
context.Response.StatusCode = 400;
|
||||
}
|
||||
}
|
||||
else if (Ooui.UI.TryGetFileContentAtPath (context.Request.Path, out var file)) {
|
||||
if (context.Request.Headers.TryGetValue ("If-None-Match", out var inms) && inms.Count > 0 && inms[0] == file.Etag) {
|
||||
response.StatusCode = 304;
|
||||
}
|
||||
else {
|
||||
response.StatusCode = 200;
|
||||
response.ContentLength = file.Content.Length;
|
||||
response.ContentType = file.ContentType;
|
||||
response.Headers.Add ("Cache-Control", "public, max-age=60");
|
||||
response.Headers.Add ("Etag", file.Etag);
|
||||
using (var s = response.Body) {
|
||||
await s.WriteAsync (file.Content, 0, file.Content.Length).ConfigureAwait (false);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
await next ().ConfigureAwait (false);
|
||||
}
|
||||
|
|
|
@ -86,9 +86,10 @@ namespace Ooui.AspNetCore
|
|||
BadRequest ("Missing `h`");
|
||||
return;
|
||||
}
|
||||
if (!double.TryParse (wValues.Last (), out var w))
|
||||
var icult = System.Globalization.CultureInfo.InvariantCulture;
|
||||
if (!double.TryParse (wValues.Last (), System.Globalization.NumberStyles.Any, icult, out var w))
|
||||
w = 640;
|
||||
if (!double.TryParse (hValues.Last (), out var h))
|
||||
if (!double.TryParse (hValues.Last (), System.Globalization.NumberStyles.Any, icult, out var h))
|
||||
h = 480;
|
||||
|
||||
//
|
||||
|
|
|
@ -59,7 +59,7 @@ namespace Ooui.Forms
|
|||
{
|
||||
ClassName = "btn btn-default"
|
||||
};
|
||||
_cancelButton.Clicked += (s, e) => SetResult(false);
|
||||
_cancelButton.Click += (s, e) => SetResult(false);
|
||||
|
||||
footer.AppendChild(_cancelButton);
|
||||
|
||||
|
@ -70,7 +70,7 @@ namespace Ooui.Forms
|
|||
ClassName = "btn btn-default"
|
||||
};
|
||||
|
||||
_acceptButton.Clicked += (s, e) => SetResult(true);
|
||||
_acceptButton.Click += (s, e) => SetResult(true);
|
||||
footer.AppendChild(_acceptButton);
|
||||
}
|
||||
|
||||
|
@ -90,23 +90,23 @@ namespace Ooui.Forms
|
|||
{
|
||||
add
|
||||
{
|
||||
_closeButton.Clicked += value;
|
||||
_closeButton.Click += value;
|
||||
|
||||
if(_cancelButton != null)
|
||||
_cancelButton.Clicked += value;
|
||||
_cancelButton.Click += value;
|
||||
|
||||
if(_acceptButton != null)
|
||||
_acceptButton.Clicked += value;
|
||||
_acceptButton.Click += value;
|
||||
}
|
||||
remove
|
||||
{
|
||||
_closeButton.Clicked -= value;
|
||||
_closeButton.Click -= value;
|
||||
|
||||
if (_cancelButton != null)
|
||||
_cancelButton.Clicked -= value;
|
||||
_cancelButton.Click -= value;
|
||||
|
||||
if (_acceptButton != null)
|
||||
_acceptButton.Clicked -= value;
|
||||
_acceptButton.Click -= value;
|
||||
}
|
||||
}
|
||||
public Element Element { get; private set; }
|
||||
|
|
|
@ -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 (Editor), typeof (EditorRenderer))]
|
||||
[assembly: ExportRenderer (typeof (Entry), typeof (EntryRenderer))]
|
||||
[assembly: ExportRenderer (typeof (Frame), typeof (FrameRenderer))]
|
||||
[assembly: ExportRenderer (typeof (Image), typeof (ImageRenderer))]
|
||||
[assembly: ExportRenderer (typeof (Label), typeof (LabelRenderer))]
|
||||
[assembly: ExportRenderer (typeof (ProgressBar), typeof (ProgressBarRenderer))]
|
||||
[assembly: ExportRenderer (typeof (Switch), typeof (SwitchRenderer))]
|
||||
[assembly: ExportImageSourceHandler (typeof (FileImageSource), typeof (FileImageSourceHandler))]
|
||||
[assembly: ExportImageSourceHandler (typeof (StreamImageSource), typeof (StreamImagesourceHandler))]
|
||||
[assembly: ExportImageSourceHandler (typeof (UriImageSource), typeof (ImageLoaderSourceHandler))]
|
||||
|
||||
namespace Ooui.Forms
|
||||
{
|
||||
|
@ -24,4 +30,13 @@ namespace Ooui.Forms
|
|||
{
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage (AttributeTargets.Assembly, AllowMultiple = true)]
|
||||
public sealed class ExportImageSourceHandlerAttribute : HandlerAttribute
|
||||
{
|
||||
public ExportImageSourceHandlerAttribute (Type handler, Type target)
|
||||
: base (handler, target)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace Ooui.Forms.Extensions
|
|||
if (self.Style.Width.Equals ("inherit")) {
|
||||
s = self.Text.MeasureSize (self.Style);
|
||||
measured = true;
|
||||
rw = double.IsPositiveInfinity (s.Width) ? double.PositiveInfinity : s.Width;
|
||||
rw = double.IsPositiveInfinity (s.Width) ? double.PositiveInfinity : Math.Ceiling (s.Width);
|
||||
}
|
||||
else {
|
||||
rw = self.Style.GetNumberWithUnits ("width", "px", 640);
|
||||
|
@ -27,7 +27,7 @@ namespace Ooui.Forms.Extensions
|
|||
s = self.Text.MeasureSize (self.Style);
|
||||
measured = true;
|
||||
}
|
||||
rh = double.IsPositiveInfinity (s.Height) ? double.PositiveInfinity : s.Height;
|
||||
rh = double.IsPositiveInfinity (s.Height) ? double.PositiveInfinity : Math.Ceiling (s.Height * 1.4);
|
||||
}
|
||||
else {
|
||||
rh = self.Style.GetNumberWithUnits ("height", "px", 480);
|
||||
|
|
|
@ -44,9 +44,23 @@ namespace Ooui.Forms.Extensions
|
|||
return Size.Zero;
|
||||
|
||||
var fontHeight = fontSize;
|
||||
var charWidth = fontSize * 0.5;
|
||||
|
||||
var width = text.Length * charWidth;
|
||||
var isBold = fontAttrs.HasFlag (FontAttributes.Bold);
|
||||
|
||||
var props = isBold ? BoldCharacterProportions : CharacterProportions;
|
||||
var avgp = isBold ? BoldAverageCharProportion : AverageCharProportion;
|
||||
|
||||
var pwidth = 1.0e-6; // Tiny little padding to account for sampling errors
|
||||
for (var i = 0; i < text.Length; i++) {
|
||||
var c = (int)text[i];
|
||||
if (c < 128) {
|
||||
pwidth += props[c];
|
||||
}
|
||||
else {
|
||||
pwidth += avgp;
|
||||
}
|
||||
}
|
||||
var width = fontSize * pwidth;
|
||||
|
||||
return new Size (width, fontHeight);
|
||||
}
|
||||
|
@ -69,5 +83,57 @@ namespace Ooui.Forms.Extensions
|
|||
}
|
||||
}
|
||||
|
||||
public static string ToOouiVerticalAlign (this TextAlignment align)
|
||||
{
|
||||
switch (align) {
|
||||
case TextAlignment.Start:
|
||||
default:
|
||||
return "top";
|
||||
case TextAlignment.Center:
|
||||
return "middle";
|
||||
case TextAlignment.End:
|
||||
return "bottom";
|
||||
}
|
||||
}
|
||||
|
||||
static readonly double[] CharacterProportions = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0.27799999713897705, 0.27799999713897705, 0.27799999713897705, 0.27799999713897705, 0.27799999713897705, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0.27799999713897705, 0.25899994373321533, 0.4259999990463257, 0.5560001134872437, 0.5560001134872437, 1.0000001192092896, 0.6299999952316284, 0.27799999713897705,
|
||||
0.25899994373321533, 0.25899994373321533, 0.3520001173019409, 0.6000000238418579, 0.27799999713897705, 0.3890000581741333, 0.27799999713897705, 0.3330000638961792,
|
||||
0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437,
|
||||
0.5560001134872437, 0.5560001134872437, 0.27799999713897705, 0.27799999713897705, 0.6000000238418579, 0.6000000238418579, 0.6000000238418579, 0.5560001134872437,
|
||||
0.8000000715255737, 0.6480001211166382, 0.6850000619888306, 0.722000002861023, 0.7040001153945923, 0.6110001802444458, 0.5740000009536743, 0.7589999437332153,
|
||||
0.722000002861023, 0.25899994373321533, 0.5190001726150513, 0.6669999361038208, 0.5560001134872437, 0.8709999322891235, 0.722000002861023, 0.7600001096725464,
|
||||
0.6480001211166382, 0.7600001096725464, 0.6850000619888306, 0.6480001211166382, 0.5740000009536743, 0.722000002861023, 0.6110001802444458, 0.9259999990463257,
|
||||
0.6110001802444458, 0.6480001211166382, 0.6110001802444458, 0.25899994373321533, 0.3330000638961792, 0.25899994373321533, 0.6000000238418579, 0.5000001192092896,
|
||||
0.22200000286102295, 0.5370000600814819, 0.593000054359436, 0.5370000600814819, 0.593000054359436, 0.5370000600814819, 0.2960001230239868, 0.5740000009536743,
|
||||
0.5560001134872437, 0.22200000286102295, 0.22200000286102295, 0.5190001726150513, 0.22200000286102295, 0.8530000448226929, 0.5560001134872437, 0.5740000009536743,
|
||||
0.593000054359436, 0.593000054359436, 0.3330000638961792, 0.5000001192092896, 0.31500017642974854, 0.5560001134872437, 0.5000001192092896, 0.7580000162124634,
|
||||
0.5180000066757202, 0.5000001192092896, 0.4800001382827759, 0.3330000638961792, 0.22200000286102295, 0.3330000638961792, 0.6000000238418579, 0
|
||||
};
|
||||
const double AverageCharProportion = 0.5131400561332703;
|
||||
|
||||
static readonly double[] BoldCharacterProportions = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0.27799999713897705, 0.27799999713897705, 0.27799999713897705, 0.27799999713897705, 0.27799999713897705, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0.27799999713897705, 0.27799999713897705, 0.46299993991851807, 0.5560001134872437, 0.5560001134872437, 1.0000001192092896, 0.6850000619888306, 0.27799999713897705,
|
||||
0.2960001230239868, 0.2960001230239868, 0.40700018405914307, 0.6000000238418579, 0.27799999713897705, 0.40700018405914307, 0.27799999713897705, 0.37099993228912354,
|
||||
0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437, 0.5560001134872437,
|
||||
0.5560001134872437, 0.5560001134872437, 0.27799999713897705, 0.27799999713897705, 0.6000000238418579, 0.6000000238418579, 0.6000000238418579, 0.5560001134872437,
|
||||
0.8000000715255737, 0.6850000619888306, 0.7040001153945923, 0.7410000562667847, 0.7410000562667847, 0.6480001211166382, 0.593000054359436, 0.7589999437332153,
|
||||
0.7410000562667847, 0.29499995708465576, 0.5560001134872437, 0.722000002861023, 0.593000054359436, 0.9070001840591431, 0.7410000562667847, 0.777999997138977,
|
||||
0.6669999361038208, 0.777999997138977, 0.722000002861023, 0.6490000486373901, 0.6110001802444458, 0.7410000562667847, 0.6299999952316284, 0.9440001249313354,
|
||||
0.6669999361038208, 0.6669999361038208, 0.6480001211166382, 0.3330000638961792, 0.37099993228912354, 0.3330000638961792, 0.6000000238418579, 0.5000001192092896,
|
||||
0.25899994373321533, 0.5740000009536743, 0.6110001802444458, 0.5740000009536743, 0.6110001802444458, 0.5740000009536743, 0.3330000638961792, 0.6110001802444458,
|
||||
0.593000054359436, 0.2580000162124634, 0.27799999713897705, 0.5740000009536743, 0.2580000162124634, 0.906000018119812, 0.593000054359436, 0.6110001802444458,
|
||||
0.6110001802444458, 0.6110001802444458, 0.3890000581741333, 0.5370000600814819, 0.3520001173019409, 0.593000054359436, 0.5200001001358032, 0.8140000104904175,
|
||||
0.5370000600814819, 0.5190001726150513, 0.5190001726150513, 0.3330000638961792, 0.223000168800354, 0.3330000638961792, 0.6000000238418579, 0
|
||||
};
|
||||
const double BoldAverageCharProportion = 0.5346300601959229;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,12 +26,12 @@ namespace Xamarin.Forms
|
|||
Device.SetIdiom (TargetIdiom.Desktop);
|
||||
Device.PlatformServices = new OouiPlatformServices ();
|
||||
Device.Info = new OouiDeviceInfo ();
|
||||
Color.SetAccent (Color.FromHex ("#0000EE")); // Safari Blue
|
||||
Color.SetAccent (Color.FromHex ("#337ab7")); // Bootstrap Blue
|
||||
|
||||
Registrar.RegisterAll (new[] {
|
||||
typeof(ExportRendererAttribute),
|
||||
//typeof(ExportCellAttribute),
|
||||
//typeof(ExportImageSourceHandlerAttribute),
|
||||
typeof(ExportImageSourceHandlerAttribute),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -62,11 +62,6 @@ namespace Xamarin.Forms
|
|||
Task.Run (action);
|
||||
}
|
||||
|
||||
public Ticker CreateTicker ()
|
||||
{
|
||||
throw new NotImplementedException ();
|
||||
}
|
||||
|
||||
public Assembly[] GetAssemblies ()
|
||||
{
|
||||
return AppDomain.CurrentDomain.GetAssemblies ();
|
||||
|
@ -119,6 +114,31 @@ namespace Xamarin.Forms
|
|||
}
|
||||
}), null, (int)interval.TotalMilliseconds, (int)interval.TotalMilliseconds);
|
||||
}
|
||||
|
||||
public Ticker CreateTicker ()
|
||||
{
|
||||
return new OouiTicker ();
|
||||
}
|
||||
|
||||
class OouiTicker : Ticker
|
||||
{
|
||||
Timer timer;
|
||||
protected override void DisableTimer ()
|
||||
{
|
||||
var t = timer;
|
||||
timer = null;
|
||||
t?.Dispose ();
|
||||
}
|
||||
protected override void EnableTimer ()
|
||||
{
|
||||
if (timer != null)
|
||||
return;
|
||||
var interval = TimeSpan.FromSeconds (1.0 / Ooui.UI.Session.MaxFps);
|
||||
timer = new Timer ((_ => {
|
||||
this.SendSignals ();
|
||||
}), null, (int)interval.TotalMilliseconds, (int)interval.TotalMilliseconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ViewInitializedEventArgs
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<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>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -57,6 +57,10 @@ namespace Ooui.Forms
|
|||
MessagingCenter.Unsubscribe<Page, ActionSheetArguments> (this, Page.ActionSheetSignalName);
|
||||
MessagingCenter.Unsubscribe<Page, AlertArguments> (this, Page.AlertSignalName);
|
||||
MessagingCenter.Unsubscribe<Page, bool> (this, Page.BusySetSignalName);
|
||||
|
||||
DisposeModelAndChildrenRenderers (Page);
|
||||
//foreach (var modal in _modals)
|
||||
//DisposeModelAndChildrenRenderers (modal);
|
||||
}
|
||||
|
||||
public static IVisualElementRenderer CreateRenderer (VisualElement element)
|
||||
|
@ -110,7 +114,29 @@ namespace Ooui.Forms
|
|||
|
||||
void HandleChildRemoved (object sender, ElementEventArgs e)
|
||||
{
|
||||
throw new NotImplementedException ();
|
||||
var view = e.Element;
|
||||
DisposeModelAndChildrenRenderers (view);
|
||||
}
|
||||
|
||||
void DisposeModelAndChildrenRenderers (Xamarin.Forms.Element view)
|
||||
{
|
||||
IVisualElementRenderer renderer;
|
||||
foreach (VisualElement child in view.Descendants ()) {
|
||||
renderer = GetRenderer (child);
|
||||
child.ClearValue (RendererProperty);
|
||||
|
||||
if (renderer != null) {
|
||||
//renderer.NativeView.RemoveFromSuperview ();
|
||||
renderer.Dispose ();
|
||||
}
|
||||
}
|
||||
|
||||
renderer = GetRenderer ((VisualElement)view);
|
||||
if (renderer != null) {
|
||||
//renderer.NativeView.RemoveFromSuperview ();
|
||||
renderer.Dispose ();
|
||||
}
|
||||
view.ClearValue (RendererProperty);
|
||||
}
|
||||
|
||||
void AddChild (VisualElement view)
|
||||
|
|
|
@ -15,5 +15,19 @@ namespace Ooui.Forms
|
|||
{
|
||||
this.platform = platform;
|
||||
}
|
||||
|
||||
protected override bool TriggerEventFromMessage (Message message)
|
||||
{
|
||||
if (message.TargetId == "window" && message.Key == "resize" && message.Value is Newtonsoft.Json.Linq.JObject j) {
|
||||
var width = (double)j["width"];
|
||||
var height = (double)j["height"];
|
||||
Platform.Element.Style.Width = width;
|
||||
Platform.Element.Style.Height = height;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return base.TriggerEventFromMessage (message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace Ooui.Forms.Renderers
|
|||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
if (Control != null) {
|
||||
Control.Clicked -= OnButtonTouchUpInside;
|
||||
Control.Click -= OnButtonTouchUpInside;
|
||||
}
|
||||
|
||||
base.Dispose (disposing);
|
||||
|
@ -44,7 +44,7 @@ namespace Ooui.Forms.Renderers
|
|||
_buttonTextColorDefaultHighlighted = Ooui.Colors.Black;
|
||||
_buttonTextColorDefaultDisabled = Ooui.Colors.Black;
|
||||
|
||||
Control.Clicked += OnButtonTouchUpInside;
|
||||
Control.Click += OnButtonTouchUpInside;
|
||||
}
|
||||
|
||||
UpdateText ();
|
||||
|
|
|
@ -31,8 +31,8 @@ namespace Ooui.Forms.Renderers
|
|||
Type = InputType.Date,
|
||||
};
|
||||
|
||||
entry.Inputted += OnStarted;
|
||||
entry.Changed += OnEnded;
|
||||
//entry.Input += OnStarted;
|
||||
entry.Change += OnEnded;
|
||||
|
||||
SetNativeControl (entry);
|
||||
}
|
||||
|
@ -100,8 +100,8 @@ namespace Ooui.Forms.Renderers
|
|||
|
||||
if (disposing) {
|
||||
if (Control != null) {
|
||||
Control.Inputted -= OnStarted;
|
||||
Control.Changed -= OnEnded;
|
||||
//Control.Input -= OnStarted;
|
||||
Control.Change -= OnEnded;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,12 @@ namespace Ooui.Forms.Renderers
|
|||
bool _disposed;
|
||||
IEditorController ElementController => Element;
|
||||
|
||||
public override SizeRequest GetDesiredSize (double widthConstraint, double heightConstraint)
|
||||
{
|
||||
var size = new Size (160, 100);
|
||||
return new SizeRequest (size, size);
|
||||
}
|
||||
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
|
@ -19,9 +25,9 @@ namespace Ooui.Forms.Renderers
|
|||
|
||||
if (disposing) {
|
||||
if (Control != null) {
|
||||
Control.Changed -= HandleChanged;
|
||||
Control.Input -= HandleChanged;
|
||||
//Control.Started -= OnStarted;
|
||||
//Control.Ended -= OnEnded;
|
||||
Control.Change -= OnEnded;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,9 +46,9 @@ namespace Ooui.Forms.Renderers
|
|||
ClassName = "form-control"
|
||||
});
|
||||
|
||||
Control.Changed += HandleChanged;
|
||||
Control.Input += HandleChanged;
|
||||
//Control.Started += OnStarted;
|
||||
//Control.Ended += OnEnded;
|
||||
Control.Change += OnEnded;
|
||||
}
|
||||
|
||||
UpdateText ();
|
||||
|
@ -75,13 +81,13 @@ namespace Ooui.Forms.Renderers
|
|||
|
||||
void HandleChanged (object sender, EventArgs e)
|
||||
{
|
||||
ElementController.SetValueFromRenderer (Editor.TextProperty, Control.Text);
|
||||
ElementController.SetValueFromRenderer (Editor.TextProperty, Control.Value);
|
||||
}
|
||||
|
||||
void OnEnded (object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (Control.Text != Element.Text)
|
||||
ElementController.SetValueFromRenderer (Editor.TextProperty, Control.Text);
|
||||
if (Control.Value != Element.Text)
|
||||
ElementController.SetValueFromRenderer (Editor.TextProperty, Control.Value);
|
||||
|
||||
Element.SetValue (VisualElement.IsFocusedPropertyKey, false);
|
||||
ElementController.SendCompleted ();
|
||||
|
@ -108,8 +114,8 @@ namespace Ooui.Forms.Renderers
|
|||
|
||||
void UpdateText ()
|
||||
{
|
||||
if (Control.Text != Element.Text)
|
||||
Control.Text = Element.Text;
|
||||
if (Control.Value != Element.Text)
|
||||
Control.Value = Element.Text;
|
||||
}
|
||||
|
||||
void UpdateTextAlignment ()
|
||||
|
|
|
@ -6,7 +6,7 @@ using Xamarin.Forms;
|
|||
|
||||
namespace Ooui.Forms.Renderers
|
||||
{
|
||||
public class EntryRenderer : ViewRenderer<Entry, Ooui.Input>
|
||||
public class EntryRenderer : ViewRenderer<Entry, Ooui.TextInput>
|
||||
{
|
||||
Ooui.Color _defaultTextColor;
|
||||
bool _disposed;
|
||||
|
@ -17,7 +17,17 @@ namespace Ooui.Forms.Renderers
|
|||
|
||||
public override SizeRequest GetDesiredSize (double widthConstraint, double heightConstraint)
|
||||
{
|
||||
var size = Element.Text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes);
|
||||
var text = Element.Text;
|
||||
if (text == null || text.Length == 0) {
|
||||
text = Element.Placeholder;
|
||||
}
|
||||
Size size;
|
||||
if (text == null || text.Length == 0) {
|
||||
size = new Size (Element.FontSize * 0.25, Element.FontSize);
|
||||
}
|
||||
else {
|
||||
size = text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes);
|
||||
}
|
||||
size = new Size (size.Width, size.Height * 1.428 + 14);
|
||||
return new SizeRequest (size, size);
|
||||
}
|
||||
|
@ -32,8 +42,8 @@ namespace Ooui.Forms.Renderers
|
|||
if (disposing) {
|
||||
if (Control != null) {
|
||||
//Control.Inputted -= OnEditingBegan;
|
||||
Control.Inputted -= OnEditingChanged;
|
||||
Control.Changed -= OnEditingEnded;
|
||||
Control.Input -= OnEditingChanged;
|
||||
Control.Change -= OnEditingEnded;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,7 +58,7 @@ namespace Ooui.Forms.Renderers
|
|||
return;
|
||||
|
||||
if (Control == null) {
|
||||
var textField = new Ooui.Input (InputType.Text);
|
||||
var textField = new Ooui.TextInput ();
|
||||
SetNativeControl (textField);
|
||||
|
||||
Debug.Assert (Control != null, "Control != null");
|
||||
|
@ -57,10 +67,10 @@ namespace Ooui.Forms.Renderers
|
|||
|
||||
_defaultTextColor = Colors.Black;
|
||||
|
||||
textField.Inputted += OnEditingChanged;
|
||||
textField.Input += OnEditingChanged;
|
||||
|
||||
//textField.EditingDidBegin += OnEditingBegan;
|
||||
textField.Changed += OnEditingEnded;
|
||||
textField.Change += OnEditingEnded;
|
||||
}
|
||||
|
||||
UpdatePlaceholder ();
|
||||
|
@ -113,8 +123,8 @@ namespace Ooui.Forms.Renderers
|
|||
void OnEditingEnded (object sender, EventArgs e)
|
||||
{
|
||||
// Typing aid changes don't always raise EditingChanged event
|
||||
if (Control.Text != Element.Text) {
|
||||
ElementController.SetValueFromRenderer (Entry.TextProperty, Control.Text);
|
||||
if (Control.Value != Element.Text) {
|
||||
ElementController.SetValueFromRenderer (Entry.TextProperty, Control.Value);
|
||||
}
|
||||
|
||||
ElementController.SetValueFromRenderer (VisualElement.IsFocusedPropertyKey, false);
|
||||
|
|
|
@ -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) {
|
||||
var size = Element.Text.MeasureSize (Element.FontFamily, Element.FontSize, Element.FontAttributes);
|
||||
size.Width = Math.Ceiling (size.Width);
|
||||
size.Height = Math.Ceiling (size.Height * 1.4);
|
||||
_perfectSize = new SizeRequest (size, size);
|
||||
_perfectSizeValid = true;
|
||||
}
|
||||
|
@ -72,6 +74,9 @@ namespace Ooui.Forms.Renderers
|
|||
{
|
||||
base.OnElementPropertyChanged (sender, e);
|
||||
|
||||
if (Control == null)
|
||||
return;
|
||||
|
||||
if (e.PropertyName == Xamarin.Forms.Label.HorizontalTextAlignmentProperty.PropertyName)
|
||||
UpdateAlignment ();
|
||||
else if (e.PropertyName == Xamarin.Forms.Label.VerticalTextAlignmentProperty.PropertyName)
|
||||
|
@ -98,8 +103,10 @@ namespace Ooui.Forms.Renderers
|
|||
|
||||
void UpdateAlignment ()
|
||||
{
|
||||
Control.Style.TextAlign = Element.HorizontalTextAlignment.ToOouiTextAlign ();
|
||||
Control.Style.VerticalAlign = Element.VerticalTextAlignment.ToOouiTextAlign ();
|
||||
this.Style.Display = "table";
|
||||
Control.Style.Display = "table-cell";
|
||||
this.Style.TextAlign = Element.HorizontalTextAlignment.ToOouiTextAlign ();
|
||||
Control.Style.VerticalAlign = Element.VerticalTextAlignment.ToOouiVerticalAlign ();
|
||||
}
|
||||
|
||||
void UpdateLineBreakMode ()
|
||||
|
|
|
@ -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;
|
||||
|
||||
EventTracker _events;
|
||||
VisualElementPackager _packager;
|
||||
VisualElementTracker _tracker;
|
||||
|
||||
|
@ -107,10 +108,10 @@ namespace Ooui.Forms
|
|||
_packager.Load ();
|
||||
}
|
||||
|
||||
//if (AutoTrack && _events == null) {
|
||||
// _events = new EventTracker (this);
|
||||
// _events.LoadEvents (this);
|
||||
//}
|
||||
if (AutoTrack && _events == null) {
|
||||
_events = new EventTracker (this);
|
||||
_events.LoadEvents (this);
|
||||
}
|
||||
|
||||
element.PropertyChanged += _propertyChangedHandler;
|
||||
}
|
||||
|
|
|
@ -81,97 +81,97 @@ namespace Ooui
|
|||
|
||||
public void Save ()
|
||||
{
|
||||
SendCall ("save");
|
||||
Call ("save");
|
||||
}
|
||||
|
||||
public void Restore ()
|
||||
{
|
||||
SendCall ("restore");
|
||||
Call ("restore");
|
||||
}
|
||||
|
||||
public void ClearRect (double x, double y, double w, double h)
|
||||
{
|
||||
SendCall ("clearRect", x, y, w, h);
|
||||
Call ("clearRect", x, y, w, h);
|
||||
}
|
||||
|
||||
public void FillRect (double x, double y, double w, double h)
|
||||
{
|
||||
SendCall ("fillRect", x, y, w, h);
|
||||
Call ("fillRect", x, y, w, h);
|
||||
}
|
||||
|
||||
public void StrokeRect (double x, double y, double w, double h)
|
||||
{
|
||||
SendCall ("strokeRect", x, y, w, h);
|
||||
Call ("strokeRect", x, y, w, h);
|
||||
}
|
||||
|
||||
public void BeginPath ()
|
||||
{
|
||||
SendCall ("beginPath");
|
||||
Call ("beginPath");
|
||||
}
|
||||
|
||||
public void ClosePath ()
|
||||
{
|
||||
SendCall ("closePath");
|
||||
Call ("closePath");
|
||||
}
|
||||
|
||||
public void MoveTo (double x, double y)
|
||||
{
|
||||
SendCall ("moveTo", x, y);
|
||||
Call ("moveTo", x, y);
|
||||
}
|
||||
|
||||
public void LineTo (double x, double y)
|
||||
{
|
||||
SendCall ("lineTo", x, y);
|
||||
Call ("lineTo", x, y);
|
||||
}
|
||||
|
||||
public void QuadraticCurveTo (double cpx, double cpy, double x, double y)
|
||||
{
|
||||
SendCall ("quadraticCurveTo", cpx, cpy, x, y);
|
||||
Call ("quadraticCurveTo", cpx, cpy, x, y);
|
||||
}
|
||||
|
||||
public void BezierCurveTo (double cp1x, double cp1y, double cp2x, double cp2y, double x, double y)
|
||||
{
|
||||
SendCall ("bezierCurveTo", cp1x, cp1y, cp2x, cp2y, x, y);
|
||||
Call ("bezierCurveTo", cp1x, cp1y, cp2x, cp2y, x, y);
|
||||
}
|
||||
|
||||
public void ArcTo (double x1, double y1, double x2, double y2, double radius)
|
||||
{
|
||||
SendCall ("arcTo", x1, y1, x2, y2, radius);
|
||||
Call ("arcTo", x1, y1, x2, y2, radius);
|
||||
}
|
||||
|
||||
public void Rect (double x, double y, double w, double h)
|
||||
{
|
||||
SendCall ("rect", x, y, w, h);
|
||||
Call ("rect", x, y, w, h);
|
||||
}
|
||||
|
||||
public void Arc (double x, double y, double radius, double startAngle, double endAngle, bool counterclockwise)
|
||||
{
|
||||
SendCall ("arc", x, y, radius, startAngle, endAngle, counterclockwise);
|
||||
Call ("arc", x, y, radius, startAngle, endAngle, counterclockwise);
|
||||
}
|
||||
|
||||
public void Fill ()
|
||||
{
|
||||
SendCall ("fill");
|
||||
Call ("fill");
|
||||
}
|
||||
|
||||
public void Stroke ()
|
||||
{
|
||||
SendCall ("stroke");
|
||||
Call ("stroke");
|
||||
}
|
||||
|
||||
public void Clip ()
|
||||
{
|
||||
SendCall ("clip");
|
||||
Call ("clip");
|
||||
}
|
||||
|
||||
public void FillText (string text, double x, double y, double? maxWidth)
|
||||
{
|
||||
SendCall ("fillText", text, x, y, maxWidth);
|
||||
Call ("fillText", text, x, y, maxWidth);
|
||||
}
|
||||
|
||||
public void StrokeText (string text, double x, double y, double? maxWidth)
|
||||
{
|
||||
SendCall ("strokeText", text, x, y, maxWidth);
|
||||
Call ("strokeText", text, x, y, maxWidth);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -57,7 +57,8 @@ function ooui (rootElementPath) {
|
|||
socket.addEventListener ("close", function (event) {
|
||||
console.error ("Web socket close", event);
|
||||
if (opened) {
|
||||
location.reload ();
|
||||
alert ("Connection to the server has been lost. Please try refreshing the page.");
|
||||
opened = false;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -65,11 +66,19 @@ function ooui (rootElementPath) {
|
|||
const messages = JSON.parse (event.data);
|
||||
if (debug) console.log("Messages", messages);
|
||||
if (Array.isArray (messages)) {
|
||||
const jqs = []
|
||||
messages.forEach (function (m) {
|
||||
// console.log('Raw value from server', m.v);
|
||||
m.v = fixupValue (m.v);
|
||||
if (m.k.startsWith ("$.")) {
|
||||
jqs.push (m);
|
||||
}
|
||||
else {
|
||||
processMessage (m);
|
||||
}
|
||||
});
|
||||
// Run jQuery functions last since they usually require a fully built DOM
|
||||
jqs.forEach (processMessage);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -92,8 +101,8 @@ function ooui (rootElementPath) {
|
|||
function resizeHandler() {
|
||||
const em = {
|
||||
m: "event",
|
||||
id: 42,
|
||||
k: "window.resize",
|
||||
id: "window",
|
||||
k: "resize",
|
||||
v: getSize (),
|
||||
};
|
||||
const ems = JSON.stringify (em);
|
||||
|
@ -163,9 +172,11 @@ function msgCall (m) {
|
|||
console.error ("Unknown node id", m);
|
||||
return;
|
||||
}
|
||||
const f = node[m.k];
|
||||
const isJQuery = m.k.startsWith ("$.");
|
||||
const target = isJQuery ? $(node) : node;
|
||||
const f = isJQuery ? target[m.k.slice(2)] : target[m.k];
|
||||
if (debug) console.log ("Call", node, f, m.v);
|
||||
const r = f.apply (node, m.v);
|
||||
const r = f.apply (target, m.v);
|
||||
if (typeof m.rid === 'string' || m.rid instanceof String) {
|
||||
nodes[m.rid] = r;
|
||||
}
|
||||
|
|
17
Ooui/Div.cs
17
Ooui/Div.cs
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ooui
|
||||
{
|
||||
|
@ -8,5 +9,21 @@ namespace Ooui
|
|||
: base ("div")
|
||||
{
|
||||
}
|
||||
|
||||
public Div (params Element[] children)
|
||||
: this ()
|
||||
{
|
||||
foreach (var c in children) {
|
||||
AppendChild (c);
|
||||
}
|
||||
}
|
||||
|
||||
public Div (IEnumerable<Element> children)
|
||||
: this ()
|
||||
{
|
||||
foreach (var c in children) {
|
||||
AppendChild (c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,12 +25,12 @@ namespace Ooui
|
|||
set => SetProperty (ref hidden, value, "hidden");
|
||||
}
|
||||
|
||||
public event TargetEventHandler Clicked {
|
||||
public event TargetEventHandler Click {
|
||||
add => AddEventListener ("click", value);
|
||||
remove => RemoveEventListener ("click", value);
|
||||
}
|
||||
|
||||
public event TargetEventHandler DoubleClicked {
|
||||
public event TargetEventHandler DoubleClick {
|
||||
add => AddEventListener ("dblclick", value);
|
||||
remove => RemoveEventListener ("dblclick", value);
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ namespace Ooui
|
|||
remove => RemoveEventListener ("keydown", value);
|
||||
}
|
||||
|
||||
public event TargetEventHandler KeyPressed {
|
||||
public event TargetEventHandler KeyPress {
|
||||
add => AddEventListener ("keypress", value);
|
||||
remove => RemoveEventListener ("keypress", value);
|
||||
}
|
||||
|
@ -55,17 +55,17 @@ namespace Ooui
|
|||
remove => RemoveEventListener ("mousedown", value);
|
||||
}
|
||||
|
||||
public event TargetEventHandler MouseEntered {
|
||||
public event TargetEventHandler MouseEnter {
|
||||
add => AddEventListener ("mouseenter", value);
|
||||
remove => RemoveEventListener ("mouseenter", value);
|
||||
}
|
||||
|
||||
public event TargetEventHandler MouseLeft {
|
||||
public event TargetEventHandler MouseLeave {
|
||||
add => AddEventListener ("mouseleave", value);
|
||||
remove => RemoveEventListener ("mouseleave", value);
|
||||
}
|
||||
|
||||
public event TargetEventHandler MouseMoved {
|
||||
public event TargetEventHandler MouseMove {
|
||||
add => AddEventListener ("mousemove", value);
|
||||
remove => RemoveEventListener ("mousemove", value);
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ namespace Ooui
|
|||
remove => RemoveEventListener ("mouseup", value);
|
||||
}
|
||||
|
||||
public event TargetEventHandler Wheeled {
|
||||
public event TargetEventHandler Wheel {
|
||||
add => AddEventListener ("wheel", value);
|
||||
remove => RemoveEventListener ("wheel", value);
|
||||
}
|
||||
|
@ -116,5 +116,19 @@ namespace Ooui
|
|||
{
|
||||
SendSet ("style." + Style.GetJsName (e.PropertyName), Style[e.PropertyName]);
|
||||
}
|
||||
|
||||
protected override bool SaveStateMessageIfNeeded (Message message)
|
||||
{
|
||||
if (message.TargetId != Id)
|
||||
return false;
|
||||
|
||||
switch (message.MessageType) {
|
||||
case MessageType.Call when message.Key.StartsWith ("$.", StringComparison.Ordinal):
|
||||
AddStateMessage (message);
|
||||
return true;
|
||||
default:
|
||||
return base.SaveStateMessageIfNeeded (message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -115,7 +115,7 @@ namespace Ooui
|
|||
MessageSent?.Invoke (message);
|
||||
}
|
||||
|
||||
protected void SendCall (string methodName, params object[] args)
|
||||
public void Call (string methodName, params object[] args)
|
||||
{
|
||||
Send (Message.Call (Id, methodName, args));
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace Ooui
|
|||
set => SetProperty (ref enctype, value ?? "", "enctype");
|
||||
}
|
||||
|
||||
public event TargetEventHandler Submitted {
|
||||
public event TargetEventHandler Submit {
|
||||
add => AddEventListener ("submit", value);
|
||||
remove => RemoveEventListener ("submit", value);
|
||||
}
|
||||
|
|
|
@ -30,16 +30,11 @@ namespace Ooui
|
|||
}
|
||||
}
|
||||
|
||||
public event TargetEventHandler Changed {
|
||||
public event TargetEventHandler Change {
|
||||
add => AddEventListener ("change", value);
|
||||
remove => RemoveEventListener ("change", value);
|
||||
}
|
||||
|
||||
public event TargetEventHandler Inputted {
|
||||
add => AddEventListener ("input", value);
|
||||
remove => RemoveEventListener ("input", value);
|
||||
}
|
||||
|
||||
string placeholder = "";
|
||||
public string Placeholder {
|
||||
get => placeholder;
|
||||
|
@ -77,7 +72,7 @@ namespace Ooui
|
|||
: base ("input")
|
||||
{
|
||||
// Subscribe to the change event so we always get up-to-date values
|
||||
Changed += (s, e) => {};
|
||||
Change += (s, e) => {};
|
||||
}
|
||||
|
||||
public Input (InputType type)
|
||||
|
@ -88,7 +83,7 @@ namespace Ooui
|
|||
|
||||
protected override bool TriggerEventFromMessage (Message message)
|
||||
{
|
||||
if (message.TargetId == Id && message.MessageType == MessageType.Event && message.Key == "change") {
|
||||
if (message.TargetId == Id && message.MessageType == MessageType.Event && (message.Key == "change" || message.Key == "input")) {
|
||||
// Don't need to notify here because the base implementation will fire the event
|
||||
if (Type == InputType.Checkbox) {
|
||||
isChecked = message.Value != null ? Convert.ToBoolean (message.Value) : false;
|
||||
|
|
16
Ooui/Node.cs
16
Ooui/Node.cs
|
@ -73,7 +73,8 @@ namespace Ooui
|
|||
}
|
||||
}
|
||||
newChild.MessageSent += HandleChildMessageSent;
|
||||
SendCall ("insertBefore", newChild, referenceChild);
|
||||
Call ("insertBefore", newChild, referenceChild);
|
||||
OnChildInsertedBefore (newChild, referenceChild);
|
||||
return newChild;
|
||||
}
|
||||
|
||||
|
@ -87,10 +88,19 @@ namespace Ooui
|
|||
}
|
||||
}
|
||||
child.MessageSent -= HandleChildMessageSent;
|
||||
SendCall ("removeChild", child);
|
||||
Call ("removeChild", child);
|
||||
OnChildRemoved (child);
|
||||
return child;
|
||||
}
|
||||
|
||||
protected virtual void OnChildInsertedBefore (Node newChild, Node referenceChild)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void OnChildRemoved (Node child)
|
||||
{
|
||||
}
|
||||
|
||||
protected void ReplaceAll (Node newNode)
|
||||
{
|
||||
var toRemove = new List<Node> ();
|
||||
|
@ -100,7 +110,7 @@ namespace Ooui
|
|||
}
|
||||
foreach (var child in toRemove) {
|
||||
child.MessageSent -= HandleChildMessageSent;
|
||||
SendCall ("removeChild", child);
|
||||
Call ("removeChild", child);
|
||||
}
|
||||
InsertBefore (newNode, null);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<Version>0.2.0</Version>
|
||||
<Version>1.0.0</Version>
|
||||
<Authors>praeclarum</Authors>
|
||||
<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>
|
||||
<PackageProjectUrl>https://github.com/praeclarum/Ooui</PackageProjectUrl>
|
||||
<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");
|
||||
}
|
||||
|
||||
public event TargetEventHandler Changed {
|
||||
public event TargetEventHandler Change {
|
||||
add => AddEventListener ("change", value);
|
||||
remove => RemoveEventListener ("change", value);
|
||||
}
|
||||
|
||||
public event TargetEventHandler Input {
|
||||
add => AddEventListener ("input", value);
|
||||
remove => RemoveEventListener ("input", value);
|
||||
}
|
||||
|
||||
public Select ()
|
||||
: base ("select")
|
||||
{
|
||||
// Subscribe to the change event so we always get up-to-date values
|
||||
Change += (s, e) => { };
|
||||
}
|
||||
|
||||
public void AddOption (string label, string value)
|
||||
{
|
||||
AppendChild (new Option { Label = label, Value = value });
|
||||
}
|
||||
|
||||
protected override void OnChildInsertedBefore (Node newChild, Node referenceChild)
|
||||
{
|
||||
base.OnChildInsertedBefore (newChild, referenceChild);
|
||||
if (string.IsNullOrEmpty (val) && newChild is Option o && !string.IsNullOrEmpty (o.Value)) {
|
||||
val = o.Value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool TriggerEventFromMessage (Message message)
|
||||
{
|
||||
if (message.TargetId == Id && message.MessageType == MessageType.Event && (message.Key == "change" || message.Key == "input")) {
|
||||
val = message.Value != null ? Convert.ToString (message.Value) : "";
|
||||
}
|
||||
return base.TriggerEventFromMessage (message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,38 +93,38 @@ namespace Ooui
|
|||
|
||||
public Value BorderTopWidth {
|
||||
get => this["border-top-width"];
|
||||
set => this["border-top-width"] = value;
|
||||
set => this["border-top-width"] = AddNumberUnits (value, "px");
|
||||
}
|
||||
|
||||
public Value BorderRightWidth {
|
||||
get => this["border-right-width"];
|
||||
set => this["border-right-width"] = value;
|
||||
set => this["border-right-width"] = AddNumberUnits (value, "px");
|
||||
}
|
||||
|
||||
public Value BorderBottomWidth {
|
||||
get => this["border-bottom-width"];
|
||||
set => this["border-bottom-width"] = value;
|
||||
set => this["border-bottom-width"] = AddNumberUnits (value, "px");
|
||||
}
|
||||
|
||||
public Value BorderLeftWidth {
|
||||
get => this["border-left-width"];
|
||||
set => this["border-left-width"] = value;
|
||||
set => this["border-left-width"] = AddNumberUnits (value, "px");
|
||||
}
|
||||
|
||||
public Value BorderRadius {
|
||||
get => this["border-radius"];
|
||||
set {
|
||||
this["border-radius"] = value;
|
||||
this["border-radius"] = AddNumberUnits (value, "px");
|
||||
}
|
||||
}
|
||||
|
||||
public Value BorderWidth {
|
||||
get => this["border-top-width"];
|
||||
set {
|
||||
this["border-top-width"] = value;
|
||||
this["border-right-width"] = value;
|
||||
this["border-bottom-width"] = value;
|
||||
this["border-left-width"] = value;
|
||||
this["border-top-width"] = AddNumberUnits (value, "px");
|
||||
this["border-right-width"] = AddNumberUnits (value, "px");
|
||||
this["border-bottom-width"] = AddNumberUnits (value, "px");
|
||||
this["border-left-width"] = AddNumberUnits (value, "px");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -253,6 +253,11 @@ namespace Ooui
|
|||
set => this["order"] = value;
|
||||
}
|
||||
|
||||
public Value Overflow {
|
||||
get => this["overflow"];
|
||||
set => this["overflow"] = value;
|
||||
}
|
||||
|
||||
public Value PaddingTop {
|
||||
get => this["padding-top"];
|
||||
set => this["padding-top"] = value;
|
||||
|
@ -403,6 +408,8 @@ namespace Ooui
|
|||
|
||||
static string AddNumberUnits (object val, string units)
|
||||
{
|
||||
if (val == null)
|
||||
return null;
|
||||
if (val is string s)
|
||||
return s;
|
||||
if (val is IConvertible c)
|
||||
|
|
|
@ -4,12 +4,12 @@ namespace Ooui
|
|||
{
|
||||
public class TextArea : FormControl
|
||||
{
|
||||
public event TargetEventHandler Changed {
|
||||
public event TargetEventHandler Change {
|
||||
add => AddEventListener ("change", value);
|
||||
remove => RemoveEventListener ("change", value);
|
||||
}
|
||||
|
||||
public event TargetEventHandler Inputted {
|
||||
public event TargetEventHandler Input {
|
||||
add => AddEventListener ("input", value);
|
||||
remove => RemoveEventListener ("input", value);
|
||||
}
|
||||
|
@ -36,18 +36,18 @@ namespace Ooui
|
|||
: base ("textarea")
|
||||
{
|
||||
// Subscribe to the change event so we always get up-to-date values
|
||||
Changed += (s, e) => {};
|
||||
Change += (s, e) => {};
|
||||
}
|
||||
|
||||
public TextArea (string text)
|
||||
: this ()
|
||||
{
|
||||
Text = text;
|
||||
Value = text;
|
||||
}
|
||||
|
||||
protected override bool TriggerEventFromMessage (Message message)
|
||||
{
|
||||
if (message.TargetId == Id && message.MessageType == MessageType.Event && message.Key == "change") {
|
||||
if (message.TargetId == Id && message.MessageType == MessageType.Event && (message.Key == "change" || message.Key == "input")) {
|
||||
// Don't need to notify here because the base implementation will fire the event
|
||||
val = message.Value != null ? Convert.ToString (message.Value) : "";
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
148
Ooui/UI.cs
148
Ooui/UI.cs
|
@ -14,6 +14,9 @@ namespace Ooui
|
|||
{
|
||||
static readonly ManualResetEvent started = new ManualResetEvent (false);
|
||||
|
||||
[ThreadStatic]
|
||||
static System.Security.Cryptography.SHA256 sha256;
|
||||
|
||||
static CancellationTokenSource serverCts;
|
||||
|
||||
static readonly Dictionary<string, RequestHandler> publishedPaths =
|
||||
|
@ -26,8 +29,10 @@ namespace Ooui
|
|||
public static StyleSelectors Styles => rules;
|
||||
|
||||
static readonly byte[] clientJsBytes;
|
||||
static readonly string clientJsEtag;
|
||||
|
||||
public static byte[] ClientJsBytes => clientJsBytes;
|
||||
public static string ClientJsEtag => clientJsEtag;
|
||||
|
||||
public static string Template { get; set; } = $@"<!DOCTYPE html>
|
||||
<html>
|
||||
|
@ -35,10 +40,14 @@ namespace Ooui
|
|||
<title>@Title</title>
|
||||
<meta name=""viewport"" content=""width=device-width, initial-scale=1"" />
|
||||
<link rel=""stylesheet"" href=""https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"" />
|
||||
<link rel=""stylesheet"" href=""https://gitcdn.github.io/bootstrap-toggle/2.2.2/css/bootstrap-toggle.min.css"" />
|
||||
<style>@Styles</style>
|
||||
</head>
|
||||
<body>
|
||||
<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>ooui(""@WebSocketPath"");</script>
|
||||
</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 ()
|
||||
{
|
||||
|
@ -79,6 +101,22 @@ namespace Ooui
|
|||
clientJsBytes = Encoding.UTF8.GetBytes (r.ReadToEnd ());
|
||||
}
|
||||
}
|
||||
clientJsEtag = "\"" + Hash (clientJsBytes) + "\"";
|
||||
}
|
||||
|
||||
public static string Hash (byte[] bytes)
|
||||
{
|
||||
var sha = sha256;
|
||||
if (sha == null) {
|
||||
sha = System.Security.Cryptography.SHA256.Create ();
|
||||
sha256 = sha;
|
||||
}
|
||||
var data = sha.ComputeHash (bytes);
|
||||
StringBuilder sBuilder = new StringBuilder ();
|
||||
for (int i = 0; i < data.Length; i++) {
|
||||
sBuilder.Append (data[i].ToString ("x2"));
|
||||
}
|
||||
return sBuilder.ToString ();
|
||||
}
|
||||
|
||||
static void Publish (string path, RequestHandler handler)
|
||||
|
@ -110,7 +148,47 @@ namespace Ooui
|
|||
if (contentType == null) {
|
||||
contentType = GuessContentType (path, filePath);
|
||||
}
|
||||
Publish (path, new DataHandler (data, contentType));
|
||||
var etag = "\"" + Hash (data) + "\"";
|
||||
Publish (path, new DataHandler (data, etag, contentType));
|
||||
}
|
||||
|
||||
public static void PublishFile (string path, byte[] data, string contentType)
|
||||
{
|
||||
var etag = "\"" + Hash (data) + "\"";
|
||||
Publish (path, new DataHandler (data, etag, contentType));
|
||||
}
|
||||
|
||||
public static void PublishFile (string path, byte[] data, string etag, string contentType)
|
||||
{
|
||||
Publish (path, new DataHandler (data, etag, contentType));
|
||||
}
|
||||
|
||||
public static bool TryGetFileContentAtPath (string path, out FileContent file)
|
||||
{
|
||||
RequestHandler handler;
|
||||
lock (publishedPaths) {
|
||||
if (!publishedPaths.TryGetValue (path, out handler)) {
|
||||
file = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (handler is DataHandler dh) {
|
||||
file = new FileContent {
|
||||
Etag = dh.Etag,
|
||||
Content = dh.Data,
|
||||
ContentType = dh.ContentType,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
file = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public class FileContent
|
||||
{
|
||||
public string ContentType { get; set; }
|
||||
public string Etag { get; set; }
|
||||
public byte[] Content { get; set; }
|
||||
}
|
||||
|
||||
public static void PublishJson (string path, Func<object> ctor)
|
||||
|
@ -121,7 +199,8 @@ namespace Ooui
|
|||
public static void PublishJson (string path, object 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)
|
||||
|
@ -153,6 +232,7 @@ namespace Ooui
|
|||
|
||||
static void Start ()
|
||||
{
|
||||
if (!serverEnabled) return;
|
||||
if (serverCts != null) return;
|
||||
serverCts = new CancellationTokenSource ();
|
||||
var token = serverCts.Token;
|
||||
|
@ -232,13 +312,23 @@ namespace Ooui
|
|||
var response = listenerContext.Response;
|
||||
|
||||
if (path == "/ooui.js") {
|
||||
var inm = listenerContext.Request.Headers.Get ("If-None-Match");
|
||||
if (string.IsNullOrEmpty (inm) || inm != clientJsEtag) {
|
||||
response.StatusCode = 200;
|
||||
response.ContentLength64 = clientJsBytes.LongLength;
|
||||
response.ContentType = "application/javascript";
|
||||
response.ContentEncoding = Encoding.UTF8;
|
||||
response.AddHeader ("Cache-Control", "public, max-age=3600");
|
||||
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 {
|
||||
var found = false;
|
||||
|
@ -308,11 +398,17 @@ namespace Ooui
|
|||
class DataHandler : RequestHandler
|
||||
{
|
||||
readonly byte[] data;
|
||||
readonly string etag;
|
||||
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.etag = etag;
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
|
@ -322,7 +418,13 @@ namespace Ooui
|
|||
var path = url.LocalPath;
|
||||
var response = listenerContext.Response;
|
||||
|
||||
var inm = listenerContext.Request.Headers.Get ("If-None-Match");
|
||||
if (!string.IsNullOrEmpty (inm) && inm == etag) {
|
||||
response.StatusCode = 304;
|
||||
}
|
||||
else {
|
||||
response.StatusCode = 200;
|
||||
response.AddHeader ("Etag", etag);
|
||||
if (!string.IsNullOrEmpty (contentType))
|
||||
response.ContentType = contentType;
|
||||
response.ContentLength64 = data.LongLength;
|
||||
|
@ -330,6 +432,7 @@ namespace Ooui
|
|||
using (var s = response.OutputStream) {
|
||||
s.Write (data, 0, data.Length);
|
||||
}
|
||||
}
|
||||
response.Close ();
|
||||
}
|
||||
}
|
||||
|
@ -432,11 +535,32 @@ namespace Ooui
|
|||
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
|
||||
//
|
||||
try {
|
||||
var session = new Session (webSocket, element, 1024, 768, serverToken);
|
||||
var session = new Session (webSocket, element, w, h, serverToken);
|
||||
await session.RunAsync ().ConfigureAwait (false);
|
||||
}
|
||||
catch (WebSocketException ex) when (ex.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely) {
|
||||
|
@ -470,9 +594,11 @@ namespace Ooui
|
|||
readonly HashSet<string> createdIds;
|
||||
readonly List<Message> queuedMessages = new List<Message> ();
|
||||
|
||||
public const int MaxFps = 30;
|
||||
|
||||
readonly System.Timers.Timer sendThrottle;
|
||||
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 initialHeight;
|
||||
|
||||
|
@ -619,6 +745,7 @@ namespace Ooui
|
|||
//
|
||||
// Add it to the queue
|
||||
//
|
||||
//Console.WriteLine ($"QM {message.MessageType} {message.TargetId} {message.Key} {message.Value}");
|
||||
queuedMessages.Add (message);
|
||||
}
|
||||
|
||||
|
@ -637,19 +764,24 @@ namespace Ooui
|
|||
// Dequeue as many messages as we can
|
||||
//
|
||||
var messagesToSend = new List<Message> ();
|
||||
System.Runtime.CompilerServices.ConfiguredTaskAwaitable task;
|
||||
lock (queuedMessages) {
|
||||
messagesToSend.AddRange (queuedMessages);
|
||||
queuedMessages.Clear ();
|
||||
}
|
||||
|
||||
if (messagesToSend.Count == 0)
|
||||
return;
|
||||
|
||||
//
|
||||
// Now actually send this message
|
||||
// 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));
|
||||
await webSocket.SendAsync (outputBuffer, WebSocketMessageType.Text, true, token).ConfigureAwait (false);
|
||||
//Console.WriteLine ("TRANSMIT " + json);
|
||||
task = webSocket.SendAsync (outputBuffer, WebSocketMessageType.Text, true, token).ConfigureAwait (false);
|
||||
}
|
||||
await task;
|
||||
}
|
||||
catch (Exception 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 label = new Label { Text = "0" };
|
||||
var btn = new Button { Text = "Increase" };
|
||||
btn.Clicked += (sender, e) => {
|
||||
btn.Click += (sender, e) => {
|
||||
count++;
|
||||
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.
|
||||
|
||||
|
@ -45,7 +49,7 @@ class Program
|
|||
|
||||
// Add some logic to it
|
||||
var count = 0;
|
||||
button.Clicked += (s, e) => {
|
||||
button.Click += (s, e) => {
|
||||
count++;
|
||||
button.Text = $"Clicked {count} times";
|
||||
};
|
||||
|
@ -77,7 +81,7 @@ Button MakeButton()
|
|||
{
|
||||
var button = new Button("Click me!");
|
||||
var count = 0;
|
||||
button.Clicked += (s, e) => {
|
||||
button.Click += (s, e) => {
|
||||
count++;
|
||||
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";
|
||||
var count = 0;
|
||||
button.Clicked += (s, e) => {
|
||||
button.Click += (s, e) => {
|
||||
count++;
|
||||
button.Text = $"Clicked {count} times";
|
||||
};
|
||||
|
|
|
@ -4,10 +4,15 @@
|
|||
x:Class="Samples.DisplayAlertPage">
|
||||
<ContentPage.Content>
|
||||
<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" />
|
||||
<ProgressBar x:Name="progress" />
|
||||
<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"
|
||||
Clicked="OnButtonClicked" />
|
||||
</StackLayout>
|
||||
|
|
|
@ -18,15 +18,24 @@ namespace Samples
|
|||
{
|
||||
var heading = new Heading ("Draw");
|
||||
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 {
|
||||
Width = 320,
|
||||
Height = 240,
|
||||
};
|
||||
var context = canvas.GetContext2D ();
|
||||
|
||||
canvas.Clicked += (s, e) => {
|
||||
canvas.Click += (s, e) => {
|
||||
var radius = 10;
|
||||
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 ();
|
||||
};
|
||||
canvas.Style.Cursor = "pointer";
|
||||
|
@ -38,7 +47,7 @@ namespace Samples
|
|||
Type = ButtonType.Submit,
|
||||
ClassName = "btn btn-danger",
|
||||
};
|
||||
clearbtn.Clicked += (s, e) => {
|
||||
clearbtn.Click += (s, e) => {
|
||||
context.ClearRect (0, 0, canvas.Width, canvas.Height);
|
||||
};
|
||||
clearbtn.Style.Display = "block";
|
||||
|
@ -46,6 +55,7 @@ namespace Samples
|
|||
var app = new Div ();
|
||||
app.AppendChild (heading);
|
||||
app.AppendChild (subtitle);
|
||||
app.AppendChild (new Div (toolSel));
|
||||
app.AppendChild (canvas);
|
||||
app.AppendChild (clearbtn);
|
||||
return app;
|
||||
|
|
|
@ -5,7 +5,7 @@ namespace Samples
|
|||
{
|
||||
public class EditorSample : ISample
|
||||
{
|
||||
public string Title => "Editor Sample";
|
||||
public string Title => "Xamarin.Forms Editor Sample";
|
||||
|
||||
public Ooui.Element CreateElement()
|
||||
{
|
||||
|
@ -13,7 +13,9 @@ namespace Samples
|
|||
|
||||
var titleLabel = new Xamarin.Forms.Label
|
||||
{
|
||||
Text = "Editor"
|
||||
Text = "Editor",
|
||||
FontSize = 24,
|
||||
FontAttributes = FontAttributes.Bold,
|
||||
};
|
||||
panel.Children.Add(titleLabel);
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="**/*.xaml" />
|
||||
<EmbeddedResource Include="BugSweeper\Images\RedBug.png" />
|
||||
<EmbeddedResource Include="BugSweeper\Images\Xamarin120.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -41,6 +43,10 @@
|
|||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="BugSweeper\Images\RedBug.png" />
|
||||
<None Remove="BugSweeper\Images\Xamarin120.png" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
|
|
|
@ -66,19 +66,19 @@ namespace Samples
|
|||
if (string.IsNullOrWhiteSpace (input.Value))
|
||||
return;
|
||||
var item = new Item (input.Value);
|
||||
item.Clicked += (s, e) => {
|
||||
item.Click += (s, e) => {
|
||||
item.IsDone = !item.IsDone;
|
||||
};
|
||||
items.InsertBefore (item, items.FirstChild);
|
||||
input.Value = "";
|
||||
}
|
||||
addbtn.Clicked += (s, e) => {
|
||||
addbtn.Click += (s, e) => {
|
||||
AddItem ();
|
||||
};
|
||||
inputForm.Submitted += (s, e) => {
|
||||
inputForm.Submit += (s, e) => {
|
||||
AddItem ();
|
||||
};
|
||||
clearbtn.Clicked += (s, e) => {
|
||||
clearbtn.Click += (s, e) => {
|
||||
var toremove = new List<Node> ();
|
||||
foreach (Item i in items.Children) {
|
||||
if (i.IsDone) toremove.Add (i);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<ContentPage.Content>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
|
@ -10,8 +11,12 @@
|
|||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Editor x:Name="editor" FontFamily="monospace" Grid.Row="0" Grid.Column="0" />
|
||||
<ContentView x:Name="results" Grid.Row="0" Grid.Column="1" BackgroundColor="White" />
|
||||
<StackLayout Orientation="Vertical" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2">
|
||||
<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>
|
||||
</ContentPage.Content>
|
||||
</ContentPage>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using System.Threading;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Samples
|
||||
|
@ -23,24 +23,50 @@ namespace Samples
|
|||
<ColumnDefinition Width=""*"" />
|
||||
<ColumnDefinition Width=""*"" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label Text=""Top Left"" Grid.Row=""0"" Grid.Column=""0"" />
|
||||
<Label Text=""Top Right"" Grid.Row=""0"" Grid.Column=""1"" />
|
||||
<Label Text=""Bottom Left"" Grid.Row=""1"" Grid.Column=""0"" />
|
||||
<Label Text=""Bottom Right"" Grid.Row=""1"" Grid.Column=""1"" />
|
||||
<StackLayout Grid.Row=""0"" Grid.Column=""0"">
|
||||
<Label Text=""Top Left"" />
|
||||
<Entry Placeholder=""I'm ready for some text"" />
|
||||
<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>
|
||||
</ContentView>";
|
||||
editor.TextChanged += (sender, e) => DisplayXaml ();
|
||||
DisplayXaml ();
|
||||
}
|
||||
|
||||
CancellationTokenSource lastCts = null;
|
||||
|
||||
public void DisplayXaml ()
|
||||
{
|
||||
try {
|
||||
var cts = new CancellationTokenSource ();
|
||||
var token = cts.Token;
|
||||
lastCts?.Cancel ();
|
||||
lastCts = cts;
|
||||
|
||||
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);
|
||||
};
|
||||
Assert.IsFalse (listened);
|
||||
b.Clicked += (s, e) => {
|
||||
b.Click += (s, e) => {
|
||||
clicked = true;
|
||||
};
|
||||
Assert.IsTrue (listened);
|
||||
|
|
|
@ -52,7 +52,7 @@ namespace Tests
|
|||
var b = new Button ();
|
||||
p.AppendChild (b);
|
||||
var clicked = false;
|
||||
b.Clicked += (s, e) => {
|
||||
b.Click += (s, e) => {
|
||||
clicked = true;
|
||||
};
|
||||
p.Receive (Message.Event (b.Id, "click"));
|
||||
|
|
|
@ -75,7 +75,7 @@ namespace Tests
|
|||
{
|
||||
var s = new Style ();
|
||||
s.BorderLeftWidth = 3.142;
|
||||
Assert.AreEqual ("border-left-width:3.142", s.ToString ());
|
||||
Assert.AreEqual ("border-left-width:3.142px", s.ToString ());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
|
|
@ -7,7 +7,7 @@ Type=simple
|
|||
Environment=HOME=/home/ubuntu
|
||||
WorkingDirectory=/home/ubuntu/Ooui/PlatformSamples/AspNetCoreMvc
|
||||
ExecStart=/usr/bin/dotnet run --no-build --server.urls=http://0.0.0.0:80/
|
||||
Restart=on-abort
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
Loading…
Reference in New Issue