SealedSun.GamePanel June08

[ Download SealedSun.Gamepanel June08 ]

SealedSun.GamePanel is a high level wrapper around the managed LcdInterface by http://www.cabhair-cainte.com/ismiselemeas/ that features a Winforms-like controls system as well as simplified event routing for the four soft buttons of the G15.

The library is not very mature and only the most commonly used features are implemented. Everyone with basic knowledge of System.Drawing should be able to implement custom functionality via user controls.

Quick Start

1. Create a new LcdScreen

The LcdScreen represents an entry in GamePanel manager and can be selected by the user via the application switch button on the Keyboard.

C#:
  1. using(var screen = new LcdScreen())
  2. // ...
  3. }

2. Open the LcdScreen

To notify the GamePanel manager of the new screen, call the Open method.

C#:
  1. screen.Open("My LCD");

3. Create a new LcdPage

An LCD page is like an empty canvas. It is the container for all your UI elements.

C#:
  1. var page = new LcdPage(screen);

4. Add controls to the LcdPage

There are currently only two built-in general purpose controls: LcdLabel and LcdImage. In order for your controls to show up, you need to add them to the page object. You can also nest controls by adding them to other controls. Always keep in mind, that the positioning is relative to the parent control and that controls cannot exceed the bounds of their container.

C#:
  1. var lTitle = new LcdLabel();
  2. lTitle.Text = "My LCD";
  3. lTitle.Location = new Point(10,10);
  4. page.Controls.Add(lTitle);

5. Configure soft buttons

The page object provides access to the four soft buttons via its Button0,1,2,3 members. Those special controls provide 'button pressed' events and are always located immediately above the respective buttons. Their Text and Image properties can be used to label them. If you need better control over the labelling, you can add child controls to the soft buttons like to any other control.

C#:
  1. page.Button0.Pressed += (sender,e) => {
  2. Console.WriteLine("0 Pressed!");
  3. };

6. Select active page

In order for a page to be displayed on a screen, it must be assigned to its CurrentPage property. Only pages that are currently active will fire events.

C#:
  1. screen.CurrentPage = page;

7. Enter message loop

Like a windows forms application, an LCD application too has a message loop. Similar to Application.Run, screen.Run will use the current thread to process messages from the GamePanel manager.

From here on...

If you want to get the users attention, you can change the pages screen priority to Alert for a short amount of time. A convenient way to do so is the TemporaryAlert method. This of course only works, if the user allows high priority screens.

C#:
  1. page.TemporaryAlert(500);

If you want to quit the message loop, either close the screen or call Screen.ExitMessageLoop.

License and Disclaimer

Except for the components mentioned in the ExternalResources.txt, I hereby release everything the zip archive under the terms of the Creative Commons Attribution-Share Alike 2.5 Switzerland license.

The software was written over the course of a few days and did therefor not undergo much testing. It is most likely that you will encounter bugs and unexpected behaviour. You use the library at your own risk.

Creative Commons License
SealedSun.GamePanel by Christian Klauser is licensed under a Creative Commons Attribution-Share Alike 2.5 Switzerland License.

Upcoming: Event-based G15 LCD library

I'm sure all of you have heard of Logitech's G15 gaming keyboard, yes the one with the built-in LCD. I've recently bought the first version (see picture) and obviously want to write applications that target the small 160x43 pixel screen. Logitech provides a native (C++) SDK with their drivers and someone was kind enough to descend into the creepy world of pointers and manual memory management for all .NET developers out there.

This managed wrapper, however, is an extremely low-level interface: In order to draw something onto the screen, you have to pass an array of bytes (more precisely: a pointer to its first element) that holds the bitmap to display. This is obviously not very practical and completely alienating for people who, up until now, lived in the comfortable world of Windows.Forms.

Logitech G15 (first version)

Not being able to find a more abstract library for the G15 LCD, I decided to write my own GUI library. The first task was to make rendering via System.Drawing possible. After having to find out that the only monochrome pixel format is indexed and can therefor not be targeted by a Graphics object, I resorted to using a 24BppRGB  bitmap for rendering instead. I then manually iterate over the pixels in that buffer, determining the brightness of the pixel and applying a threshold in order to get a monochrome copy into a byte array.

On top of that I am currently building a Widget/Control oriented display layer complete with event routing for the four soft buttons, Z-ordering and clipping for nested controls. Below you see the trivial implementation of the LcdLabel control:

C#:
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Drawing;
  5.  
  6. namespace Logitech.GamePanel
  7. {
  8.   public class LcdLabel : LcdControl
  9.   {
  10.  
  11.     public LcdLabel()
  12.     {
  13.       StringFormat = new StringFormat()
  14.       Size = new Size(LcdScreen.Width, DefaultFont.Height);
  15.     }
  16.  
  17.     private string _text;
  18.  
  19.     public string Text
  20.     {
  21.       get
  22.       {
  23.         return _text;
  24.       }
  25.       set
  26.       {
  27.         if (value == null)
  28.           value = "";
  29.         _text = value;
  30.         Update();
  31.       }
  32.     }
  33.  
  34.     public StringFormat StringFormat { get; set; }
  35.  
  36.     protected override void OnPaintBackground(PaintEventArgs e)
  37.     {
  38.       using (var back = new SolidBrush(BackgroundColor))
  39.         e.Graphics.FillRectangle(back, ClientBounds);
  40.     }
  41.  
  42.     protected override void OnPaint(PaintEventArgs e)
  43.     {
  44.       //Paints background
  45.       base.OnPaint(e);
  46.  
  47.       using (var fore = new SolidBrush(ForegroundColor))
  48.         e.Graphics.DrawString(_text, Font, fore,
  49.             ClientBounds, StringFormat);
  50.     }
  51.   }
  52. }

G15 Counter in the LCD emulator

One thing I am proud of, is my implementation of delayed and repeated button press events if a button is being hold down. The buttons use the same repeat delay as the Keyboard (configurable via the device driver/windows).
Repeated Pressed events are particularly useful for the sample application below, a little application that provides a counter that can be incremented, decremented and reset via soft buttons.

C#:
  1. using System;
  2. using System.Drawing;
  3. using System.Threading;
  4. using Logitech.GamePanel;
  5.  
  6. namespace LcdSandbox
  7. {
  8.   class CounterPage : LcdPage
  9.   {
  10.     const string counterFormat = "Count: {0:N0}";
  11.  
  12.     LcdLabel lTitle;
  13.     LcdLabel lCounter;
  14.  
  15.     public int Count { get; set; }
  16.  
  17.     public CounterPage(LcdScreen scr)
  18.       : base(scr)
  19.     {
  20.       #region Page initialization
  21.  
  22.       //Although the page itself does not draw any text,
  23.       // the font is inherited by child controls
  24.       Font = new Font("Calibri", 12, GraphicsUnit.Pixel);
  25.  
  26.       //lTitle - A label that display a title
  27.       lTitle = new LcdLabel()
  28.       {
  29.         Location = new Point(0, 2),
  30.         Text = "I'll count for you!"
  31.       };
  32.       Controls.Add(lTitle);
  33.  
  34.       //lCounter - A label that displays the current count
  35.       lCounter = new LcdLabel()
  36.       {
  37.         Location = new Point(0, 20),
  38.         StringFormat = new StringFormat()
  39.         {
  40.           Alignment = StringAlignment.Center,
  41.           LineAlignment = StringAlignment.Center
  42.         }
  43.       };
  44.       Controls.Add(lCounter);
  45.  
  46.       //Define soft button descriptions
  47.       LcdControl BDesc;
  48.  
  49.       //Button 0 - Increment
  50.       BDesc = new LcdImage(Icons.UpArrow);
  51.       Button0.Controls.Add(BDesc);
  52.       Button0.Pressed += (obj, e) =>
  53.       {
  54.         Count++;
  55.         updateCounter();
  56.       };
  57.  
  58.       //Button 1 - Decrement
  59.       BDesc = new LcdImage(Icons.DownArrow);
  60.       Button1.Controls.Add(BDesc);
  61.       Button1.Pressed += (obj, e) =>
  62.       {
  63.         Count--;
  64.         updateCounter();
  65.       };
  66.  
  67.       //Button 2 - Reset
  68.       BDesc = new LcdImage(Icons.First);
  69.       Button2.Controls.Add(BDesc);
  70.       Button2.Pressed += (obj, e) =>
  71.       {
  72.         Count = 0;
  73.         updateCounter();
  74.       };
  75.  
  76.       //Button 3 - Exit
  77.       var exitSignal = new AutoResetEvent(false);
  78.       BDesc = new LcdImage(Icons.Delete);
  79.       Button3.Controls.Add(BDesc);
  80.       Button3.Pressed += (obj, e) =>
  81.       {
  82.         Screen.ExitMessageLoop();
  83.       };
  84.  
  85.       #endregion
  86.  
  87.       Priority = ScreenPriority.Normal;
  88.       updateCounter();
  89.     }   
  90.  
  91.     private void updateCounter()
  92.     {
  93.       lCounter.Text = String.Format(counterFormat, Count);
  94.       Update();
  95.     }
  96.  
  97.   }
  98.  
  99.   class Program
  100.   {
  101.     static void Main(string[] args)
  102.     {
  103.       using (var screen = new LcdScreen())
  104.       {
  105.         //You must not forget to open the screen
  106.         // before asigning any pages.
  107.         screen.Open("My LCD Sandbox");
  108.  
  109.         //Create a new instance of our counter page       
  110.         var page = new CounterPage(screen);
  111.  
  112.         //Tell the screen to display our counter page
  113.         screen.CurrentPage = page;
  114.  
  115.         //Temporarily set the priority to Alert
  116.         page.TemporaryAlert(500);       
  117.  
  118.         Console.WriteLine("Application running.");
  119.         Console.WriteLine("See LCD for instructions");
  120.  
  121.         //The Run method is similar to Application.Run
  122.         // from System.Windows.Forms. Until screen.Close
  123.         // or screen.ExitMessageLoop is called, this
  124.         // thread will handle events.
  125.         screen.Run();
  126.       }
  127.     }
  128.   }
  129. }

The library is in what others call 'Alpha' stage. I do plan to release it here once I'm done documenting the most commonly used methods or if someone is particularly interested and wants to start coding right now.

Using LINQ for the Euler Project

Project Euler is a series of challenging mathematical/computer programming problems that will require more than just mathematical insights to solve. Although mathematics will help you arrive at elegant and efficient methods, the use of a computer and programming skills will be required to solve most problems.
The motivation for starting Project Euler, and its continuation, is to provide a platform for the inquiring mind to delve into unfamiliar areas and learn new concepts in a fun and recreational context.

If you need to kill time and like mathematics and programming, Project Euler is a great source of nearly 200 tricky problems to solve. Having found a solution is especially rewarding as there is a strict policy of not providing any help apart from the problem description. If you can't solve a problem, then you can't solve it.

As of right now I have solved 26 out of 196 Problems, which according to the website makes me a 13% genius. My tool of choice is of course C# 3.0 and I must say that LINQ to Objects has allowed me to write solutions that resemble those shiny Haskell and Mathematica code snippets you find in the answer forums.

Even though I am probably not supposed to publicly post problem solutions, there is this one particular program that beautifully outlines how LINQ can be employed to compute the result:

Consider all integer combinations of ab for 2 ≤ a ≤ 5 and 2 ≤ b ≤ 5:

22=4, 23=8, 24=16, 25=32

32=9, 33=27, 34=81, 35=243

42=16, 43=64, 44=256, 45=1024

52=25, 53=125, 54=625, 55=3125

If they are then placed in numerical order, with any repeats removed, we get the following sequence of 15 distinct terms:

4, 8, 9, 16, 25, 27, 32, 64, 81, 125, 243, 256, 625, 1024, 3125

How many distinct terms are in the sequence generated by ab for 2 ≤ a ≤ 100 and 2 ≤ b ≤ 100?

C#:
  1. public static IEnumerable Digits(this int n)
  2. {
  3.     while (n> 0)
  4.     {
  5.         yield return n%10;
  6.         n /= 10;
  7.     }
  8. }
  9. var s = (from i in 2.To(999999)
  10.     where i == i.Digits().Select(
  11.         d => (int) Math.Pow(d, 5)).Sum()
  12.     select i).Sum();

The only part where this solution could be improved is the choice of the range to search in as one could reason about the maximum number of digits (Hint: 6*95).

In the problem forums there is often a competition around writing the shortest program possible and obviously "minimalistic" (in terms of syntax) languages like J and Ruby have an advantage there. But with today's IDE support actually writing the LINQ-expression above didn't take any longer.