diff --git a/TestSuite/CompilationSamples/Coords.pas b/TestSuite/CompilationSamples/Coords.pas new file mode 100644 index 000000000..8f144c9f9 --- /dev/null +++ b/TestSuite/CompilationSamples/Coords.pas @@ -0,0 +1,635 @@ +/// Координатная сетка с возможностью масштабирования мышью +unit Coords; + +uses GraphWPFBase; +uses GraphWPF; +uses System.Threading.Tasks; + +type + Colors = GraphWPF.Colors; + Point = System.Windows.Point; + Alignment = GraphWPF.Alignment; + Command = class + procedure Play(dc: DrawingContext); abstract; + function AddToListAndPlay: Command; + end; + TypeFace = System.Windows.Media.Typeface; + FormattedText = System.Windows.Media.FormattedText; + +var +// Команды для "проигрывания" при перерисовке (изменении масштаба и сдвиге) + Commands := new List; + +function Command.AddToListAndPlay: Command; +begin + Commands.Add(Self); + FastDraw(dc -> Self.Play(dc)); + Result := Self; +end; + +var + CurrentPen := ColorPen(Colors.Black,1.4); + + CurrentFontFace := new Typeface('Arial'); + + Palette: array of Color := Arr(Colors.Green, Colors.Blue, Colors.Red, Colors.Orange, + Colors.Magenta, Colors.LightGreen, Colors.LightBlue, Colors.Coral, Colors.Gray, Colors.LightGray); + PaletteIndex := 0; + +function Pnt(x,y: real): Point := GraphWPF.Pnt(x,y); + +function CurrentPenColor: Color + := (CurrentPen.Brush as System.Windows.Media.SolidColorBrush).Color; + +function CurrentPenWidth: real := CurrentPen.Thickness; + +{$region Класс координатной сетки FS} + +var MinLen := 25; + +// Сделаем в логических координатах fso.LineToReal, fso.MoveToReal, fso.CircleReal +type + FS = class + // origin - точка по центру графика в логических координатах + origin: Point; + // scale - количество пикселей на одну единицу + scale: real; + mx, my: real; + a, b, min, max: real; // реальные координаты [a.b] x [min,max] + x, y, w, h: real; // экранные координаты - левый верхний угол, ширина и высота + // XTicks, YTicks - сколько логических единиц занимает одно деление (по умолчанию 1) + XTicks, YTicks: real; + // XTicks = YTicks = FindP(Scale)/Scale + // Ticks будет меняться по стратегии: 0.1 0.2 0.5 1 2 5 10 + MarginY := 6; + MarginX := 6; + XTicksPrecision := 4; + YTicksPrecision := 5; + spaceBetweenTextAndGraph := 0; + + constructor (scale, x, y, w, h: real; origin: Point := Pnt(0,0)); + begin + Self.Scale := scale; + Self.x := x; Self.y := y; Self.w := w; Self.h := h; + Self.origin := origin; + CorrectBounds; + end; + + function PointInside(p: Point): boolean; + begin + Result := (p.X >= a) and (p.X <= b) and (p.Y >= min) and (p.Y <= max); + end; + + function FindP(Scale: real): real; + begin // MinLen = 25 - то самое число + var p := Scale; + if p < MinLen then + while p < MinLen do + p *= 10 + else begin + while p >= MinLen * 10 do + p /= 10; + end; + // p in [MinLen..MinLen * 10) + if p >= MinLen * 5 then + p /= 5 + else if p >= MinLen *2 then + p /= 2; + Result := p; + end; + + procedure CorrectBounds; + begin + mx := Scale; + my := Scale; + a := -w/2/mx; + b := -a; + max := h/2/my; + min := - max; + + XTicks := FindP(Scale)/Scale; + YTicks := XTicks; + + var th := TextHeightP('0'); // высота текста + // tw - ширина текста - максимум по всем числам + var RY0 := GetRY0; // самое маленькое логическое значение по y. Соответствует нижней части экрана + var tw := RY0.Step(YTicks).TakeWhile(ry -> ry <= max + origin.y) + .Select(y -> TextWidthP(y.Round(YTicksPrecision).ToString)).DefaultIfEmpty.Max; + if tw = 0 then + tw := TextWidthP('0'); + var dd := TextWidthP(b.Round(xTicksPrecision).ToString)/2; + // dd - это половинка ширины последней цифры + //var tw := TextWidth('-99.9'); + w -= {MarginX * 2 +} spaceBetweenTextAndGraph + tw + dd; + h -= MarginY * 2 + spaceBetweenTextAndGraph + th; + x += MarginX + spaceBetweenTextAndGraph + tw; + y += MarginY; + + // И ещё раз пересчитаем! + a := -w/2/mx; + b := -a; + max := h/2/my; + min := - max; + + XTicks := FindP(Scale)/Scale; + YTicks := XTicks; + end; + + // a,b,min,max - логические + function RealToScreenX(xx: real) := x + mx * (xx - origin.x - a); + function RealToScreenY(yy: real) := y + h - my * (yy - origin.y - min); // ? + function ScreenToRealX(xx: real) := (xx - x) / mx + a + origin.x; + function ScreenToRealY(yy: real) := (y + h - yy) / my + min + origin.y; + function ScreenToReal(p: Point): Point := Pnt(ScreenToRealX(p.X),ScreenToRealY(p.Y)); + function RealToScreen(p: Point): Point := Pnt(RealToScreenX(p.X),RealToScreenY(p.Y)); + + /// Самое маленькое логическое значение по x + function GetRX0: real; + begin + var xt := XTicks * Trunc(Abs(a + origin.x)/XTicks); + var rx0: real; + if a + origin.x <= 0 then + rx0 := -xt + else rx0 := xt + XTicks; + Result := rx0; + end; + + /// Самое маленькое логическое значение по y + function GetRY0: real; + begin + var yt: real := YTicks * Trunc(Abs(min + origin.y)/YTicks); + var ry0: real; + if min + origin.y <= 0 then + ry0 := -yt + else ry0 := yt + YTicks; + Result := ry0; + end; + + procedure DrawDC(dc: DrawingContext); + begin + var AxisColor := GrayColor(112); + var GridPen := ColorPen(Colors.LightGray,1); + var AxisPen := ColorPen(AxisColor,1); + + var stepx := mx * XTicks; // Экранный шаг по оси x + var rx0 := GetRX0; // реальная начальная координата по x. При запуске = 14 + var x0 := RealToScreenX(rx0); // Экранная начальная координата по x. + + // ширина надписи по оси ox + var www := PABCSystem.max(TextWidthP(rx0.Round(XTicksPrecision).ToString), + TextWidthP((rx0+XTicks).Round(XTicksPrecision).ToString)) + 3; + var numTicks := Trunc(w / 2 / stepx); + + var it := 0; + if numTicks.IsEven then + it += 1; + + while x0<=x+w+0.000001 do + begin + if Abs(rx0)<0.000001 then + dc.DrawLine(AxisPen,Pnt(x0,y),Pnt(x0,y+h)) + else dc.DrawLine(GridPen,Pnt(x0,y),Pnt(x0,y+h)); + var text := rx0.Round(XTicksPrecision).ToString; + if (www <= stepx) or (it mod 2 = 1) then + TextOutDC(dc,x0,y+h+4,text,Alignment.CenterTop); + x0 += stepx; + rx0 += XTicks; + it += 1; + end; + + var stepy := my * YTicks; + var ry0 := GetRY0; + var y0 := RealToScreenY(ry0); + + while y0>=y-0.000001 do + begin + if Abs(ry0)<0.000001 then + DrawLineDC(dc,x,y0,x+w,y0,AxisPen) + else DrawLineDC(dc,x,y0,x+w,y0,GridPen); + TextOutDC(dc,x-4,y0,ry0.Round(YTicksPrecision).ToString,Alignment.RightCenter); + y0 -= stepy; + ry0 += YTicks; + end; + + DrawRectangleDC(dc, x, y, w, h, nil, ColorPen(Colors.Black,1)); + end; + + // Рисование координат + procedure Draw := FastDraw(DrawDC); + + // Рисование точек в целочисленных координатах. Специфический метод системы координат + procedure DrawPoints := FastDraw(dc -> begin + var sz := 1.2; + if Scale < 10 then + exit; + var a := ScreenToRealX(x); var b := ScreenToRealX(x+w); + var min := ScreenToRealY(y+h); var max := ScreenToRealY(y); + for var x := Ceil(a) to Floor(b) do + for var y := Ceil(min) to Floor(max) do + begin + var p := RealToScreen(Pnt(x,y)); + dc.DrawEllipse(ColorBrush(Colors.Black),nil,p,sz,sz); + end; + end); + end; + +const Scale0 = 27.1; + +{$endregion} + +// Переменные для системы координат +var + Scale := Scale0; + Origin := Pnt(0,0); + +var + // Система координат + fso: FS; + +// Вспомогательная переменная для задачи перерисовки +var tsk: System.Threading.Tasks.Task := nil; // Задача для перерисовки. В каждый момент одна + +var RusCultureInfo := new System.Globalization.CultureInfo('ru-ru'); + +function FormText(text: string; size: real := 14; color: GColor := Colors.Black): FormattedText := + new FormattedText(text, RusCultureInfo, System.Windows.FlowDirection.LeftToRight, + CurrentFontFace, size, Brushes.Black); + +function FormTextWithName(text: string; FontName: string := 'Arial'; size: real := 14; color: GColor := Colors.Black): FormattedText := + new FormattedText(text, RusCultureInfo, System.Windows.FlowDirection.LeftToRight, + new Typeface(FontName), size, Brushes.Black); + +{$region Команды при "проигрывании"} +var PointRadius := 2.0; + +var x := FormText('sdfgh'); + +procedure PointPlay(dc: DrawingContext; x,y: real; c: GColor; PointRadius: real); +begin + var p := fso.RealToScreen(Pnt(x,y)); + dc.DrawEllipse(ColorBrush(c),nil,p,PointRadius,PointRadius); +end; + +procedure PointsPlay(dc: DrawingContext; points: array of Point; c: GColor; PointRadius: real); +begin + var cc := ColorBrush(c); + foreach var p in points do + begin + var pp := fso.RealToScreen(p); + dc.DrawEllipse(cc,nil,pp,PointRadius,PointRadius); + end; +end; + +procedure LineToPlay(dc: DrawingContext; x,y: real); +begin + var p := fso.RealToScreen(Pnt(x,y)); + dc.DrawLine(CurrentPen,Pnt(Pen.X,Pen.Y),p); + GraphWPF.MoveTo(p.X,p.Y); +end; + +procedure CirclePlay(dc: DrawingContext; x,y,r: real; c: Color; borderc: Color); +begin + var cb := ColorBrush(c); + var cp := ColorPen(borderc,CurrentPenWidth); + var p := fso.RealToScreen(Pnt(x,y)); + r := r * Scale; + dc.DrawEllipse(cb,cp,p,r,r); +end; + +procedure RectanglePlay(dc: DrawingContext; x,y,w,h: real; c: Color; borderc: Color); +begin + var cb := ColorBrush(c); + var cp := ColorPen(borderc,CurrentPenWidth); + var p := fso.RealToScreen(Pnt(x,y)); + w := w * Scale; + h := h * Scale; + dc.DrawRectangle(cb,cp,Rect(p.x,p.y,w,h)); +end; + +procedure CorrectXYForTextHelper(var p: Point; w,h: real; Align: Alignment); +begin + case Align of + Alignment.LeftTop: {ничего}; + Alignment.LeftCenter: p.y -= h/2; + Alignment.LeftBottom: p.y -= h; + Alignment.CenterTop: begin p.x -= w/2 end; + Alignment.Center: begin p.x -= w/2; p.y -= h/2 end; + Alignment.CenterBottom: begin p.x -= w/2; p.y -= h end; + Alignment.RightTop: begin p.x -= w; end; + Alignment.RightCenter: begin p.x -= w; p.y -= h/2 end; + Alignment.RightBottom: begin p.x -= w; p.y -= h end; + end; +end; + +procedure TextPlayUnscaled(dc: DrawingContext; x,y: real; text: string; size: real; color: GColor; + Align: Alignment := Alignment.LeftTop); +begin + if size > 30000 then + size := 30000; + var p := fso.RealToScreen(Pnt(x,y)); + var ft := FormText(text,size,color); + var (w,h) := (ft.Width,ft.Height); + CorrectXYForTextHelper(p,w,h,Align); + dc.DrawText(ft,p); +end; + +procedure TextFontNamePlayUnscaled(dc: DrawingContext; x,y: real; text,fontname: string; size: real; color: GColor; + Align: Alignment := Alignment.LeftTop); +begin + if size > 30000 then + size := 30000; + var p := fso.RealToScreen(Pnt(x,y)); + var ft := FormTextWithName(text,fontname,size,color); + var (w,h) := (ft.Width,ft.Height); + CorrectXYForTextHelper(p,w,h,Align); + dc.DrawText(ft,p); +end; + +procedure TextPlay(dc: DrawingContext; x,y: real; text: string; size: real; color: GColor; + Align: Alignment := Alignment.LeftTop) + := TextPlayUnscaled(dc,x,y,text,size*Scale/Scale0,color,Align); + +procedure TextFontNamePlay(dc: DrawingContext; x,y: real; text,fontname: string; size: real; color: GColor; + Align: Alignment := Alignment.LeftTop) + := TextFontNamePlayUnscaled(dc,x,y,text,fontname,size*Scale/Scale0,color,Align); + +{$endregion} + + +type + PointC = auto class(Command) + x,y: real; + c: Color; + pointradius: real; + public + procedure Play(dc: DrawingContext); override := PointPlay(dc,x,y,c,pointradius); + end; + PointsC = auto class(Command) + points: array of Point; + c: Color; + pointradius: real; + public + procedure Play(dc: DrawingContext); override := PointsPlay(dc,points,c,pointradius); + end; + CircleC = auto class(Command) + x,y,r: real; + c,borderc: Color; + public + procedure Play(dc: DrawingContext); override := CirclePlay(dc,x,y,r,c,borderc); + end; + RectangleC = auto class(Command) + x,y,w,h: real; + c,borderc: Color; + public + procedure Play(dc: DrawingContext); override := RectanglePlay(dc,x,y,w,h,c,borderc); + end; + TextC = auto class(Command) + x,y: real; + text: string; + size: real; + c: Color; + Align: Alignment; + public + procedure Play(dc: DrawingContext); override := TextPlay(dc,x,y,text,size,c,Align); + end; + TextFontNameC = auto class(Command) + x,y: real; + text: string; + size: real; + c: Color; + fontname: string; + Align: Alignment; + public + procedure Play(dc: DrawingContext); override := TextFontNamePlay(dc,x,y,text,fontname,size,c,Align); + end; + TextUnscaledC = auto class(Command) + x,y: real; + text: string; + size: real; + c: Color; + Align: Alignment; + public + procedure Play(dc: DrawingContext); override := TextPlayUnscaled(dc,x,y,text,size,c,Align); + end; + TextFontNameUnscaledC = auto class(Command) + x,y: real; + text: string; + size: real; + c: Color; + fontname: string; + Align: Alignment; + public + procedure Play(dc: DrawingContext); override := TextFontNamePlayUnscaled(dc,x,y,text,fontname,size,c,Align); + end; + +procedure AddCommand(c: Command); +begin + Commands.Add(c); +end; + +{$endregion} + +function Window := GraphWPF.Window; + +// fso - глобальная и всегда инициализированная!!! +// tp - в логических (это точка Черепахи) + +{$region Примитивы рисования, вносимые в список команд} + +/// Рисует точку заданным цветом +procedure DrawPoint(x,y: real; Color: GColor := Colors.Black; PointRadius: real := 2) + := PointC.Create(x,y,color,PointRadius).AddToListAndPlay; + +/// Рисует прямоугольник +procedure DrawRectangle(x,y,w,h: real; color: GColor := Colors.White; borderColor: GColor := Colors.Black) + := RectangleC.Create(x,y,w,h,color,borderColor).AddToListAndPlay; + +/// Рисует прямоугольник без границы +procedure FillRectangle(x,y,w,h: real; color: GColor := Colors.White) + := RectangleC.Create(x,y,w,h,color,Colors.Transparent).AddToListAndPlay; + +/// Рисует круг +procedure DrawCircle(x,y,r: real; color: GColor := Colors.White; borderColor: GColor := Colors.Black) + := CircleC.Create(x,y,r,color,borderColor).AddToListAndPlay; + +/// Рисует круг без границы +procedure FillCircle(x,y,r: real; color: GColor := Colors.White) + := CircleC.Create(x,y,r,color,Colors.Transparent).AddToListAndPlay; + +/// Выводит текст в заданную позицию +procedure DrawText(x,y: real; text: string; Size: real := 14; Color: GColor := Colors.Black; FontName: string := nil; + Align: Alignment := Alignment.LeftTop); +begin + if FontName = nil then + TextC.Create(x,y,text,Size,Color,Align).AddToListAndPlay + else TextFontNameC.Create(x,y,text,Size,Color,fontname,Align).AddToListAndPlay; +end; + +/// Выводит текст в заданную позицию +procedure DrawTextUnscaled(x,y: real; text: string; Size: real := 14; Color: GColor := Colors.Black; FontName: string := nil; + Align: Alignment := Alignment.LeftTop); +begin + if FontName = nil then + TextUnscaledC.Create(x,y,text,Size,Color,Align).AddToListAndPlay + else TextFontNameUnscaledC.Create(x,y,text,Size,Color,fontname,Align).AddToListAndPlay; +end; + +/// Рисует точки заданным цветом +procedure DrawPoints(points: array of Point; color: GColor; PointRadius: real := 2) + := PointsC.Create(points,color,PointRadius).AddToListAndPlay; + +/// Рисует точки следующим цветом в палитре цветов +procedure DrawPoints(points: array of Point; PointRadius: real := 2); +begin + var color := Palette[PaletteIndex]; + PaletteIndex += 1; + if PaletteIndex >= Palette.Length then + PaletteIndex := 0; + DrawPoints(points,color,PointRadius); +end; + +/// Рисует точки заданным цветом +procedure DrawPoints(xx,yy: array of real; color: GColor; PointRadius: real := 2); +begin + var points := Zip(xx,yy,(x,y) -> Pnt(x,y)).ToArray; + DrawPoints(points,color,PointRadius); +end; + +/// Рисует точки следующим цветом в палитре цветов +procedure DrawPoints(xx,yy: array of real; PointRadius: real := 2); +begin + var points := Zip(xx,yy,(x,y) -> Pnt(x,y)).ToArray; + DrawPoints(points,PointRadius); +end; + +{$endregion} + +procedure PlayCommands; +begin + FastDraw(dc -> begin + dc.PushClip(new System.Windows.Media.RectangleGeometry(Rect(fso.x,fso.y,fso.w,fso.h))); + foreach var command in commands do + command.Play(dc); + dc.Pop; + end); +end; + +var DrawCoords := 1; + +procedure InitCoordGrid; +begin + fso := new FS(Scale,0,0,Window.Width,Window.Height,Origin); + if DrawCoords = 1 then + fso.Draw + else if DrawCoords = 2 then + begin + fso.Draw; + fso.DrawPoints; + end; +end; + +var + // Процедура дополнительной инициализации для модулей на основе Coords + InitProc: procedure := nil; + +/// Установка положения начала координат +procedure SetOrigin(x,y: real); +begin + Origin := Pnt(x,y); +end; + +/// Установка масштаба +procedure SetScale(sc: real); +begin + Scale := sc; + Window.Clear; + InitCoordGrid; + if InitProc <> nil then + InitProc; +end; + +procedure Redraw; +begin + if (tsk <> nil) and not tsk.IsCompleted then + exit; + tsk := Task.Run(() -> begin + GraphWPF.Redraw(()->begin + Window.Clear; + InitCoordGrid; + if InitProc <> nil then + InitProc; + PlayCommands; + end) + end) +end; + +type + Handlers = static class +// Вспомогательные переменные для событий + static StartPoint,MousePoint: Point; + static deltaMultScale := 1.2; // на сколько увеличивать-уменьшать при прокручивании колёсика мыши + static AfterResize := False; + + static procedure MouseDown(x,y: real; mb: integer); + begin + StartPoint := Pnt(x,y); + end; + + static procedure MouseMove(x,y: real; mb: integer); + begin + if AfterResize then + begin + AfterResize := False; + exit; + end; + if (tsk<>nil) and not tsk.IsCompleted then + exit; + MousePoint := Pnt(x,y); + if mb<>1 then + exit; + var v := fso.ScreenToReal(MousePoint) - fso.ScreenToReal(StartPoint); + Origin.X -= v.X; + Origin.Y -= v.Y; + StartPoint := MousePoint; + Redraw; + end; + + static procedure MouseWheel(delta: real); + begin + if (tsk<>nil) and not tsk.IsCompleted then + exit; + + if delta > 0 then // вверх - увеличение + begin + if MinLen/Scale > 0.0001 then + Scale *= deltaMultScale + else exit; + Origin := Origin + (deltaMultScale - 1)/deltaMultScale * (fso.ScreenToReal(mousePoint) - Origin); + end + else + begin + if MinLen/Scale < 500 then + Scale /= deltaMultScale + else exit; + Origin := Origin - (deltaMultScale - 1) * (fso.ScreenToReal(mousePoint) - Origin); + end; + Redraw; + end; + + static procedure Resize; + begin + Redraw; + AfterResize := True; + end; + end; + +initialization + InitCoordGrid; + Window.Title := 'Система координат'; +finalization + Redraw; + OnMouseWheel := Handlers.MouseWheel; + OnMouseDown := Handlers.MouseDown; + OnMouseMove := Handlers.MouseMove; + OnResize := Handlers.Resize; +end. \ No newline at end of file