This article describes how to create cross platform desktop widgets using overlays. Here is a oscilloscope overlay widget running on Linux.
And the same oscilloscope overlay widget running on Windows:
Surface overlays are type of window whose pixels are defined by drawing operations to an ISurface interface. Each individual window pixel can have a range of transparency between 0 which is totally invisible, to 255 which is completely opaque, or any value in between the two. Access to cross platform surface overlays is provided by the ISplash interface, which is defined as follows:
{ ISplash is a floating window whose shape is defined by a bitmap }
ISplash = interface
['{291570E9-3567-4C10-8899-CDA04979060F}']
function GetBitmap: IBitmap;
function GetOpacity: Byte;
procedure SetOpacity(Value: Byte);
function GetVisible: Boolean;
procedure SetVisible(Value: Boolean);
function GetHandle: THandle;
{ Move the window to x and y }
procedure Move(X, Y: Integer);
{ Update the window when you're done drawing on bitmap }
procedure Update;
{ Bitmap is the image surface which defines the window shape }
property Bitmap: IBitmap read GetBitmap;
{ Opacity controls the overall transparency of the window }
property Opacity: Byte read GetOpacity write SetOpacity;
{ Visible shows or hide the window }
property Visible: Boolean read GetVisible write SetVisible;
{ Handle is the udnerlying operating system window handle }
property Handle: THandle read GetHandle;
end;
Overlays can be used to create widget type windows, unusual notification popups, custom tooltip windows, or even application splash or about screens.
Here is a listing of the oscilloscope example above.
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
{ We use a prendered png image to define the frame }
FFrame := NewBitmap;
FFrame.LoadFromFile('frame.png');
{ And also we prerender some shadow images }
FInner := NewBitmap;
FInner.LoadFromFile('inner.png');
FOuter := NewBitmap;
FOuter.LoadFromFile('outer.png');
{ This is our surface overlay }
FSplash := NewSplash;
{ Make the overlay match the size of the frame }
FSplash.Bitmap.SetSize(FFrame.Width, FFrame.Height);
{ Move the overlay to the top right of the screen }
FSplash.Move(Screen.Width - FSplash.Bitmap.Width, 0);
end;
procedure TForm1.DrawScope;
const
ScaleX = 30;
ScaleY = 90;
ScaleT = 10;
var
Surface: ISurface;
R: TRectI;
G: IGradientBrush;
P: IPen;
C: TColorB;
T: Double;
X, Y: Float;
A, B: TPointF;
I: Integer;
begin
{ Erase the overlay }
FSplash.Bitmap.Clear;
Surface := FSplash.Bitmap.Surface;
R := FSplash.Bitmap.ClientRect;
R.Inflate(-15, -15);
G := NewBrush(R.Left, R.Top, R.Left, R.Bottom);
{ Draw a gradient background using the color teal }
C := clTeal;
{ With a little bit of transparency }
C.Alpha := $E0;
{ Start out dark at the top }
G.AddStop(C.Darken(0.5), 0);
G.AddStop(C, 0.5);
{ Finish with light at the bottom }
G.AddStop(C.Lighten(0.5), 1);
Surface.FillRect(G, R);
C := clTeal;
R := FSplash.Bitmap.ClientRect;
{ Draw both shadows }
FOuter.Surface.CopyTo(R, Surface, R);
FInner.Surface.CopyTo(R, Surface, R, $7F);
R.Inflate(400, 0);
R.Bottom := R.Bottom * 3;
{ Draw white relection along the top }
G := NewBrush(R);
C := clWhite;
G.AddStop(C.Fade(0), 0);
G.AddStop(C.Fade(0), 0.8);
G.AddStop(C.Fade(0.2), 0.81);
G.AddStop(C.Fade(0), 0.9);
R := FSplash.Bitmap.ClientRect;
R.Inflate(-20, -20);
Surface.FillRect(G, R);
{ Draw the oscilloscope sin wave pattern }
X := R.Left;
Y := FSplash.Bitmap.Height div 2;
{ Animate using time }
T := TimeQuery * ScaleT;
Surface.MoveTo(X, Y + Sin(X / ScaleX + T) * ScaleY);
for I := 0 to R.Width div 2 do
begin
X := X + 2;
Surface.LineTo(X, Y + Sin(X / ScaleX + T) * ScaleY);
end;
C := clTeal;
C := C.Lighten(0.75);
P := NewPen(C.Fade(0.05), 14);
{ Stroke the path with a shrinking pen }
for I := 0 to 3 do
begin
Surface.Stroke(P, True);
P.Color := C.Fade(I / 4 + 0.1);
P.Width := P.Width - 4;
end;
Surface.Path.Remove;
{ Connect the sin wave end points }
A.X := 45;
A.Y := Y + Sin(A.X / ScaleX + T) * ScaleY;
B.X := FSplash.Bitmap.Width - 45;
B.Y := Y + Sin(B.X / ScaleX + T) * ScaleY;
Surface.MoveTo(A.X, A.Y);
Surface.LineTo(B.X, B.Y);
P.Width := 6;
P.Color := C.Fade(0.02);
for I := 0 to 1 do
begin
Surface.Stroke(P, True);
P.Color := C.Fade(I / 4 + 0.2);
P.Width := P.Width - 2;
end;
{ Draw the frame around our graph and shadows }
R := FSplash.Bitmap.ClientRect;
FFrame.Surface.CopyTo(R, Surface, R);
R := TRectI.Create(30, 30);
R.Center(Round(A.X), Round(A.Y));
G := NewBrush(R);
G.AddStop(C.Fade(0.4), 0);
G.AddStop(C.Fade(0), 1);
{ Draw a sparkles of the left }
Surface.FillRect(G, R);
R.Center(Round(B.X), Round(B.Y));
G := NewBrush(R);
G.AddStop(C.Fade(0.4), 0);
G.AddStop(C.Fade(0), 1);
{ And also on the right }
Surface.FillRect(G, R);
{ Tell the overlay we are done drawing }
FSplash.Update;
{ And show it if it's not already visible }
FSplash.Visible := True;
end;
procedure TForm1.TimerTimer(Sender: TObject);
begin
{ Animate }
DrawScope;
end;