123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523 |
- unit MainUnit;
- (* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1 or LGPL 2.1 with linking exception
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * Alternatively, the contents of this file may be used under the terms of the
- * Free Pascal modified version of the GNU Lesser General Public License
- * Version 2.1 (the "FPC modified LGPL License"), in which case the provisions
- * of this license are applicable instead of those above.
- * Please see the file LICENSE.txt for additional information concerning this
- * license.
- *
- * The Original Code is ArrowHead Example for Graphics32
- *
- * The Initial Developer of the Original Code is
- * Angus Johnson < http://www.angusj.com >
- *
- * Portions created by the Initial Developer are Copyright (C) 2012
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *
- * ***** END LICENSE BLOCK ***** *)
- interface
- {$include GR32.inc}
- uses
- {$IFDEF FPC} LCLIntf, LResources, {$ENDIF} SysUtils, Classes, Graphics,
- Controls, Forms, StdCtrls, ExtCtrls, ComCtrls, GR32, GR32_Image, GR32_Layers,
- GR32_Paths, GR32_Polygons, GR32_ArrowHeads;
- type
- TFmArrowHead = class(TForm)
- Animation: TTimer;
- BtnClose: TButton;
- CbxAnimate: TCheckBox;
- EdtArrowSize: TEdit;
- ImgView32: TImgView32;
- LblArrowSize: TLabel;
- LblLineWidth: TLabel;
- PnlControl: TPanel;
- RgpArrowStyle: TRadioGroup;
- RgpPosition: TRadioGroup;
- TbrAnimationSpeed: TTrackBar;
- TbrLineWidth: TTrackBar;
- procedure FormCreate(Sender: TObject);
- procedure FormDestroy(Sender: TObject);
- procedure AnimationTimer(Sender: TObject);
- procedure BtnCloseClick(Sender: TObject);
- procedure CbxAnimateClick(Sender: TObject);
- procedure EdtArrowSizeChange(Sender: TObject);
- procedure ImgView32MouseDown(Sender: TObject; Button: TMouseButton;
- Shift: TShiftState; X, Y: Integer; Layer: TCustomLayer);
- procedure ImgView32MouseUp(Sender: TObject; Button: TMouseButton;
- Shift: TShiftState; X, Y: Integer; Layer: TCustomLayer);
- procedure ImgView32MouseMove(Sender: TObject; Shift: TShiftState;
- X, Y: Integer; Layer: TCustomLayer);
- procedure ImgView32Resize(Sender: TObject);
- procedure RgpArrowStyleClick(Sender: TObject);
- procedure TbrAnimationSpeedChange(Sender: TObject);
- procedure TbrLineWidthChange(Sender: TObject);
- private
- FArrowSize: Integer;
- FBoxIndex: Integer;
- FLastPos: TPoint;
- FDashes: TArrayOfFloat;
- FAnimationSpeed: Integer;
- FBoxCenter: array [0..1] of TFloatPoint;
- FVelocity: array [0..1] of TFloatPoint;
- FPattern: array [0..1] of TBitmap32;
- FBitmapFiller: TBitmapPolygonFiller;
- procedure SetArrowSize(const Value: Integer);
- protected
- procedure ArrowSizeChanged; virtual;
- public
- procedure ReDraw;
- property ArrowSize: Integer read FArrowSize write SetArrowSize;
- end;
- var
- FmArrowHead: TFmArrowHead;
- const
- CBoxSize = 60;
- CBorderSize = 10;
- CBoxSizePlus = CBoxSize + CBorderSize;
- CRad = (CBoxSize + CBorderSize) div 2;
- implementation
- {$R *.dfm}
- uses
- Math, GR32_LowLevel, GR32_Geometry, GR32_VectorUtils, GR32_ColorGradients, Types;
- { Miscellaneous functions }
- procedure ChangeSign(var Value: TFloat); {$IFDEF USEINLINING} inline; {$ENDIF}
- begin
- Value := -Value;
- end;
- procedure SwapVelocities(var Value1, Value2: TFloat); {$IFDEF USEINLINING} inline; {$ENDIF}
- var
- Val: TFloat;
- begin
- Val := Value1;
- Value1 := Value2;
- Value2 := Val;
- end;
- function GetNearestPointOnBox(const Pt, BoxCenter: TFloatPoint;
- const BoxPts: array of TFloatPoint): TFloatPoint;
- var
- I, Index: Integer;
- DistSqrd, DS: TFloat;
- begin
- Index := 0;
- DistSqrd := SqrDistance(BoxPts[0], Pt);
- for I := 1 to High(BoxPts) do
- begin
- DS := SqrDistance(BoxPts[I], Pt);
- if DS >= DistSqrd then Continue;
- DistSqrd := DS;
- Index := I;
- end;
- if Index = High(BoxPts) then I := 0 else I := Index + 1;
- if not SegmentIntersect(Pt, BoxCenter, BoxPts[Index], BoxPts[I], Result) then
- begin
- if Index = 0 then I := High(BoxPts) else I := Index - 1;
- if not SegmentIntersect(Pt, BoxCenter, BoxPts[Index], BoxPts[I], Result) then
- Result := Pt;
- end;
- end;
- function BoxesOverlap(const Box1Center, Box2Center: TFloatPoint;
- BoxSize: TFloat): Boolean;
- begin
- Result := (Abs(Box1Center.X - Box2Center.X) <= BoxSize) and
- (Abs(Box1Center.Y - Box2Center.Y) <= BoxSize);
- end;
- function MakeBezierCurve(const CtrlPts: TArrayOfFloatPoint): TArrayOfFloatPoint;
- var
- Index: Integer;
- Path: TFlattenedPath;
- begin
- Path := TFlattenedPath.Create;
- try
- Path.MoveTo(CtrlPts[0]);
- for Index := 0 to (High(CtrlPts) - 3) div 3 do
- Path.CurveTo(CtrlPts[Index * 3 + 1], CtrlPts[Index * 3 + 2], CtrlPts[Index * 3 + 3]);
- Path.EndPath;
- Result := Path.Path[0];
- finally
- Path.Free;
- end;
- end;
- function MakeBox(CenterPt: TFloatPoint; Size: TFloat): TArrayOfFloatPoint;
- begin
- Size := Size * 0.5;
- SetLength(Result, 4);
- Result[0] := OffsetPoint(CenterPt, -Size, -Size);
- Result[1] := OffsetPoint(CenterPt, Size, -Size);
- Result[2] := OffsetPoint(CenterPt, Size, Size);
- Result[3] := OffsetPoint(CenterPt, -Size, Size);
- end;
- { TFmArrowHead }
- procedure TFmArrowHead.FormCreate(Sender: TObject);
- begin
- ImgView32.Bitmap.DrawMode := dmOpaque;
- ImgView32.SetupBitmap(True, clWhite32);
- FBoxIndex := -1;
- FArrowSize := 20;
- FDashes := [14, 3, 3, 3, 3, 3];
- FBoxCenter[0] := FloatPoint(120, 100);
- FBoxCenter[1] := FloatPoint(240, 300);
- FAnimationSpeed := TbrAnimationSpeed.Position;
- CbxAnimateClick(nil);
- FPattern[0] := TBitmap32.Create;
- FPattern[0].LoadFromResourceName(HInstance, 'PATTERN1');
- FPattern[1] := TBitmap32.Create;
- FPattern[1].LoadFromResourceName(HInstance, 'PATTERN2');
- FBitmapFiller := TBitmapPolygonFiller.Create;
- Redraw;
- end;
- procedure TFmArrowHead.FormDestroy(Sender: TObject);
- begin
- FPattern[0].Free;
- FPattern[1].Free;
- FBitmapFiller.Free;
- end;
- procedure TFmArrowHead.ImgView32MouseDown(Sender: TObject;
- Button: TMouseButton; Shift: TShiftState; X, Y: Integer; Layer: TCustomLayer);
- var
- Index: Integer;
- begin
- FBoxIndex := -1;
- for Index := 0 to High(FBoxCenter) do
- if GR32.PtInRect(
- FloatRect(FBoxCenter[Index].X - CRad, FBoxCenter[Index].Y - CRad, FBoxCenter[Index].X + CRad, FBoxCenter[Index].Y + CRad),
- GR32.Point(X, Y)) then
- begin
- FLastPos := GR32.Point(X, Y);
- FBoxIndex := Index;
- Exit;
- end;
- end;
- procedure TFmArrowHead.ImgView32MouseMove(Sender: TObject;
- Shift: TShiftState; X, Y: Integer; Layer: TCustomLayer);
- var
- Index: Integer;
- begin
- if FBoxIndex >= 0 then
- begin
- FBoxCenter[FBoxIndex].X := EnsureRange(FBoxCenter[FBoxIndex].X + X - FLastPos.X, CRad, ImgView32.Width - CRad);
- FBoxCenter[FBoxIndex].Y := EnsureRange(FBoxCenter[FBoxIndex].Y + Y - FLastPos.Y, CRad, ImgView32.Height - CRad);
- ReDraw;
- FLastPos := GR32.Point(X, Y);
- end
- else
- begin
- for Index := 0 to High(FBoxCenter) do
- if GR32.PtInRect(
- FloatRect(FBoxCenter[Index].X - CRad, FBoxCenter[Index].Y - CRad, FBoxCenter[Index].X + CRad, FBoxCenter[Index].Y + CRad),
- GR32.Point(X, Y)) then
- begin
- ImgView32.Cursor := crHandPoint;
- Exit;
- end;
- ImgView32.Cursor := crArrow;
- end;
- end;
- procedure TFmArrowHead.ImgView32MouseUp(Sender: TObject;
- Button: TMouseButton; Shift: TShiftState; X, Y: Integer; Layer: TCustomLayer);
- begin
- FBoxIndex := -1;
- end;
- procedure TFmArrowHead.ImgView32Resize(Sender: TObject);
- begin
- ImgView32.Bitmap.SetSize(ImgView32.Width, ImgView32.Height);
- ReDraw;
- end;
- procedure TFmArrowHead.ReDraw;
- var
- Box : array [0..1] of TArrayOfFloatPoint;
- Poly, ArrowPts: TArrayOfFloatPoint;
- StartPoint, EndPoint, StartOffsetPt, EndOffsetPt: TFloatPoint;
- Delta: TFloatPoint;
- Arrow: TArrowHeadAbstract;
- GradientFiller: TLinearGradientPolygonFiller;
- ArrowOverlap: integer;
- const
- StartArrowColor: TColor32 = $60009900;
- StartArrowPenColor: TColor32 = $FF339900;
- EndArrowColor: TColor32 = $600000AA;
- EndArrowPenColor: TColor32 = $FF0033AA;
- begin
- ImgView32.Bitmap.Clear(clWhite32);
- (*
- ** Stippled boxes
- *)
- Box[0] := MakeBox(FBoxCenter[0], CBoxSize);
- Box[1] := MakeBox(FBoxCenter[1], CBoxSize);
- FBitmapFiller.Pattern := FPattern[0];
- DashLineFS(ImgView32.Bitmap, Box[0], FDashes, FBitmapFiller, EndArrowPenColor, True, CBorderSize, 1.5);
- FBitmapFiller.Pattern := FPattern[1];
- DashLineFS(ImgView32.Bitmap, Box[1], FDashes, FBitmapFiller, EndArrowPenColor, True, CBorderSize, 1.5);
- (*
- ** Construct a bezier line connecting the two boxes
- *)
- // Find line start and end point;
- // Given a box center point, and the size of the box plus the border, calculate the outer boxes.
- Box[0] := MakeBox(FBoxCenter[0], CBoxSizePlus);
- Box[1] := MakeBox(FBoxCenter[1], CBoxSizePlus);
- // If the boxes overlap we use the box center as the start and end points...
- if BoxesOverlap(FBoxCenter[0], FBoxCenter[1], CBoxSizePlus) then
- begin
- StartPoint := FBoxCenter[0];
- EndPoint := FBoxCenter[1];
- end else
- // ...otherwise we use nearest point on the border;
- begin
- StartPoint := GetNearestPointOnBox(FBoxCenter[1], FBoxCenter[0], Box[0]);
- EndPoint := GetNearestPointOnBox(FBoxCenter[0], FBoxCenter[1], Box[1]);
- end;
- // Calculate the bezier control points;
- Delta.X := StartPoint.X - FBoxCenter[0].X;
- Delta.Y := StartPoint.Y - FBoxCenter[0].Y;
- if Abs(Delta.X) > Abs(Delta.Y) then
- StartOffsetPt := FloatPoint(StartPoint.X + Delta.X * 2, StartPoint.Y)
- else
- StartOffsetPt := FloatPoint(StartPoint.X, StartPoint.Y + Delta.Y *2);
- Delta.X := EndPoint.X - FBoxCenter[1].X;
- Delta.Y := EndPoint.Y - FBoxCenter[1].Y;
- if Abs(Delta.X) > Abs(Delta.Y) then
- EndOffsetPt := FloatPoint(EndPoint.X + Delta.X * 2, EndPoint.Y)
- else
- EndOffsetPt := FloatPoint(EndPoint.X, EndPoint.Y + Delta.Y * 2);
- // Create a polyline and from that, a bezier
- Poly := BuildPolygonF([
- StartPoint.X, StartPoint.Y,
- StartOffsetPt.X, StartOffsetPt.Y,
- EndOffsetPt.X, EndOffsetPt.Y,
- EndPoint.X, EndPoint.Y]);
- Poly := MakeBezierCurve(Poly);
- (*
- ** Arrow heads
- *)
- case RgpArrowStyle.ItemIndex of
- 1: Arrow := TArrowHeadSimple.Create(ArrowSize);
- 2: Arrow := TArrowHeadFourPt.Create(ArrowSize);
- 3: Arrow := TArrowHeadDiamond.Create(ArrowSize);
- 4: Arrow := TArrowHeadCircle.Create(ArrowSize);
- else
- Arrow := nil;
- end;
- (*
- ** Draw arrow head(s) and a gradient connecting line
- ** or
- ** Draw a solid connecting line
- *)
- // Draw arrow head(s) and a gradient connecting line
- if (Arrow <> nil) then
- begin
- // Shorten line path at specified end(s) so arrow doesn't overlap box border;
- ArrowOverlap := ArrowSize;
- if (RgpArrowStyle.ItemIndex <> 4) then
- // Note: Because of the miter the arrow might still overlap the border a few pixels.
- Inc(ArrowOverlap, TbrLineWidth.Position)
- else
- Inc(ArrowOverlap, (TbrLineWidth.Position+1) div 2);
- case RgpPosition.ItemIndex of
- 0: Poly := Shorten(Poly, ArrowOverlap, lpStart);
- 1: Poly := Shorten(Poly, ArrowOverlap, lpEnd);
- 2: Poly := Shorten(Poly, ArrowOverlap, lpBoth);
- end;
- // Draw a gradient connecting line;
- GradientFiller := TLinearGradientPolygonFiller.Create;
- try
- GradientFiller.SimpleGradient(Poly[0], StartArrowPenColor, Poly[High(Poly)], EndArrowPenColor);
- PolylineFS(ImgView32.Bitmap, Poly, GradientFiller, False, TbrLineWidth.Position);
- finally
- GradientFiller.Free;
- end;
- // Draw arrow(s);
- // Start arrow head...
- if RgpPosition.ItemIndex <> 1 then
- begin
- ArrowPts := Arrow.GetPoints(Poly, False);
- // Brush
- PolygonFS(ImgView32.Bitmap, ArrowPts, StartArrowColor);
- // Stroke
- PolylineFS(ImgView32.Bitmap, ArrowPts, StartArrowPenColor, True, TbrLineWidth.Position);
- end;
- // End arrow head...
- if RgpPosition.ItemIndex <> 0 then
- begin
- ArrowPts := Arrow.GetPoints(Poly, True);
- // Brush
- PolygonFS(ImgView32.Bitmap, ArrowPts, EndArrowColor);
- // Stroke
- PolylineFS(ImgView32.Bitmap, ArrowPts, EndArrowPenColor, True, TbrLineWidth.Position);
- end;
- end else
- // Draw a solid connecting line
- PolylineFS(ImgView32.Bitmap, Poly, clBlack32, False, TbrLineWidth.Position);
- end;
- procedure TFmArrowHead.RgpArrowStyleClick(Sender: TObject);
- begin
- ReDraw;
- end;
- procedure TFmArrowHead.EdtArrowSizeChange(Sender: TObject);
- begin
- ArrowSize := EnsureRange(StrToIntDef(EdtArrowSize.Text, ArrowSize), 5, 40);
- end;
- procedure TFmArrowHead.BtnCloseClick(Sender: TObject);
- begin
- Close;
- end;
- procedure TFmArrowHead.CbxAnimateClick(Sender: TObject);
- begin
- Animation.Enabled := CbxAnimate.Checked;
- Randomize;
- FVelocity[0] := FloatPoint((2 * Random - 1) * FAnimationSpeed,
- (2 * Random -1) * FAnimationSpeed);
- FVelocity[1] := FloatPoint((2 * Random - 1) * FAnimationSpeed,
- (2 * Random -1) * FAnimationSpeed);
- end;
- procedure TFmArrowHead.TbrAnimationSpeedChange(Sender: TObject);
- var
- SpeedRatio: TFloat;
- begin
- if not Animation.Enabled then Exit;
- SpeedRatio := TbrAnimationSpeed.Position / FAnimationSpeed;
- FAnimationSpeed := TbrAnimationSpeed.Position;
- with FVelocity[0] do
- begin
- X := X * SpeedRatio;
- Y := Y * SpeedRatio;
- end;
- with FVelocity[1] do
- begin
- X := X * SpeedRatio;
- Y := Y * SpeedRatio;
- end;
- end;
- procedure TFmArrowHead.AnimationTimer(Sender: TObject);
- var
- Index: Integer;
- NextCenter: array [0..1] of TFloatPoint;
- begin
- if FBoxIndex >= 0 then Exit;
- // move boxes ...
- FBoxCenter[0] := OffsetPoint(FBoxCenter[0], FVelocity[0].X, FVelocity[0].Y);
- FBoxCenter[1] := OffsetPoint(FBoxCenter[1], FVelocity[1].X, FVelocity[1].Y);
- ReDraw;
- // update velocities where there are collisions ...
- NextCenter[0] := OffsetPoint(FBoxCenter[0], FVelocity[0].X, FVelocity[0].Y);
- NextCenter[1] := OffsetPoint(FBoxCenter[1], FVelocity[1].X, FVelocity[1].Y);
- if BoxesOverlap(NextCenter[0], NextCenter[1], CBoxSizePlus) then
- begin
- // manage box collisions ...
- if (Abs(FBoxCenter[0].X - FBoxCenter[1].X) > CBoxSizePlus) then
- SwapVelocities(FVelocity[0].X, FVelocity[1].X);
- if (Abs(FBoxCenter[0].Y - FBoxCenter[1].Y) > CBoxSizePlus) then
- SwapVelocities(FVelocity[0].Y, FVelocity[1].Y);
- NextCenter[0] := OffsetPoint(FBoxCenter[0], FVelocity[0].X, FVelocity[0].Y);
- NextCenter[1] := OffsetPoint(FBoxCenter[1], FVelocity[1].X, FVelocity[1].Y);
- end;
- // manage wall collisions ...
- for Index := 0 to High(FBoxCenter) do
- begin
- if (NextCenter[Index].X + CRad > ImgView32.Width) then
- FVelocity[Index].X := -Abs(FVelocity[Index].X)
- else
- if (NextCenter[Index].X - CRad < 0) then
- FVelocity[Index].X := Abs(FVelocity[Index].X);
- if (NextCenter[Index].Y + CRad > ImgView32.Height) then
- FVelocity[Index].Y := -Abs(FVelocity[Index].Y)
- else
- if (NextCenter[Index].Y - CRad < 0) then
- FVelocity[Index].Y := Abs(FVelocity[Index].Y);
- end;
- end;
- procedure TFmArrowHead.SetArrowSize(const Value: Integer);
- begin
- if FArrowSize <> Value then
- begin
- FArrowSize := Value;
- ArrowSizeChanged;
- end;
- end;
- procedure TFmArrowHead.ArrowSizeChanged;
- begin
- Redraw;
- end;
- procedure TFmArrowHead.TbrLineWidthChange(Sender: TObject);
- begin
- Redraw;
- end;
- end.
|