Created
March 26, 2014 18:17
-
-
Save bubbafat/9789791 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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