Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
using FirebaseSharp.Portable;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace FirebaseWpfDraw
{
public class InitialPayload
{
public string path;
public Dictionary<string, string> data = new Dictionary<string, string>();
}
public class IncrementalPayload
{
public string path;
public string data;
}
public class PaintQueue
{
public Point Point;
public string Color;
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
// the web demo is at
// https://www.firebase.com/tutorial/#session/tsqic8m0ifl
private readonly Dictionary<string, SolidColorBrush> _brushMap;
private readonly Firebase _firebase = new Firebase("https://tsqic8m0ifl.firebaseio-demo.com/");
private readonly BackgroundWorker _firebaseWorker = new BackgroundWorker();
private readonly BlockingCollection<PaintQueue> _queue = new BlockingCollection<PaintQueue>();
public MainWindow()
{
InitializeComponent();
_brushMap = new Dictionary<string, SolidColorBrush>();
// the colors that the web demo uses
string[] colors =
{
"fff", "000", "f00", "0f0", "00f", "88f", "f8d", "f88", "f05", "f80", "0f8", "cf0", "08f",
"408", "ff8", "8ff"
};
// cache the brushes for the known colors
foreach (string color in colors)
{
GetBrushFromFirebaseColor(color);
}
// this is the worker loop that does all the communication with Firebase
_firebaseWorker = new BackgroundWorker();
_firebaseWorker.DoWork += _firebaseWorker_DoWork;
}
void _firebaseWorker_DoWork(object sender, DoWorkEventArgs e)
{
// setup streaming
_firebase.GetStreaming(string.Empty, PaintFromFirebase);
// changes are queued so that the UI thread doesn't need
// to do anything expensive
while (true)
{
PaintQueue queue = _queue.Take();
try
{
_firebase.PutAsync(string.Format("{0}:{1}", queue.Point.X, queue.Point.Y),
string.Format("\"{0}\"", queue.Color)).Wait();
}
catch (Exception)
{
// This is really robust
}
}
}
// wait to start the background worker until the canvas is loaded
private void PaintCanvas_OnLoaded(object sender, RoutedEventArgs e)
{
_firebaseWorker.RunWorkerAsync();
}
// a new streaming message came in
private void PaintFromFirebase(StreamingEvent ev)
{
if (ev.Event != "put")
{
return;
}
// the initial data in the stream contains a bunch of stuff
if (!TryLoadInitialData(ev))
{
// but incremental updates contain only a little
var result = JsonConvert.DeserializeObject<IncrementalPayload>(ev.Payload);
// now put the pixel on the canvas
PaintPoint(NormalizedPointFromFirebase(result.path.Substring(1)), GetBrushFromFirebaseColor(result.data));
}
}
// super lazy method - instead of creating a proper model I do this
// I need to create a better notion of child updates in the client library
private bool TryLoadInitialData(StreamingEvent ev)
{
InitialPayload result;
try
{
result = JsonConvert.DeserializeObject<InitialPayload>(ev.Payload);
}
catch (Exception)
{
return false;
}
if (result.data != null)
{
foreach (var kvp in result.data)
{
PaintPoint(NormalizedPointFromFirebase(kvp.Key), GetBrushFromFirebaseColor(kvp.Value));
}
}
return true;
}
// paint a point
private void PaintCanvas_OnMouseDown(object sender, MouseButtonEventArgs e)
{
Point firebasePoint = FirebasePointFromCanvas(GetNormalizedPoint(e.GetPosition(PaintCanvas)));
_queue.Add(new PaintQueue
{
Point = firebasePoint,
Color = "000",
});
}
// this is where we'd paint lines if we wanted
private void PaintCanvas_OnMouseMove(object sender, MouseEventArgs e)
{
// paint a line
}
// paint on the canvas
void PaintPoint(Point point, Brush brush)
{
Point normalized = GetNormalizedPoint(point);
PaintCanvas.Dispatcher.BeginInvoke((Action)(() => PaintCanvas.Children.Add(RectangleFromPoint(normalized, brush))));
}
// "000" -> Black brush
Brush GetBrushFromFirebaseColor(string color)
{
SolidColorBrush brush;
if (!_brushMap.TryGetValue(color, out brush))
{
Color c = (Color)ColorConverter.ConvertFromString(ThreeDigitToSixDigitHex(color));
brush = new SolidColorBrush(c);
_brushMap.Add(color, brush);
}
return brush;
}
// "4:12" -> Point(32,96)
Point NormalizedPointFromFirebase(string firebaseLoc)
{
string[] loc = firebaseLoc.Split(new[] { ':' }, 2, StringSplitOptions.RemoveEmptyEntries);
int x = int.Parse(loc[0]);
int y = int.Parse(loc[1]);
return GetNormalizedPoint(CanvasPointFromFirebase(x, y));
}
// [4,12] -> Point(32,96)
private Point CanvasPointFromFirebase(int x, int y)
{
x = Math.Max(x, 0) * 8;
y = Math.Max(y, 0) * 8;
return new Point(x, y);
}
// Point(32,96) -> Point(4,12)
private Point FirebasePointFromCanvas(Point point)
{
point.X = point.X/8;
point.Y = point.Y/8;
return point;
}
// "0fa" -> "#00ffaa"
private string ThreeDigitToSixDigitHex(string threeDigit)
{
StringBuilder sb = new StringBuilder(7);
sb.Append('#');
sb.Append(threeDigit[0]);
sb.Append(threeDigit[0]);
sb.Append(threeDigit[1]);
sb.Append(threeDigit[1]);
sb.Append(threeDigit[2]);
sb.Append(threeDigit[2]);
return sb.ToString();
}
// Point(33, 98) -> POint(32, 96)
// remember, we're showing 8x8 blocks as a single "pixel"
static Point GetNormalizedPoint(Point point)
{
// align to nearest large pixel boundary
return new Point(
(((int)point.X / 8) * 8),
(((int)point.Y / 8) * 8)
);
}
// figures out where the point is on the rect and
// builds a rectangle that can be displayed
Rectangle RectangleFromPoint(Point point, Brush brush)
{
Rectangle r = new Rectangle
{
Height = 8,
Width = 8,
Fill = brush,
};
Canvas.SetLeft(r, point.X);
Canvas.SetTop(r, point.Y);
return r;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.