#.think.in
learn.create.enjoy

Silverlight Dynamic Languages SDK

January 26, 2009 15:12 by tarn

I decided to have a look at creating Silverlight applications with IronPython, this led me to the Silverlight Dynamic Languages SDK. The main piece of the SDK is Chiron which is a development utility for creating Silverlight application packages (XAPs) from dynamic languages.

Chiron can be used to:

  • Create dynamic language Silverlight projects
  • Start a local web server which dynamically generates XAPs from the project files
  • Write generated XAPs to disk.   

I needed something to build on Silverlight with IronPython, so I decided to write a small part of the UI for an RSS Feed Visualizer I've been wanting to write. Its only really a demo, the UI could use some more work and I've just used some sample names as data. I hope to build on this example in the future, but I'll need to do some refactoring, the code is pretty scrappy as I wrote most of it after I lost a house poker game. Apparently a stronger understanding of counter intuitive probability problems hasn't helped my game and I don't write my best code after an afternoon of drinking. 

(If there is problems viewing the demo on this page, it can also be viewed here)

Here is the IronPython code that drives the demo, I've also uploaded the entire project. I used examples of dragging and inertia from the excellent Project "Rosetta Stone" Tutorials.

   1: from System.Windows import Application
   2: from System.Windows.Controls import UserControl
   3: from System.Windows.Controls import TextBlock
   4: from System.Windows.Controls import Canvas
   5: from System.Windows.Media import Colors
   6: from System.Windows.Media import SolidColorBrush
   7: from System.Windows import Visibility
   8: from System.Windows import FontWeights
   9: from System.Windows import HorizontalAlignment
  10: from System.Windows.Media.Animation import Storyboard
  11: from System import Random;
  12: import math
  13:  
  14: class App:
  15:   def __init__(self):
  16:     self.root = Application.Current.LoadRootVisual(UserControl(), "app.xaml");
  17:     spinner = Spinner(self.root,['Tarn','Leah','Alex','Mick','John','Rex','Jack','Jill','Elle']);
  18:  
  19:  
  20: class Spinner():
  21:   def __init__(self, root, names):
  22:     self.root = root;
  23:     self.names = names
  24:     self.radius = 0;
  25:     self.textBlocks = [];
  26:     self.mainText = Block(self.root.layout_root,self.centreClick);
  27:     self.mainText.SetPosition(100.0,100.0);
  28:     self.mainText.HorizontalAlignment = HorizontalAlignment.Center;
  29:     self.selectedItem = None;
  30:     self.angle = 0;
  31:     self.diff = 0;
  32:     self.target = 0;
  33:     self.drag = False;
  34:     self.createTextBlocks(self.names[0], self.names);
  35:     self.root.MouseLeftButtonDown += self.leftButtonDown;
  36:     self.root.MouseLeftButtonUp += self.leftButtonUp;
  37:     self.root.MouseMove += self.mouseMove;
  38:     self.sb = Storyboard();
  39:     self.root.Resources.Add("sb",self.sb);
  40:     self.sb.Completed += self.enterFrame;
  41:     self.sb.Begin();
  42:   def centreClick(self,item):
  43:     self.selectedItem = item;
  44:     self.state = "Selected";
  45:     pass;
  46:   def createTextBlocks(self, item, list):
  47:     self.state = "Expanding";
  48:     self.mainText.text.Opacity = 1;
  49:     self.mainText.text.Text = item;
  50:     self.velocity = 0.01;
  51:     for textBlock in self.textBlocks:
  52:       textBlock.dispose();
  53:     textBlocks = [];
  54:     i = 0;
  55:     list = filter(lambda m: (m != item), list)
  56:  
  57:     for title in list:
  58:       textBox = Block(self.root.layout_root,self.centreClick);
  59:       textBox.text.Text = title;
  60:       textBox.angle = i * 2 * math.pi / list.__len__();
  61:       textBox.HorizontalAlignment = HorizontalAlignment.Center;
  62:       textBox.radius = -(100/list.__len__()) * i;
  63:       textBox.SetPosition2(0);
  64:       self.textBlocks.append(textBox);
  65:       i += 1;
  66:   def enterFrame(self, object, e):
  67:     diff = ((self.target - self.diff)-self.angle) ;
  68:     if (self.drag == True):
  69:       oldAngle = self.angle;
  70:       if (diff > math.pi): diff = diff - math.pi * 2;
  71:       if (diff <= -math.pi): diff = diff + math.pi * 2;     
  72:       self.angle  = self.angle + ((diff) * 0.2); 
  73:       self.velocity = self.angle - oldAngle;
  74:     else:
  75:       self.angle = self.angle + self.velocity;
  76:       if (math.fabs(self.velocity) > 0.01): 
  77:         self.velocity = self.velocity * 0.90;
  78:     self.angle = self.angle % (2 * math.pi);
  79:  
  80:     if (self.state == "Expanding"):
  81:       expandComplete = True;
  82:       for textBlock in self.textBlocks:
  83:         if (textBlock.Expand(100) == False): expandComplete = False;    
  84:     if (self.state == "Selected"):
  85:       expandComplete = True;
  86:       if (self.selectedItem != self.mainText): 
  87:         self.mainText.text.Opacity -= 0.10;
  88:         self.selectedItem.moveTo(100,100);
  89:       for textBlock in self.textBlocks: 
  90:         if (self.selectedItem != textBlock):
  91:           textBlock.text.Opacity -= 0.010;
  92:           if (textBlock.Expand(200) == False): expandComplete = False;    
  93:       if (expandComplete == True): 
  94:         self.createTextBlocks(self.selectedItem.text.Text, self.names);
  95:     for textBlock in self.textBlocks:
  96:       if (self.selectedItem != textBlock): textBlock.SetPosition2(self.angle);
  97:     self.sb.Begin();
  98:   def leftButtonUp(self,object,e):
  99:     self.root.ReleaseMouseCapture();
 100:     self.drag = False;
 101:   def mouseMove(self, object, e):
 102:     if (self.drag == True):
 103:         self.target = self.getAngle(e);
 104:   def leftButtonDown(self, object, e):
 105:       self.root.CaptureMouse();
 106:       angle = self.getAngle(e);
 107:       self.diff = angle - self.angle;
 108:       self.target = self.getAngle(e);
 109:       self.drag = True;
 110:   def getAngle(self, e):
 111:     x = e.GetPosition(self.root).X-100;
 112:     y = e.GetPosition(self.root).Y-100;
 113:     angle = (math.atan2(x,y));
 114:     if (angle < 0): angle = angle + (math.pi*2);
 115:     return angle;
 116:  
 117: class Block():
 118:   def __init__(self, canvas, clicked):
 119:     self.clicked = clicked;
 120:     self.canvas = canvas;
 121:     self.text = TextBlock();
 122:     self.text.Foreground = SolidColorBrush(Colors.White);
 123:     self.offset = 100;
 124:     self.radius = 10;
 125:     self.angle = 0;
 126:     canvas.Children.Add(self.text);
 127:     self.text.MouseEnter += self.mouseOver;
 128:     self.text.MouseLeave += self.mouseOut;
 129:     self.text.MouseLeftButtonDown += self.mouseClick;
 130:   def dispose(self):
 131:     self.canvas.Children.Remove(self.text);
 132:   def moveTo(self, x, y):
 133:     self.text.FontWeight = FontWeights.Normal;
 134:     currentX = self.text.GetValue(Canvas.LeftProperty);
 135:     currentY = self.text.GetValue(Canvas.TopProperty);
 136:     newX = currentX + ( (x - currentX) * 0.1);
 137:     newY = currentY + ( (y - currentY) * 0.1);
 138:     self.SetPosition(newX,newY);
 139:   def Expand(self, max):
 140:     if (self.radius < max):
 141:       self.radius += 1;
 142:       return False;
 143:   def mouseClick(self,sender,e):
 144:     self.clicked(self);
 145:   def mouseOver(self,sender,e):
 146:     self.text.FontWeight = FontWeights.Bold;
 147:   def mouseOut(self,sender,e):
 148:     self.text.FontWeight = FontWeights.Normal;
 149:   def SetPosition(self, x, y):
 150:     self.text.SetValue(Canvas.LeftProperty, x);
 151:     self.text.SetValue(Canvas.TopProperty, y);    
 152:   def SetPosition2(self, angle):
 153:     if (self.radius < 20):self.text.Visibility = Visibility.Collapsed;
 154:     else:self.text.Visibility=Visibility.Visible;
 155:     self.SetPosition( (math.sin(self.angle + angle) * self.radius) + self.offset, (math.cos(self.angle + angle) * self.radius) + self.offset);  
 156:   
 157:  
 158: App()

I noticed when I uploaded the XAP that it was quite large (> 1Mb) for a very small demo. I think this is because additional assemblies for dynamic languages and IronPython are not included in the client Silverlight plug-in and have to be included in the XAP.


Tags: , ,
Categories:
Comments (2)

Comments

Comments are closed