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.
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:
using System.Collections.Generic;
using System.Linq;
using System.Drawing;
namespace Logitech.GamePanel
{
public class LcdLabel : LcdControl
{
public LcdLabel()
{
StringFormat = new StringFormat()
Size = new Size(LcdScreen.Width, DefaultFont.Height);
}
private string _text;
public string Text
{
get
{
return _text;
}
set
{
if (value == null)
value = "";
_text = value;
Update();
}
}
public StringFormat StringFormat { get; set; }
protected override void OnPaintBackground(PaintEventArgs e)
{
using (var back = new SolidBrush(BackgroundColor))
e.Graphics.FillRectangle(back, ClientBounds);
}
protected override void OnPaint(PaintEventArgs e)
{
//Paints background
base.OnPaint(e);
using (var fore = new SolidBrush(ForegroundColor))
e.Graphics.DrawString(_text, Font, fore,
ClientBounds, StringFormat);
}
}
}
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. using System.Drawing;
using System.Threading;
using Logitech.GamePanel;
namespace LcdSandbox
{
class CounterPage : LcdPage
{
const string counterFormat = "Count: {0:N0}";
LcdLabel lTitle;
LcdLabel lCounter;
public int Count { get; set; }
public CounterPage(LcdScreen scr)
: base(scr)
{
#region Page initialization
//Although the page itself does not draw any text,
// the font is inherited by child controls
Font = new Font("Calibri", 12, GraphicsUnit.Pixel);
//lTitle - A label that display a title
lTitle = new LcdLabel()
{
Location = new Point(0, 2),
Text = "I'll count for you!"
};
Controls.Add(lTitle);
//lCounter - A label that displays the current count
lCounter = new LcdLabel()
{
Location = new Point(0, 20),
StringFormat = new StringFormat()
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center
}
};
Controls.Add(lCounter);
//Define soft button descriptions
LcdControl BDesc;
//Button 0 - Increment
BDesc = new LcdImage(Icons.UpArrow);
Button0.Controls.Add(BDesc);
Button0.Pressed += (obj, e) =>
{
Count++;
updateCounter();
};
//Button 1 - Decrement
BDesc = new LcdImage(Icons.DownArrow);
Button1.Controls.Add(BDesc);
Button1.Pressed += (obj, e) =>
{
Count--;
updateCounter();
};
//Button 2 - Reset
BDesc = new LcdImage(Icons.First);
Button2.Controls.Add(BDesc);
Button2.Pressed += (obj, e) =>
{
Count = 0;
updateCounter();
};
//Button 3 - Exit
var exitSignal = new AutoResetEvent(false);
BDesc = new LcdImage(Icons.Delete);
Button3.Controls.Add(BDesc);
Button3.Pressed += (obj, e) =>
{
Screen.ExitMessageLoop();
};
#endregion
Priority = ScreenPriority.Normal;
updateCounter();
}
private void updateCounter()
{
lCounter.Text = String.Format(counterFormat, Count);
Update();
}
}
class Program
{
static void Main(string[] args)
{
using (var screen = new LcdScreen())
{
//You must not forget to open the screen
// before asigning any pages.
screen.Open("My LCD Sandbox");
//Create a new instance of our counter page
var page = new CounterPage(screen);
//Tell the screen to display our counter page
screen.CurrentPage = page;
//Temporarily set the priority to Alert
page.TemporaryAlert(500);
Console.WriteLine("Application running.");
Console.WriteLine("See LCD for instructions");
//The Run method is similar to Application.Run
// from System.Windows.Forms. Until screen.Close
// or screen.ExitMessageLoop is called, this
// thread will handle events.
screen.Run();
}
}
}
}
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.
This sounds great! Can’t wait to work with it, I’ve been meaning to do something similar I just haven’t found the time.
Thanks, that’s really motivating: