Contributor: UDO JUERSS

{
  This component uses the VGA standard 8x16 font. No resources are used.

  properties description:

    property BackGround:     Background color of panel. Not visible if size is 1,
                             because pixeldensity is too high.

    property BevelOuter:     as usual.
    property BevelInner:     as usual.
    property BevelWidth:     as usual.
    property Characters:     How many Character are displayed in panel.
                             Increasing this slows down the outputspeed.
    property OffColor:       Color of Pixels not set in character.
    property OnColor:        Color of Pixels set in character.
    property OnComplete:     Fired if output of RunText completed.
    property Running:        Flag if horizontal scrolling is active.
    property RunText:        Outputstring.
    property ScrollBy:       Number of pixels per horizontal scroll.
    property ScrollInterval: Cycletime of horizontal scrolling.
    property Size:           Size of output. If set to 1 character size is 8x16
                             pixels. Increasing size decreases display contrast.

  Contact: Udo Juerss, 57078 Siegen, Germany, CompuServe [101364,526]

  Previously published by me: Luffing switch      (March  8. 1996)
                              Scaleable LED light (March 10. 1996)

  If someone makes useful enhances or corrections to these components,
  please send me an update!

  March 11. 1996
}

unit
  Marquee;
{------------------------------------------------------------------------------}

interface

uses
  WinTypes, WinProcs, Messages, Classes, Graphics, Controls, ExtCtrls;
{------------------------------------------------------------------------------}

const
  Dual: array[0..7] of Byte = (1,2,4,8,16,32,64,128);
{------------------------------------------------------------------------------}

type
  TMarquee = class(TGraphicControl)
  private
    Timer: TTimer;

    FBackGround: TColor;
    FBevelOuter: TPanelBevel;
    FBevelInner: TPanelBevel;
    FBevelWidth: Byte;
    FBkGnd: TColor;
    FCharacters: Byte;
    FScrollInterval: Word;
    FOffColor: TColor;
    FOnColor: TColor;
    FOnComplete: TNotifyEvent;
    FRunning: Boolean;
    FRunText: string;
    FSize: Byte;
    FScrollBy: Byte;

    Border:Byte;
    Index: Byte;
    WorkString: string;
    PixelPos: Byte;
    CharOfs: Word;
    TextLen: Byte;
    XPos: Integer;
    YPos: Integer;
    procedure Draw;
    procedure DrawText(Shift:Boolean);
    procedure GetCharData(Character: Char);
    procedure PutVerticalPixels(Horizontal: Byte);
    procedure Setup;
    procedure ShiftString;
    procedure TimerShift(Sender: TObject);
  protected
    procedure DrawBevel(Rect: TRect);
    procedure SetBackGround(Value: TColor);
    procedure SetBevelOuter(Value: TPanelBevel);
    procedure SetBevelInner(Value: TPanelBevel);
    procedure SetBevelWidth(Value: Byte);
    procedure SetCharacters(Value: Byte);
    procedure SetScrollInterval(Value: Word);
    procedure SetOffColor(Value: TColor);
    procedure SetOnColor(Value: TColor);
    procedure SetRunning(Value: Boolean);
    procedure SetRunText(Value: string);
    procedure SetSize(Value: Byte);
    procedure SetScrollBy(Value: Byte);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Clear;
    procedure Paint; override;
  published
    property BackGround: TColor read FBackGround write SetBackGround default clBlack;
    property BevelOuter: TPanelBevel read FBevelOuter write SetBevelOuter default bvRaised;
    property BevelInner: TPanelBevel read FBevelInner write SetBevelInner default bvLowered;
    property BevelWidth: Byte read FBevelWidth write SetBevelWidth default 2;
    property Characters: Byte read FCharacters write SetCharacters default 7;
    property ScrollInterval: Word read FScrollInterval write SetScrollInterval default 50;
    property OffColor: TColor read FOffColor write SetOffColor default clGray;
    property OnColor: TColor read FOnColor write SetOnColor default clLime;
    property OnComplete: TNotifyEvent read FOnComplete write FOnComplete;
    property Running: Boolean read FRunning write SetRunning default False;
    property RunText: string read FRunText write SetRunText;
    property ScrollBy: Byte read FScrollBy write SetScrollBy default 1;
    property Size: Byte read FSize write SetSize default 2;
  end;
{------------------------------------------------------------------------------}

procedure GetFontOfs(CharSet: Byte; var FntOfs: Word);
function SegC000: Word;
procedure Register;

implementation
{------------------------------------------------------------------------------}

var
  CharArray: array[0..15] of Byte;
  FontPtr: Pointer;
  FontOfs: Word;
{------------------------------------------------------------------------------}

procedure GetFontOfs(CharSet: Byte; var FntOfs: Word); assembler;
asm
           push  bp
           mov   ax,1130h
           mov   bh,CharSet
           int   10h
           mov   ax,bp
           pop   bp
           les   di,FntOfs
           stosw
end;
{------------------------------------------------------------------------------}

function SegC000: Word; external 'KERNEL' Index 195;
{------------------------------------------------------------------------------}

constructor TMarquee.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Parent:=AOwner as TWinControl;
  Canvas.Brush.Style:=bsSolid;
  Timer:=nil;
  FBackGround:=clBlack;
  FBevelOuter:=bvRaised;
  FBevelInner:=bvLowered;
  FBevelWidth:=2;
  FCharacters:=7;
  FScrollInterval:=50;
  FOffColor:=clGray;
  FOnColor:=clLime;
  FOnComplete:=nil;
  FRunning:=False;
  FRunText:='RunText ';
  FSize:=2;
  FScrollBy:=1;

  Border:=2;
  GetFontOfs(6,FontOfs);
  FontPtr:=Ptr(Ofs(SegC000),FontOfs);

  PixelPos:=0;
  TextLen:=Length(FRunText);
  Index:=0;
  WorkString:=FRunText;
  Setup;
  Draw;
end;
{------------------------------------------------------------------------------}

destructor TMarquee.Destroy;
begin
  if FRunning then SetRunning(False);
  inherited Destroy;
end;
{------------------------------------------------------------------------------}

procedure TMarquee.Paint;
begin
  Draw;
end;
{------------------------------------------------------------------------------}

procedure TMarquee.Clear;
var
  Temp: Byte;
begin
  Temp:=FOnColor;
  FOnColor:=FOffColor;
  DrawText(False);
  FOnColor:=Temp;
end;
{------------------------------------------------------------------------------}

procedure TMarquee.Draw;
var
  R: TRect;
begin
  R:=GetClientRect;
  DrawBevel(R);
  Canvas.Pen.Color:=FBackGround;
  Canvas.Brush.Color:=FBackGround;
  InflateRect(R,-Border,-Border);
  Canvas.FillRect(R);
  DrawText(False);
end;
{------------------------------------------------------------------------------}

procedure TMarquee.DrawBevel(Rect: TRect);
var
  TopColor: TColor;
  BottomColor: TColor;

  procedure SetColors(Bevel: TPanelBevel);
  begin
    TopColor:=clBtnHighlight;
    if Bevel = bvLowered then TopColor:=clBtnShadow;
    BottomColor:=clBtnShadow;
    if Bevel = bvLowered then BottomColor:=clBtnHighlight;
  end;

begin
  if FBevelOuter <> bvNone then
  begin
    SetColors(BevelOuter);
    Frame3D(Canvas,Rect,TopColor,BottomColor,BevelWidth);
  end;

  if FBevelInner <> bvNone then
  begin
    SetColors(FBevelInner);
    Frame3D(Canvas,Rect,TopColor,BottomColor,FBevelWidth);
  end;
end;
{------------------------------------------------------------------------------}

procedure TMarquee.DrawText(Shift: Boolean);
var
  Pos: Byte;
  I: Byte;
  R: TRect;
begin
  R:=GetClientRect;
  XPos:=R.Left + Border;
  YPos:=R.Top + Border;
  GetCharData(WorkString[1]);
  for I:=PixelPos to 7 do PutVerticalPixels(I);

  for Pos:=2 to FCharacters do
  begin
    GetCharData(WorkString[Pos]);
    for I:=0 to 7 do PutVerticalPixels(I);
  end;

  GetCharData(WorkString[Succ(FCharacters)]);
  for I:=0 to PixelPos do PutVerticalPixels(I);

  if Shift then Inc(PixelPos,FScrollBy);
  if PixelPos > 7 then
  begin
    PixelPos:=0;
    ShiftString;
  end;
end;
{------------------------------------------------------------------------------}

procedure TMarquee.GetCharData(Character: Char); assembler;
asm
           push  ds
           push  ds
           pop   es
           mov   di,offset CharArray
           xor   bh,bh
           mov   bl,Character
           shl   bx,4
           lds   si,FontPtr
           add   si,bx
           mov   cx,16

@MovsLoop: push  cx
           lodsb
           mov   ah,0
           mov   cx,8

@RolLoop:  rol   al,1
           adc   ah,0
           ror   ah,1
           loop  @RolLoop

           mov   al,ah
           stosb
           pop   cx
           loop  @MovsLoop

           pop   ds
end;
{------------------------------------------------------------------------------}

procedure TMarquee.PutVerticalPixels(Horizontal: Byte);
var
  Vertical: Byte;
begin
  for Vertical:=0 to 15 do
  begin
    if CharArray[Vertical] and Dual[Horizontal] > 0 then
      Canvas.Pixels[XPos,YPos + Vertical * FSize]:=FOnColor
      else Canvas.Pixels[XPos,YPos + Vertical * FSize]:=FOffColor;
  end;
  Inc(XPos,FSize);
end;
{------------------------------------------------------------------------------}

procedure TMarquee.TimerShift(Sender: TObject);
begin
  DrawText(True);
end;
{------------------------------------------------------------------------------}

procedure TMarquee.ShiftString;
begin
  Inc(Index);
  if FCharacters >= TextLen - Index then
  begin
    WorkString:=Copy(FRunText,Succ(Index),TextLen - Index);
    WorkString:=WorkString + Copy(RunText,1,Succ(FCharacters) - (TextLen - Index));
  end
  else WorkString:=Copy(FRunText,Succ(Index),Succ(FCharacters));
  if Index >= TextLen then
  begin
    Index:=0;
    if Assigned(FOnComplete) then FOnComplete(Self);
  end;
end;
{------------------------------------------------------------------------------}

procedure TMarquee.Setup;
begin
  Width:=FSize * 8 * FCharacters + 2 * Border + 1;
  Height:=FSize * 16 + 2 * Border;
end;
{------------------------------------------------------------------------------}

procedure TMarquee.SetBackGround(Value: TColor);
begin
  if FBackGround <> Value then
  begin
    FBackGround:=Value;
    Draw;
  end;
end;
{------------------------------------------------------------------------------}

procedure TMarquee.SetBevelOuter(Value: TPanelBevel);
begin
  if FBevelOuter <> Value then
  begin
    FBevelOuter:=Value;
    if FBevelOuter <> bvNone then Border:=FBevelWidth else Border:=0;
    if FBevelInner <> bvNone then Inc(Border,FBevelWidth);
    Setup;
    Draw;
  end;
end;
{------------------------------------------------------------------------------}

procedure TMarquee.SetBevelInner(Value: TPanelBevel);
begin
  if FBevelInner <> Value then
  begin
    FBevelInner:=Value;
    if FBevelOuter <> bvNone then Border:=FBevelWidth else Border:=0;
    if FBevelInner <> bvNone then Inc(Border,FBevelWidth);
    Setup;
    Draw;
  end;
end;
{------------------------------------------------------------------------------}

procedure TMarquee.SetBevelWidth(Value: Byte);
begin
  if FBevelWidth <> Value then
  begin
    FBevelWidth:=Value;
    if FBevelOuter <> bvNone then Border:=FBevelWidth else Border:=0;
    if FBevelInner <> bvNone then Inc(Border,FBevelWidth);
    Setup;
    Draw;
  end;
end;
{------------------------------------------------------------------------------}

procedure TMarquee.SetCharacters(Value: Byte);
var
  I: Byte;
begin
  if Value < 1 then Value:=1 else if Value > 80 then Value:=80;
  if FCharacters <> Value then
  begin
    FCharacters:=Value;
    if TextLen < FCharacters then
    begin
      for I:=TextLen to FCharacters do FRunText:=FRunText + ' ';
      TextLen:=Byte(FRunText[0]);
    end;
    SetUp;
    Draw;
  end;
end;
{------------------------------------------------------------------------------}

procedure TMarquee.SetScrollInterval(Value: Word);
begin
  if FScrollInterval <> Value then
  begin
    FScrollInterval:=Value;
    if FRunning and Assigned(Timer) then Timer.Interval:=FScrollInterval;
  end;
end;
{------------------------------------------------------------------------------}

procedure TMarquee.SetSize(Value: Byte);
begin
  if Value < 1 then Value:=1 else if Value > 8 then Value:=8;
  if FSize <> Value then
  begin
    FSize:=Value;
    SetUp;
    Draw;
  end;
end;
{------------------------------------------------------------------------------}

procedure TMarquee.SetScrollBy(Value: Byte);
begin
  if Value < 1 then Value:=1 else if Value > 8 then Value:=8;
  if FScrollBy <> Value then FScrollBy:=Value;
end;
{------------------------------------------------------------------------------}

procedure TMarquee.SetOffColor(Value: TColor);
begin
  if FOffColor <> Value then
  begin
    FOffColor:=Value;
    Draw;
  end;
end;
{------------------------------------------------------------------------------}

procedure TMarquee.SetOnColor(Value: TColor);
begin
  if FOnColor <> Value then
  begin
    FOnColor:=Value;
    Draw;
  end;
end;
{------------------------------------------------------------------------------}

procedure TMarquee.SetRunning(Value: Boolean);
begin
  if FRunning <> Value then
  begin
    FRunning:=Value;
    if FRunning then
    begin
      Timer:=TTimer.Create(Self);
      Timer.Interval:=FScrollInterval;
      Timer.OnTimer:=TimerShift;
      Timer.Enabled:=True;
    end
    else if Assigned(Timer) then
    begin
      Timer.Free;
      Timer:=nil;
    end;
  end;
end;
{------------------------------------------------------------------------------}

procedure TMarquee.SetRunText(Value: string);
var
  I: Byte;
begin
  Index:=0;
  FRunText:=Value;
  TextLen:=Byte(FRunText[0]);
  if TextLen < FCharacters then for I:=TextLen to FCharacters do FRunText:=FRunText + ' ';
  TextLen:=Byte(FRunText[0]);
end;
{------------------------------------------------------------------------------}

procedure Register;
begin
  RegisterComponents('Udo|s',[TMarquee]);
end;
{------------------------------------------------------------------------------}

initialization
end.