From 5c6f0c8bfc9c74584c032eb7b242c2b28e3e5441 Mon Sep 17 00:00:00 2001 From: Pilzinsel64 Date: Mon, 10 Jun 2024 12:29:56 +0200 Subject: [PATCH] Pilz.UI: convert to C# --- Pilz.UI/DisplayHelp.cs | 252 ++++++ Pilz.UI/Extensions.cs | 13 + Pilz.UI/Extensions.vb | 14 + Pilz.UI/HelpfulFunctions.cs | 19 + Pilz.UI/HighlightPanel.cs | 323 +++++++ Pilz.UI/Highlighter.cs | 510 +++++++++++ Pilz.UI/My Project/Application.Designer.vb | 13 - Pilz.UI/My Project/Application.myapp | 10 - Pilz.UI/My Project/Resources.Designer.vb | 63 -- Pilz.UI/My Project/Settings.Designer.vb | 73 -- Pilz.UI/My Project/Settings.settings | 7 - Pilz.UI/PaintingControl/ArrowLineCapProps.cs | 18 + .../PaintingControl/DefaultDrawMethodes.cs | 284 ++++++ .../PaintingControl/DefaultLineCapProps.cs | 17 + .../EventArgs/PaintingObjectEventArgs.cs | 8 + .../EventArgs/PaintingObjectPaintEventArgs.cs | 53 ++ .../IPaintingObjectContainer.cs | 7 + Pilz.UI/PaintingControl/ImageProperties.cs | 9 + .../LineCapConfigurationArgs.cs | 26 + Pilz.UI/PaintingControl/LineCapProps.cs | 9 + Pilz.UI/PaintingControl/PaintingControl.cs | 785 +++++++++++++++++ .../PaintingControlDelegates.cs | 11 + Pilz.UI/PaintingControl/PaintingObject.cs | 826 ++++++++++++++++++ .../PaintingObjectJsonSerializer.cs | 7 + .../PaintingControl/PaintingObjectLayering.cs | 112 +++ .../PaintingObjectListLayering.cs | 87 ++ .../PaintingControl/PaintingObjectResizing.cs | 346 ++++++++ Pilz.UI/Pilz.UI.csproj | 85 ++ Pilz.UI/Resources.Designer.cs | 70 ++ Pilz.UI/{My Project => }/Resources.resx | 0 Pilz.UI/Utilities/DrawingControl.cs | 50 ++ Pilz.UI/Utilities/User32Bridge.cs | 9 + 32 files changed, 3950 insertions(+), 166 deletions(-) create mode 100644 Pilz.UI/DisplayHelp.cs create mode 100644 Pilz.UI/Extensions.cs create mode 100644 Pilz.UI/Extensions.vb create mode 100644 Pilz.UI/HelpfulFunctions.cs create mode 100644 Pilz.UI/HighlightPanel.cs create mode 100644 Pilz.UI/Highlighter.cs delete mode 100644 Pilz.UI/My Project/Application.Designer.vb delete mode 100644 Pilz.UI/My Project/Application.myapp delete mode 100644 Pilz.UI/My Project/Resources.Designer.vb delete mode 100644 Pilz.UI/My Project/Settings.Designer.vb delete mode 100644 Pilz.UI/My Project/Settings.settings create mode 100644 Pilz.UI/PaintingControl/ArrowLineCapProps.cs create mode 100644 Pilz.UI/PaintingControl/DefaultDrawMethodes.cs create mode 100644 Pilz.UI/PaintingControl/DefaultLineCapProps.cs create mode 100644 Pilz.UI/PaintingControl/EventArgs/PaintingObjectEventArgs.cs create mode 100644 Pilz.UI/PaintingControl/EventArgs/PaintingObjectPaintEventArgs.cs create mode 100644 Pilz.UI/PaintingControl/IPaintingObjectContainer.cs create mode 100644 Pilz.UI/PaintingControl/ImageProperties.cs create mode 100644 Pilz.UI/PaintingControl/LineCapConfigurationArgs.cs create mode 100644 Pilz.UI/PaintingControl/LineCapProps.cs create mode 100644 Pilz.UI/PaintingControl/PaintingControl.cs create mode 100644 Pilz.UI/PaintingControl/PaintingControlDelegates.cs create mode 100644 Pilz.UI/PaintingControl/PaintingObject.cs create mode 100644 Pilz.UI/PaintingControl/PaintingObjectJsonSerializer.cs create mode 100644 Pilz.UI/PaintingControl/PaintingObjectLayering.cs create mode 100644 Pilz.UI/PaintingControl/PaintingObjectListLayering.cs create mode 100644 Pilz.UI/PaintingControl/PaintingObjectResizing.cs create mode 100644 Pilz.UI/Pilz.UI.csproj create mode 100644 Pilz.UI/Resources.Designer.cs rename Pilz.UI/{My Project => }/Resources.resx (100%) create mode 100644 Pilz.UI/Utilities/DrawingControl.cs create mode 100644 Pilz.UI/Utilities/User32Bridge.cs diff --git a/Pilz.UI/DisplayHelp.cs b/Pilz.UI/DisplayHelp.cs new file mode 100644 index 0000000..f80cae0 --- /dev/null +++ b/Pilz.UI/DisplayHelp.cs @@ -0,0 +1,252 @@ +using System.Drawing; +using System.Drawing.Drawing2D; + +namespace Pilz.UI; + +public class DisplayHelp +{ + public static void FillRectangle(Graphics g, Rectangle bounds, Color color1) + { + FillRectangle(g, bounds, color1, Color.Empty, 90); + } + + public static void FillRectangle(Graphics g, Rectangle bounds, Color color1, Color color2) + { + FillRectangle(g, bounds, color1, color2, 90); + } + + public static void FillRectangle(Graphics g, Rectangle r, Color color1, Color color2, int gradientAngle) + { + if (r.Width == 0 || r.Height == 0) + return; + + if (color2.IsEmpty) + { + if (!color1.IsEmpty) + { + var sm = g.SmoothingMode; + g.SmoothingMode = SmoothingMode.None; + + using var brush = new SolidBrush(color1); + g.FillRectangle(brush, r); + + g.SmoothingMode = sm; + } + } + else + { + using var brush = CreateLinearGradientBrush(r, color1, color2, gradientAngle); + g.FillRectangle(brush, r); + } + } + + public static void FillRectangle(Graphics g, Rectangle r, Color color1, Color color2, int gradientAngle, float[] factors, float[] positions) + { + if (r.Width == 0 || r.Height == 0) + return; + + if (color2.IsEmpty) + { + if (!color1.IsEmpty) + { + var sm = g.SmoothingMode; + g.SmoothingMode = SmoothingMode.None; + + using var brush = new SolidBrush(color1); + g.FillRectangle(brush, r); + + g.SmoothingMode = sm; + } + } + else + { + using var brush = CreateLinearGradientBrush(r, color1, color2, gradientAngle); + var blend = new Blend(factors.Length); + blend.Factors = factors; + blend.Positions = positions; + brush.Blend = blend; + g.FillRectangle(brush, r); + } + } + + public static void FillRoundedRectangle(Graphics g, Rectangle bounds, int cornerSize, Color color1, Color color2, int gradientAngle) + { + if (color2.IsEmpty) + { + if (!color1.IsEmpty) + { + using var brush = new SolidBrush(color1); + FillRoundedRectangle(g, brush, bounds, cornerSize); + } + } + else + { + using var brush = CreateLinearGradientBrush(bounds, color1, color2, gradientAngle); + FillRoundedRectangle(g, brush, bounds, cornerSize); + } + } + + public static void FillRoundedRectangle(Graphics g, Rectangle bounds, int cornerSize, Color color1, Color color2) + { + FillRoundedRectangle(g, bounds, cornerSize, color1, color2, 90); + } + + public static void FillRoundedRectangle(Graphics g, Rectangle bounds, int cornerSize, Color color1) + { + using var brush = new SolidBrush(color1); + FillRoundedRectangle(g, brush, bounds, cornerSize); + } + + public static void FillRoundedRectangle(Graphics g, Brush brush, Rectangle bounds, int cornerSize) + { + if (cornerSize <= 0) + { + var sm = g.SmoothingMode; + g.SmoothingMode = SmoothingMode.None; + g.FillRectangle(brush, bounds); + g.SmoothingMode = sm; + } + else + { + bounds.Width -= 1; + bounds.Height -= 1; + + using var path = GetRoundedRectanglePath(bounds, cornerSize); + g.FillPath(brush, path); + } + } + + public static void DrawRectangle(Graphics g, Color color, int x, int y, int width, int height) + { + using var pen = new Pen(color, 1f); + DrawRectangle(g, pen, x, y, width, height); + } + + public static void DrawRectangle(Graphics g, Color color, Rectangle r) + { + DrawRectangle(g, color, r.X, r.Y, r.Width, r.Height); + } + + public static void DrawRectangle(Graphics g, Pen pen, int x, int y, int width, int height) + { + width -= 1; + height -= 1; + g.DrawRectangle(pen, x, y, width, height); + } + + public static void DrawRoundedRectangle(Graphics g, Color color, Rectangle bounds, int cornerSize) + { + if (color.IsEmpty) + return; + + using var pen = new Pen(color); + DrawRoundedRectangle(g, pen, bounds.X, bounds.Y, bounds.Width, bounds.Height, cornerSize); + } + + public static void DrawRoundedRectangle(Graphics g, Pen pen, int x, int y, int width, int height, int cornerSize) + { + DrawRoundedRectangle(g, pen, null, x, y, width, height, cornerSize); + } + + public static void DrawRoundedRectangle(Graphics g, Pen pen, Brush fill, int x, int y, int width, int height, int cornerSize) + { + width -= 1; + height -= 1; + var r = new Rectangle(x, y, width, height); + using var path = GetRoundedRectanglePath(r, cornerSize); + + if (fill is not null) + g.FillPath(fill, path); + + g.DrawPath(pen, path); + } + + public static GraphicsPath GetRoundedRectanglePath(Rectangle r, int cornerSize) + { + var path = new GraphicsPath(); + + if (cornerSize == 0) + path.AddRectangle(r); + else + { + AddCornerArc(path, r, cornerSize, eCornerArc.TopLeft); + AddCornerArc(path, r, cornerSize, eCornerArc.TopRight); + AddCornerArc(path, r, cornerSize, eCornerArc.BottomRight); + AddCornerArc(path, r, cornerSize, eCornerArc.BottomLeft); + path.CloseAllFigures(); + } + + return path; + } + + public static LinearGradientBrush CreateLinearGradientBrush(Rectangle r, Color color1, Color color2, float gradientAngle) + { + if (r.Width <= 0) + r.Width = 1; + if (r.Height <= 0) + r.Height = 1; + return new LinearGradientBrush(new Rectangle(r.X, r.Y - 1, r.Width, r.Height + 1), color1, color2, gradientAngle); + } + + public static void AddCornerArc(GraphicsPath path, Rectangle bounds, int cornerDiameter, eCornerArc corner) + { + if (cornerDiameter > 0) + { + var a = GetCornerArc(bounds, cornerDiameter, corner); + path.AddArc(a.X, a.Y, a.Width, a.Height, a.StartAngle, a.SweepAngle); + } + + else if (corner == eCornerArc.TopLeft) + path.AddLine(bounds.X, bounds.Y + 2, bounds.X, bounds.Y); + else if (corner == eCornerArc.BottomLeft) + path.AddLine(bounds.X + 2, bounds.Bottom, bounds.X, bounds.Bottom); + else if (corner == eCornerArc.TopRight) + path.AddLine(bounds.Right - 2, bounds.Y, bounds.Right, bounds.Y); + else if (corner == eCornerArc.BottomRight) + path.AddLine(bounds.Right, bounds.Bottom - 2, bounds.Right, bounds.Bottom); + } + + internal static ArcData GetCornerArc(Rectangle bounds, int cornerDiameter, eCornerArc corner) + { + if (cornerDiameter == 0) + cornerDiameter = 1; + + int diameter = cornerDiameter * 2; + + return corner switch + { + eCornerArc.TopLeft => new ArcData(bounds.X, bounds.Y, diameter, diameter, 180f, 90f), + eCornerArc.TopRight => new ArcData(bounds.Right - diameter, bounds.Y, diameter, diameter, 270f, 90f), + eCornerArc.BottomLeft => new ArcData(bounds.X, bounds.Bottom - diameter, diameter, diameter, 90f, 90f), + _ => new ArcData(bounds.Right - diameter, bounds.Bottom - diameter, diameter, diameter, 0f, 90f), + }; + } + + public enum eCornerArc + { + TopLeft, + TopRight, + BottomLeft, + BottomRight + } + + internal struct ArcData + { + public int X; + public int Y; + public int Width; + public int Height; + public float StartAngle; + public float SweepAngle; + + public ArcData(int x, int y, int width, int height, float startAngle, float sweepAngle) + { + X = x; + Y = y; + Width = width; + Height = height; + StartAngle = startAngle; + SweepAngle = sweepAngle; + } + } +} \ No newline at end of file diff --git a/Pilz.UI/Extensions.cs b/Pilz.UI/Extensions.cs new file mode 100644 index 0000000..7a345f4 --- /dev/null +++ b/Pilz.UI/Extensions.cs @@ -0,0 +1,13 @@ +using System.Drawing; + +namespace Pilz.UI; + +public static class Extensions +{ + public static Image? ToIcon(this Image image) + { + if (image is Bitmap bmp) + return Icon.FromHandle(bmp.GetHicon()); + return null; + } +} \ No newline at end of file diff --git a/Pilz.UI/Extensions.vb b/Pilz.UI/Extensions.vb new file mode 100644 index 0000000..d7fd6ef --- /dev/null +++ b/Pilz.UI/Extensions.vb @@ -0,0 +1,14 @@ +Imports System.Drawing +Imports System.Runtime.CompilerServices + +Public Module Extensions + + + Public Function ToIcon(image As Image) + If TypeOf image Is Bitmap Then + Return Icon.FromHandle(CType(image, Bitmap).GetHicon) + End If + Return Nothing + End Function + +End Module diff --git a/Pilz.UI/HelpfulFunctions.cs b/Pilz.UI/HelpfulFunctions.cs new file mode 100644 index 0000000..7d776e7 --- /dev/null +++ b/Pilz.UI/HelpfulFunctions.cs @@ -0,0 +1,19 @@ +using System.Diagnostics; +using System.Windows.Forms; + +namespace Pilz.UI; + +public static class HelpfulFunctions +{ + public static void Sleep(int milliseconds) + { + var stopw = new Stopwatch(); + + stopw.Start(); + + while (stopw.ElapsedMilliseconds < milliseconds) + Application.DoEvents(); + + stopw.Stop(); + } +} \ No newline at end of file diff --git a/Pilz.UI/HighlightPanel.cs b/Pilz.UI/HighlightPanel.cs new file mode 100644 index 0000000..7462a33 --- /dev/null +++ b/Pilz.UI/HighlightPanel.cs @@ -0,0 +1,323 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; + +namespace Pilz.UI; + + +internal class HighlightPanel : Control +{ + + private Dictionary _Highlights = null; + private List _HighlightRegions = new List(); + + public HighlightPanel(Dictionary highlights) + { + _Highlights = highlights; + SetStyle(ControlStyles.UserPaint, true); + SetStyle(ControlStyles.AllPaintingInWmPaint, true); + SetStyle(ControlStyles.Opaque, true); + SetStyle(ControlStyles.ResizeRedraw, true); + SetStyle(ControlStyles.OptimizedDoubleBuffer, true); + SetStyle(ControlStyles.Selectable, false); + } + + protected override void OnPaint(PaintEventArgs e) + { + var g = e.Graphics; + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; + + foreach (HighlightRegion highlightRegion in _HighlightRegions) + { + Color[] colors = GetHighlightColors(highlightRegion.HighlightColor); + var r = highlightRegion.Bounds; + var back = highlightRegion.BackColor; + r.Inflate(1, 1); + DisplayHelp.FillRectangle(g, r, back); + r.Inflate(-1, -1); + DisplayHelp.FillRoundedRectangle(g, r, 2, colors[0]); + r.Inflate(-2, -2); + DisplayHelp.DrawRectangle(g, colors[2], r); + r.Inflate(1, 1); + DisplayHelp.DrawRoundedRectangle(g, colors[1], r, 2); + } + + base.OnPaint(e); + } + + private Color[] GetHighlightColors(eHighlightColor color) + { + Color[] colors = new Color[3]; + + if (color == eHighlightColor.Blue) + { + colors[0] = GetColor(172, 0x6A9CD4); + colors[1] = GetColor(0x6A9CD4); + colors[2] = GetColor(0x5D7EA4); + } + else if (color == eHighlightColor.Orange) + { + colors[0] = GetColor(172, 0xFF9C00); + colors[1] = GetColor(0xFF9C00); + colors[2] = GetColor(0xCC6600); + } + else if (color == eHighlightColor.Green) + { + colors[0] = GetColor(172, 0x71B171); + colors[1] = GetColor(0x71B171); + colors[2] = GetColor(0x339933); + } + else if (color == eHighlightColor.Custom) + { + if (_CustomHighlightColors is null || _CustomHighlightColors.Length < 3) + { + colors[0] = Color.Red; + colors[1] = Color.Red; + colors[2] = Color.Red; + } + else + { + colors[0] = _CustomHighlightColors[0]; + colors[1] = _CustomHighlightColors[1]; + colors[2] = _CustomHighlightColors[2]; + } + } + else + { + colors[0] = GetColor(172, 0xC63030); + colors[1] = GetColor(0xC63030); + colors[2] = GetColor(0x990000); + } + + return colors; + } + + protected override void OnVisibleChanged(EventArgs e) + { + if (Visible && !_UpdatingRegion) + UpdateRegion(); + base.OnVisibleChanged(e); + } + + protected override void OnHandleCreated(EventArgs e) + { + if (!_RegionInitialized) + UpdateRegion(); + base.OnHandleCreated(e); + } + + private bool _RegionInitialized = false; + private bool _UpdatingRegion = false; + + internal void UpdateRegion() + { + if (_UpdatingRegion || !IsHandleCreated) + return; + + try + { + _UpdatingRegion = true; + Region = null; + _HighlightRegions.Clear(); + if (_Highlights is null) + return; + + if (_Highlights.Count == 0 && _FocusHighlightControl is null) + { + Visible = false; + return; + } + + bool processFocusControl = true; + Region region = null; + + foreach (KeyValuePair item in _Highlights) + { + if (item.Value == eHighlightColor.None || !GetIsVisible(item.Key)) + continue; + if (ReferenceEquals(item.Key, _FocusHighlightControl)) + processFocusControl = false; + var r = GetControlRect(item.Key); + if (r.IsEmpty) + continue; + r.Inflate(2, 2); + _HighlightRegions.Add(new HighlightRegion(r, GetBackColor(item.Key.Parent), item.Value)); + + if (region is null) + region = new Region(r); + else + { + region.Union(r); + } + + r.Inflate(-3, -3); + region.Exclude(r); + } + + if (processFocusControl && _FocusHighlightControl is not null && _FocusHighlightControl.Visible) + { + var r = GetControlRect(_FocusHighlightControl); + + if (!r.IsEmpty) + { + r.Inflate(2, 2); + _HighlightRegions.Add(new HighlightRegion(r, GetBackColor(_FocusHighlightControl.Parent), _FocusHighlightColor)); + + if (region is null) + region = new Region(r); + else + { + region.Union(r); + } + + r.Inflate(-3, -3); + region.Exclude(r); + } + } + + Region = region; + + if (region is null) + Visible = false; + else if (!Visible) + { + Visible = true; + BringToFront(); + } + + Invalidate(); + } + finally + { + _UpdatingRegion = false; + _RegionInitialized = true; + } + } + + private static Color GetColor(int rgb) + { + if (rgb == -1) + return Color.Empty; + else + { + return Color.FromArgb((rgb & 0xFF0000) >> 16, (rgb & 0xFF00) >> 8, rgb & 0xFF); + } + } + + private static Color GetColor(int alpha, int rgb) + { + if (rgb == -1) + return Color.Empty; + else + { + return Color.FromArgb(alpha, (rgb & 0xFF0000) >> 16, (rgb & 0xFF00) >> 8, rgb & 0xFF); + } + } + + private bool GetIsVisible(Control control) + { + if (!control.Visible) + return false; + if (control.Parent is null || !control.IsHandleCreated) + return control.Visible; + var rect = new Pilz.Win32.Native.RECT(); + Pilz.Win32.Native.User32.GetWindowRect(control.Handle, ref rect); + var pp = control.Parent.PointToClient(new Point(rect.Left + 3, rect.Top + 3)); + var handle = Pilz.Win32.Native.User32.ChildWindowFromPointEx(control.Parent.Handle, new Pilz.Win32.Native.POINT(pp.X, pp.Y), (uint)Pilz.Win32.Native.WindowFromPointFlags.CWP_SKIPINVISIBLE); + if (handle == nint.Zero) + return control.Visible; + var c = Control.FromHandle(handle); + + if (c is not null && !ReferenceEquals(c, control) && !ReferenceEquals(c, this) && !ReferenceEquals(c, control.Parent)) + return false; + + return control.Visible; + } + + private Color GetBackColor(Control control) + { + var backColor = control.BackColor; + + if (backColor.IsEmpty || backColor == Color.Transparent) + backColor = SystemColors.Control; + else if (backColor.A < 255) + { + backColor = Color.FromArgb(255, backColor); + } + + return backColor; + } + + protected override void OnResize(EventArgs e) + { + UpdateRegion(); + base.OnResize(e); + } + + private Rectangle GetControlRect(Control c) + { + if (!c.IsHandleCreated) + return Rectangle.Empty; + var rect = default(Pilz.Win32.Native.RECT); + Pilz.Win32.Native.User32.GetWindowRect(c.Handle, ref rect); + var p = this.PointToClient(rect.Location); + return new Rectangle(p, rect.Size); + } + + private struct HighlightRegion + { + public Rectangle Bounds; + public Color BackColor; + public eHighlightColor HighlightColor; + + public HighlightRegion(Rectangle bounds, Color backColor, eHighlightColor highlightColor) + { + Bounds = bounds; + BackColor = backColor; + HighlightColor = highlightColor; + } + } + + private Control _FocusHighlightControl; + + public Control FocusHighlightControl + { + get + { + return _FocusHighlightControl; + } + set + { + _FocusHighlightControl = value; + } + } + + private eHighlightColor _FocusHighlightColor = eHighlightColor.Blue; + + public eHighlightColor FocusHighlightColor + { + get + { + return _FocusHighlightColor; + } + set + { + _FocusHighlightColor = value; + } + } + + private Color[] _CustomHighlightColors = null; + + public Color[] CustomHighlightColors + { + get + { + return _CustomHighlightColors; + } + set + { + _CustomHighlightColors = value; + } + } +} \ No newline at end of file diff --git a/Pilz.UI/Highlighter.cs b/Pilz.UI/Highlighter.cs new file mode 100644 index 0000000..f428f17 --- /dev/null +++ b/Pilz.UI/Highlighter.cs @@ -0,0 +1,510 @@ +using Microsoft.VisualBasic.CompilerServices; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +// Nicht gemergte Änderung aus Projekt "Pilz.UI (net8.0-windows)" +// Vor: +// Imports System.Windows.Forms +// Imports System.Drawing +// Nach: +// Imports System.Drawing +// Imports System.Windows.Forms +using System.Windows.Forms; + +namespace Pilz.UI; + + +public class Highlighter : Component +{ + + private Dictionary _Highlights = new Dictionary(); + private Dictionary _HighlightOnFocus = new Dictionary(); + + protected override void Dispose(bool disposing) + { + if (_ContainerControl is not null) + { + _ContainerControl.SizeChanged -= ContainerControlSizeChanged; + _ContainerControl.HandleCreated -= ContainerControlHandleCreated; + } + + if (_HighlightPanel is not null && _HighlightPanel.Parent is null && !_HighlightPanel.IsDisposed) + { + _HighlightPanel.Dispose(); + _HighlightPanel = null; + } + else + { + _HighlightPanel = null; + } + + base.Dispose(disposing); + } + + [DefaultValue(false)] + [Localizable(true)] + [Description("Indicates whether control is highlighted when it receives input focus.")] + public bool GetHighlightOnFocus(Control c) + { + if (_HighlightOnFocus.ContainsKey(c)) + return _HighlightOnFocus[c]; + + return false; + } + + public void SetHighlightOnFocus(Control c, bool highlight) + { + if (c is null) + throw new NullReferenceException(); + + if (_HighlightOnFocus.ContainsKey(c)) + { + + if (!highlight) + RemoveHighlightOnFocus(_HighlightOnFocus, c); + + return; + } + + if (highlight) + AddHighlightOnFocus(_HighlightOnFocus, c); + } + + private void AddHighlightOnFocus(Dictionary highlightOnFocus, Control c) + { + c.Enter += ControlHighlightEnter; + c.Leave += ControlHighlightLeave; + c.VisibleChanged += ControlHighlightVisibleChanged; + highlightOnFocus.Add(c, true); + } + + private void ControlHighlightVisibleChanged(object sender, EventArgs e) + { + if (_HighlightPanel is not null && Conversions.ToBoolean(Operators.ConditionalCompareObjectEqual(_HighlightPanel.FocusHighlightControl, sender, false))) + UpdateHighlighterRegion(); + } + + private void ControlHighlightLeave(object sender, EventArgs e) + { + if (_HighlightPanel is not null) + _HighlightPanel.FocusHighlightControl = null; + UpdateHighlighterRegion(); + } + + private void ControlHighlightEnter(object sender, EventArgs e) + { + if (_HighlightPanel is not null) + { + if (!_HighlightPanel.Visible) + _HighlightPanel.Visible = true; + _HighlightPanel.BringToFront(); + _HighlightPanel.FocusHighlightControl = (Control)sender; + } + + UpdateHighlighterRegion(); + } + + private void RemoveHighlightOnFocus(Dictionary highlightOnFocus, Control c) + { + c.Enter -= ControlHighlightEnter; + c.Leave -= ControlHighlightLeave; + c.VisibleChanged -= ControlHighlightVisibleChanged; + highlightOnFocus.Remove(c); + } + + [DefaultValue(eHighlightColor.None)] + [Localizable(true)] + [Description("Indicates the highlight color that is applied to the control.")] + public eHighlightColor GetHighlightColor(Control c) + { + if (_Highlights.ContainsKey(c)) + return _Highlights[c]; + + return eHighlightColor.None; + } + + public void SetHighlightColor(Control c, eHighlightColor highlightColor) + { + if (_Highlights.ContainsKey(c)) + { + + if (highlightColor == eHighlightColor.None) + RemoveHighlight(_Highlights, c); + else + { + var color = _Highlights[c]; + RemoveHighlight(_Highlights, c); + AddHighlight(_Highlights, c, highlightColor); + } + } + else if (highlightColor != eHighlightColor.None) + { + AddHighlight(_Highlights, c, highlightColor); + } + } + + private Dictionary _TabControl2 = new Dictionary(); + private Dictionary _ParentPanel = new Dictionary(); + + private void AddHighlight(Dictionary highlights, Control c, eHighlightColor highlightColor) + { + highlights.Add(c, highlightColor); + c.LocationChanged += new EventHandler(ControlLocationChanged); + c.SizeChanged += new EventHandler(ControlSizeChanged); + c.VisibleChanged += new EventHandler(ControlVisibleChanged); + + if (_HighlightPanel is not null) + { + if (!_HighlightPanel.Visible) + _HighlightPanel.Visible = true; + _HighlightPanel.BringToFront(); + } + + if (c.Parent is null) + c.ParentChanged += ControlParentChanged; + else + { + AddTabControlHandlers(c); + } + + UpdateHighlighterRegion(); + } + + private void ControlParentChanged(object sender, EventArgs e) + { + Control c = (Control)sender; + c.ParentChanged -= ControlParentChanged; + AddTabControlHandlers(c); + } + + private void AddTabControlHandlers(Control c) + { + TabControl tab2 = GetParentControl(c, typeof(TabControl)) as TabControl; + + if (tab2 is not null) + { + + if (_TabControl2.ContainsKey(tab2)) + _TabControl2[tab2] = _TabControl2[tab2] + 1; + else + { + _TabControl2.Add(tab2, 1); + tab2.SelectedIndexChanged += WinFormsTabSelectedIndexChanged; + } + } + else + { + Panel parentPanel = GetParentControl(c, typeof(Panel)) as Panel; + + if (parentPanel is not null) + { + + if (_ParentPanel.ContainsKey(parentPanel)) + _ParentPanel[parentPanel] = _ParentPanel[parentPanel] + 1; + else + { + _ParentPanel.Add(parentPanel, 1); + parentPanel.Resize += ParentPanelResized; + parentPanel.LocationChanged += ParentPanelLocationChanged; + } + } + } + } + + private void ParentPanelLocationChanged(object sender, EventArgs e) + { + UpdateHighlights(); + } + + private void ParentPanelResized(object sender, EventArgs e) + { + UpdateHighlights(); + } + + private void WinFormsTabSelectedIndexChanged(object sender, EventArgs e) + { + UpdateHighlighterRegion(); + } + + private Control GetParentControl(Control c, Type parentType) + { + var parent = c.Parent; + + while (parent is not null) + { + if (parentType.IsAssignableFrom(parent.GetType())) + return parent; + parent = parent.Parent; + } + + return null; + } + + private void ControlVisibleChanged(object sender, EventArgs e) + { + UpdateHighlighterRegion(); + } + + private void ControlSizeChanged(object sender, EventArgs e) + { + UpdateHighlighterRegion(); + } + + private void ControlLocationChanged(object sender, EventArgs e) + { + UpdateHighlighterRegion(); + } + + private void UpdateHighlighterRegion() + { + if (_HighlightPanel is not null) + _HighlightPanel.UpdateRegion(); + } + + public void UpdateHighlights() + { + UpdateHighlighterRegion(); + } + + private void RemoveHighlight(Dictionary highlights, Control c) + { + highlights.Remove(c); + c.LocationChanged -= new EventHandler(ControlLocationChanged); + c.SizeChanged -= new EventHandler(ControlSizeChanged); + c.VisibleChanged -= new EventHandler(ControlVisibleChanged); + TabControl tab2 = GetParentControl(c, typeof(TabControl)) as TabControl; + + if (tab2 is not null) + { + + if (_TabControl2.ContainsKey(tab2)) + { + + if (_TabControl2[tab2] == 1) + { + _TabControl2.Remove(tab2); + tab2.SelectedIndexChanged -= WinFormsTabSelectedIndexChanged; + } + else + { + _TabControl2[tab2] = _TabControl2[tab2] - 1; + } + } + } + else + { + Panel parentPanel = GetParentControl(c, typeof(Panel)) as Panel; + + if (parentPanel is not null) + { + + if (_ParentPanel.ContainsKey(parentPanel)) + { + + if (_ParentPanel[parentPanel] == 1) + { + _ParentPanel.Remove(parentPanel); + parentPanel.LocationChanged -= ParentPanelLocationChanged; + parentPanel.SizeChanged -= ParentPanelResized; + } + else + { + _ParentPanel[parentPanel] = _ParentPanel[parentPanel] - 1; + } + } + } + } + + UpdateHighlighterRegion(); + } + + internal Dictionary Highlights + { + get + { + return _Highlights; + } + } + + private eHighlightColor _FocusHighlightColor = eHighlightColor.Blue; + + [DefaultValue(eHighlightColor.Blue)] + [Category("Appearance")] + [Description("Indicates the highlight focus color.")] + [Localizable(true)] + public eHighlightColor FocusHighlightColor + { + get + { + return _FocusHighlightColor; + } + set + { + _FocusHighlightColor = value; + + if (_HighlightPanel is not null) + { + _HighlightPanel.FocusHighlightColor = value; + UpdateHighlighterRegion(); + } + } + } + + private HighlightPanel _HighlightPanel = null; + private Control _ContainerControl = null; + + [Description("Indicates container control highlighter is bound to. Should be set to parent form.")] + [Category("Behavior")] + public Control ContainerControl + { + get + { + return _ContainerControl; + } + set + { + + if (DesignMode) + { + _ContainerControl = value; + return; + } + + if (!ReferenceEquals(_ContainerControl, value)) + { + + if (_ContainerControl is not null) + { + _ContainerControl.SizeChanged -= ContainerControlSizeChanged; + _ContainerControl.HandleCreated -= ContainerControlHandleCreated; + if (_HighlightPanel is not null && ReferenceEquals(_HighlightPanel.Parent, _ContainerControl)) + _ContainerControl.Controls.Remove(_HighlightPanel); + } + + _ContainerControl = value; + + if (_ContainerControl is not null) + { + + if (_HighlightPanel is null) + { + _HighlightPanel = new HighlightPanel(_Highlights); + _HighlightPanel.FocusHighlightColor = _FocusHighlightColor; + _HighlightPanel.Margin = new Padding(0); + _HighlightPanel.Padding = new Padding(0); + _HighlightPanel.CustomHighlightColors = _CustomHighlightColors; + _HighlightPanel.Visible = false; + } + + _ContainerControl.SizeChanged += ContainerControlSizeChanged; + _ContainerControl.HandleCreated += ContainerControlHandleCreated; + _ContainerControl.Controls.Add(_HighlightPanel); + UpdateHighlightPanelBounds(); + } + } + } + } + + private void ContainerControlHandleCreated(object sender, EventArgs e) + { + if (_Highlights.Count > 0 && _HighlightPanel is not null && !_HighlightPanel.Visible) + _HighlightPanel.Visible = true; + } + + private void UpdateHighlightPanelBounds() + { + var bounds = new Rectangle(0, 0, _ContainerControl.ClientRectangle.Width, _ContainerControl.ClientRectangle.Height); + + if (_HighlightPanel is not null) + { + if (_HighlightPanel.Parent is Form) + { + Form form = _HighlightPanel.Parent as Form; + + if (form.AutoSize) + { + bounds.X += form.Padding.Left; + bounds.Y += form.Padding.Top; + bounds.Width -= form.Padding.Horizontal; + bounds.Height -= form.Padding.Vertical; + } + } + + if (_HighlightPanel.Bounds.Equals(bounds)) + _HighlightPanel.UpdateRegion(); + else + { + _HighlightPanel.Bounds = bounds; + } + + _HighlightPanel.BringToFront(); + } + } + + private Timer _DelayTimer = null; + + private void ContainerControlSizeChanged(object sender, EventArgs e) + { + Form form = sender as Form; + + if (form is not null) + { + + if (_DelayTimer is null) + { + _DelayTimer = new Timer(); + _DelayTimer.Interval = 100; + _DelayTimer.Tick += new EventHandler(DelayTimerTick); + _DelayTimer.Start(); + } + + return; + } + + UpdateHighlightPanelBounds(); + } + + private void DelayTimerTick(object sender, EventArgs e) + { + var timer = _DelayTimer; + _DelayTimer = null; + timer.Tick -= new EventHandler(DelayTimerTick); + timer.Stop(); + timer.Dispose(); + UpdateHighlightPanelBounds(); + } + + private Color[] _CustomHighlightColors = null; + + [Category("Appearance")] + [Description("Array of colors used to render custom highlight color. Control expects 3 colors in array to be specified which define the highlight border.")] + public Color[] CustomHighlightColors + { + get + { + return _CustomHighlightColors; + } + set + { + _CustomHighlightColors = value; + + if (_HighlightPanel is not null) + { + _HighlightPanel.CustomHighlightColors = _CustomHighlightColors; + _HighlightPanel.Invalidate(); + } + } + } + +} + +public enum eHighlightColor +{ + None, + Red, + Blue, + Green, + Orange, + Custom +} \ No newline at end of file diff --git a/Pilz.UI/My Project/Application.Designer.vb b/Pilz.UI/My Project/Application.Designer.vb deleted file mode 100644 index 8ab460b..0000000 --- a/Pilz.UI/My Project/Application.Designer.vb +++ /dev/null @@ -1,13 +0,0 @@ -'------------------------------------------------------------------------------ -' -' Dieser Code wurde von einem Tool generiert. -' Laufzeitversion:4.0.30319.42000 -' -' Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn -' der Code erneut generiert wird. -' -'------------------------------------------------------------------------------ - -Option Strict On -Option Explicit On - diff --git a/Pilz.UI/My Project/Application.myapp b/Pilz.UI/My Project/Application.myapp deleted file mode 100644 index 758895d..0000000 --- a/Pilz.UI/My Project/Application.myapp +++ /dev/null @@ -1,10 +0,0 @@ - - - false - false - 0 - true - 0 - 1 - true - diff --git a/Pilz.UI/My Project/Resources.Designer.vb b/Pilz.UI/My Project/Resources.Designer.vb deleted file mode 100644 index 22d4a15..0000000 --- a/Pilz.UI/My Project/Resources.Designer.vb +++ /dev/null @@ -1,63 +0,0 @@ -'------------------------------------------------------------------------------ -' -' Dieser Code wurde von einem Tool generiert. -' Laufzeitversion:4.0.30319.42000 -' -' Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn -' der Code erneut generiert wird. -' -'------------------------------------------------------------------------------ - -Option Strict On -Option Explicit On - -Imports System - -Namespace My.Resources - - 'Diese Klasse wurde von der StronglyTypedResourceBuilder automatisch generiert - '-Klasse über ein Tool wie ResGen oder Visual Studio automatisch generiert. - 'Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen - 'mit der /str-Option erneut aus, oder Sie erstellen Ihr VS-Projekt neu. - ''' - ''' Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw. - ''' - _ - Friend Module Resources - - Private resourceMan As Global.System.Resources.ResourceManager - - Private resourceCulture As Global.System.Globalization.CultureInfo - - ''' - ''' Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird. - ''' - _ - Friend ReadOnly Property ResourceManager() As Global.System.Resources.ResourceManager - Get - If Object.ReferenceEquals(resourceMan, Nothing) Then - Dim temp As Global.System.Resources.ResourceManager = New Global.System.Resources.ResourceManager("Pilz.UI.Resources", GetType(Resources).Assembly) - resourceMan = temp - End If - Return resourceMan - End Get - End Property - - ''' - ''' Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle - ''' Ressourcenzuordnungen, die diese stark typisierte Ressourcenklasse verwenden. - ''' - _ - Friend Property Culture() As Global.System.Globalization.CultureInfo - Get - Return resourceCulture - End Get - Set - resourceCulture = value - End Set - End Property - End Module -End Namespace diff --git a/Pilz.UI/My Project/Settings.Designer.vb b/Pilz.UI/My Project/Settings.Designer.vb deleted file mode 100644 index d282691..0000000 --- a/Pilz.UI/My Project/Settings.Designer.vb +++ /dev/null @@ -1,73 +0,0 @@ -'------------------------------------------------------------------------------ -' -' Dieser Code wurde von einem Tool generiert. -' Laufzeitversion:4.0.30319.42000 -' -' Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn -' der Code erneut generiert wird. -' -'------------------------------------------------------------------------------ - -Option Strict On -Option Explicit On - - -Namespace My - - _ - Partial Friend NotInheritable Class MySettings - Inherits Global.System.Configuration.ApplicationSettingsBase - - Private Shared defaultInstance As MySettings = CType(Global.System.Configuration.ApplicationSettingsBase.Synchronized(New MySettings()),MySettings) - -#Region "Automatische My.Settings-Speicherfunktion" -#If _MyType = "WindowsForms" Then - Private Shared addedHandler As Boolean - - Private Shared addedHandlerLockObject As New Object - - _ - Private Shared Sub AutoSaveSettings(sender As Global.System.Object, e As Global.System.EventArgs) - If My.Application.SaveMySettingsOnExit Then - My.Settings.Save() - End If - End Sub -#End If -#End Region - - Public Shared ReadOnly Property [Default]() As MySettings - Get - -#If _MyType = "WindowsForms" Then - If Not addedHandler Then - SyncLock addedHandlerLockObject - If Not addedHandler Then - AddHandler My.Application.Shutdown, AddressOf AutoSaveSettings - addedHandler = True - End If - End SyncLock - End If -#End If - Return defaultInstance - End Get - End Property - End Class -End Namespace - -Namespace My - - _ - Friend Module MySettingsProperty - - _ - Friend ReadOnly Property Settings() As Global.Pilz.UI.My.MySettings - Get - Return Global.Pilz.UI.My.MySettings.Default - End Get - End Property - End Module -End Namespace diff --git a/Pilz.UI/My Project/Settings.settings b/Pilz.UI/My Project/Settings.settings deleted file mode 100644 index 85b890b..0000000 --- a/Pilz.UI/My Project/Settings.settings +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/Pilz.UI/PaintingControl/ArrowLineCapProps.cs b/Pilz.UI/PaintingControl/ArrowLineCapProps.cs new file mode 100644 index 0000000..caee79f --- /dev/null +++ b/Pilz.UI/PaintingControl/ArrowLineCapProps.cs @@ -0,0 +1,18 @@ +using System.Drawing; +using System.Drawing.Drawing2D; + +namespace Pilz.UI; + +public class ArrowLineCapProps : LineCapProps +{ + + public Size Size { get; set; } = new Size(10, 10); + public bool IsFilles { get; set; } = true; + + internal override LineCapConfigurationArgs Configure() + { + var cap = new AdjustableArrowCap(Size.Width, Size.Height, IsFilles); + return new LineCapConfigurationArgs(cap); + } + +} \ No newline at end of file diff --git a/Pilz.UI/PaintingControl/DefaultDrawMethodes.cs b/Pilz.UI/PaintingControl/DefaultDrawMethodes.cs new file mode 100644 index 0000000..df9a0e7 --- /dev/null +++ b/Pilz.UI/PaintingControl/DefaultDrawMethodes.cs @@ -0,0 +1,284 @@ +using Pilz.Drawing; +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace Pilz.UI; + + +/// +/// Contains static methods that are used for the standart PaintingObject Types. +/// +public class DefaultDrawMethodes +{ + + public static void DrawText(PaintingObjectPaintEventArgs e) + { + var obj = e.PaintingObject; + var b = new SolidBrush(obj.TextColor); + var p = new PointF(); + var rect = new Rectangle((int)Math.Round(e.X), (int)Math.Round(e.Y), (int)Math.Round(obj.Width), (int)Math.Round(obj.Height)); + + var f = StringFormat.GenericDefault; + f.Alignment = obj.HorizontalTextAlignment; + f.LineAlignment = obj.VerticalTextAlignment; + + float zoomFactor; + if (obj.Parent is null) + zoomFactor = 1.0f; + else + { + zoomFactor = obj.Parent.ZoomFactor.Width; + } + + e.Graphics.DrawString(obj.Text, new Font(obj.TextFont.FontFamily, obj.TextFont.Size * zoomFactor, obj.TextFont.Style), b, rect, f); + } + private static object _DrawPicture_newSyncObj = new object(); + + public static void DrawPicture(PaintingObjectPaintEventArgs e) + { + var obj = e.PaintingObject; + Image objImg; + Size objImgSize; + RectangleF result; + Bitmap image; + SizeF zoomf; + bool hasNoParent = e.PaintingObject.Parent is null; + object syncObj; + + if (hasNoParent) + { + zoomf = new SizeF(1f, 1f); + syncObj = _DrawPicture_newSyncObj; + } + else + { + zoomf = e.PaintingObject.Parent.ZoomFactor; + syncObj = e.PaintingObject.Parent; + } + + lock (syncObj) + { + if (obj?.Image is null) + return; + objImg = obj.Image; + objImgSize = objImg.Size; + } + + image = (Bitmap)obj.BufferedImage; + result = CalculateImageResolution(obj, objImgSize, zoomf); + + if (obj.ImageProperties.Rotate == 90 || obj.ImageProperties.Rotate == 270) + result = CalculateImageResolution(obj, new SizeF(objImgSize.Height, objImgSize.Width), zoomf); + + if (image is null) + { + bool needRescaleImageBecauseRot = false; + + image = DrawToNewImage((Bitmap)objImg, result.Size); + + switch (obj.ImageProperties.Rotate) + { + case 90: + { + image.RotateFlip(RotateFlipType.Rotate90FlipNone); + needRescaleImageBecauseRot = true; + break; + } + case 180: + { + image.RotateFlip(RotateFlipType.Rotate180FlipNone); + break; + } + case 270: + { + image.RotateFlip(RotateFlipType.Rotate270FlipNone); + needRescaleImageBecauseRot = true; + break; + } + } + if (obj.ImageProperties.FlipX) + image.RotateFlip(RotateFlipType.RotateNoneFlipX); + if (obj.ImageProperties.FlipY) + image.RotateFlip(RotateFlipType.RotateNoneFlipY); + + if (needRescaleImageBecauseRot) + { + result = CalculateImageResolution(obj, new SizeF(objImgSize.Height, objImgSize.Width), zoomf); + image = DrawToNewImage(image, result.Size); + } + + obj.BufferedImage = image; + } + + if (image is not null) + { + lock (syncObj) + e.Graphics.DrawImageUnscaled(image, new Rectangle(new Point((int)Math.Round(obj.Location.X + result.Location.X - e.Offset.X), (int)Math.Round(obj.Location.Y + result.Location.Y - e.Offset.Y)), result.Size.ToSize())); + } + } + + private static Bitmap DrawToNewImage(Bitmap image, SizeF newSize) + { + var bmp = new Bitmap((int)Math.Round(newSize.Width < 0f ? newSize.Width * -1 : newSize.Width), (int)Math.Round(newSize.Height < 0f ? newSize.Height * -1 : newSize.Height)); + var gimage = Graphics.FromImage(bmp); + gimage.SmoothingMode = SmoothingMode.HighQuality; + gimage.PixelOffsetMode = PixelOffsetMode.HighQuality; + gimage.PageUnit = GraphicsUnit.Pixel; + gimage.InterpolationMode = InterpolationMode.HighQualityBicubic; + gimage.DrawImage(image, new RectangleF(PointF.Empty, newSize)); + gimage.Dispose(); + return bmp; + } + + public static void DrawLine(PaintingObjectPaintEventArgs e) + { + var obj = e.PaintingObject; + var p2 = new Pen(obj.OutlineColor, obj.OutlineThicknes) { DashStyle = obj.OutlineDashStyle }; + + if (obj.LineEndCap is not null) + { + var args = obj.LineEndCap.Configure(); + p2.StartCap = args.LineCap; + p2.CustomStartCap = args.CustomLineCap; + } + + if (obj.LineStartCap is not null) + { + var args = obj.LineStartCap.Configure(); + p2.EndCap = args.LineCap; + p2.CustomEndCap = args.CustomLineCap; + } + + p2.Alignment = PenAlignment.Center; + var no = new PointF(e.X, e.Y); + e.Graphics.DrawLine(p2, no, no + obj.Size); + } + + private static RectangleF CalculateImageResolution(PaintingObject obj, SizeF imageSize, SizeF zoom) + { + var result = new RectangleF(); + var objrect = new RectangleF(obj.Location, obj.Size); + var size = new SizeF(imageSize.Width * zoom.Width, imageSize.Height * zoom.Height); + var clientRectangle = objrect; + float val = clientRectangle.Width / size.Width; + + clientRectangle = objrect; + float num = Math.Min(val, clientRectangle.Height / size.Height); + + result.Width = (int)Math.Round(Math.Truncate((double)(size.Width * num))); + result.Height = (int)Math.Round(Math.Truncate((double)(size.Height * num))); + + clientRectangle = objrect; + result.X = (long)Math.Round(clientRectangle.Width - result.Width) / 2L; + + clientRectangle = objrect; + result.Y = (long)Math.Round(clientRectangle.Height - result.Height) / 2L; + + return result; + } + + public static void DrawTriangle(PaintingObjectPaintEventArgs e) + { + var obj = e.PaintingObject; + + var p1 = new Point((int)Math.Round(obj.Size.Width / 2f + e.X), (int)Math.Round(e.Y)); + var p2 = new Point((int)Math.Round(e.X), (int)Math.Round(e.Y + obj.Size.Height)); + var p3 = new Point((int)Math.Round(e.X + obj.Size.Width), (int)Math.Round(e.Y + obj.Size.Height)); + + if (obj.EnableFill) + { + var b = new SolidBrush(obj.FillColor); + e.Graphics.FillPolygon(b, new[] { p1, p2, p3 }); + } + + if (obj.EnableOutline) + { + float lw = obj.OutlineThicknes; + var p = new Pen(obj.OutlineColor, obj.OutlineThicknes) { DashStyle = obj.OutlineDashStyle, Alignment = PenAlignment.Inset }; + e.Graphics.DrawPolygon(p, new[] { p1, p2, p3 }); + } + } + + public static void DrawRectangle(PaintingObjectPaintEventArgs e) + { + var obj = e.PaintingObject; + float hol = obj.OutlineThicknes / 2f; + + if (obj.EnableFill) + { + var b = new SolidBrush(obj.FillColor); + var rect = obj.EnableOutline ? new Rectangle((int)Math.Round(e.X + hol), (int)Math.Round(e.Y + hol), (int)Math.Round(obj.Size.Width - hol * 2f), (int)Math.Round(obj.Size.Height - hol * 2f)) : new Rectangle((int)Math.Round(e.X), (int)Math.Round(e.Y), (int)Math.Round(obj.Size.Width), (int)Math.Round(obj.Size.Height)); + e.Graphics.FillRectangle(b, rect); + } + + if (obj.EnableOutline) + { + var p = new Pen(obj.OutlineColor, obj.OutlineThicknes) { DashStyle = obj.OutlineDashStyle, Alignment = PenAlignment.Inset }; + var rect = new Rectangle((int)Math.Round(e.X), (int)Math.Round(e.Y), (int)Math.Round(obj.Size.Width), (int)Math.Round(obj.Size.Height)); + e.Graphics.DrawRectangle(p, rect); + } + } + + public static void DrawEllipse(PaintingObjectPaintEventArgs e) + { + var obj = e.PaintingObject; + + if (obj.EnableFill) + { + var b = new SolidBrush(obj.FillColor); + var rect = new Rectangle((int)Math.Round(e.X), (int)Math.Round(e.Y), (int)Math.Round(obj.Size.Width), (int)Math.Round(obj.Size.Height)); + e.Graphics.FillEllipse(b, rect); + } + + if (obj.EnableOutline) + { + var p = new Pen(obj.OutlineColor, obj.OutlineThicknes) { DashStyle = obj.OutlineDashStyle, Alignment = PenAlignment.Inset }; + var rect = new Rectangle((int)Math.Round(e.X), (int)Math.Round(e.Y), (int)Math.Round(obj.Size.Width), (int)Math.Round(obj.Size.Height)); + e.Graphics.DrawEllipse(p, rect); + } + } + + public static void DrawSelection(PaintingObjectPaintEventArgs e) + { + var obj = e.PaintingObject; + float lw = 2.5f; + float hlw = lw / 2f; + float hlwphol = hlw; // + hol + float hlwpholm2 = hlwphol * 2f; + + var p = new Pen(Color.CornflowerBlue, lw) { DashStyle = obj.SelectionDashStyle, Alignment = PenAlignment.Outset }; + var rect = new Rectangle((int)Math.Round(e.X - hlwphol), (int)Math.Round(e.Y - hlwphol), (int)Math.Round(obj.Size.Width + hlwpholm2), (int)Math.Round(obj.Size.Height + hlwpholm2)); + e.Graphics.DrawRectangle(p, rect); + } + + public static void DrawGrid(PaintEventArgs e, PaintingControl pc, PointF offset) + { + var p = new Pen(pc.GridColor, 0.5f); + + int curX = (int)Math.Round(pc.GridChunkSize.Width * pc.ZoomFactor.Width - offset.X); + while (curX < pc.Width) + { + e.Graphics.DrawLine(p, curX, -offset.Y, curX, pc.Height); + curX = (int)Math.Round(curX + pc.GridChunkSize.Width * pc.ZoomFactor.Width); + } + + int curY = (int)Math.Round(pc.GridChunkSize.Height * pc.ZoomFactor.Height - offset.Y); + while (curY < pc.Height) + { + e.Graphics.DrawLine(p, -offset.X, curY, pc.Width, curY); + curY = (int)Math.Round(curY + pc.GridChunkSize.Height * pc.ZoomFactor.Height); + } + } + + public static void DrawAreaSelection(PaintEventArgs e, PaintingControl pc, PointF startMousePos, PointF lastMousePos) + { + var rectToDraw = HelpfulDrawingFunctions.GetRectangle(startMousePos, lastMousePos); + var p = new Pen(pc.AreaSelectionColor); + p.DashStyle = startMousePos.X >= lastMousePos.X ? DashStyle.DashDot : DashStyle.Solid; + p.Width = 3f; + e.Graphics.DrawRectangle(p, rectToDraw.X, rectToDraw.Y, rectToDraw.Width, rectToDraw.Height); + } + +} \ No newline at end of file diff --git a/Pilz.UI/PaintingControl/DefaultLineCapProps.cs b/Pilz.UI/PaintingControl/DefaultLineCapProps.cs new file mode 100644 index 0000000..59c8ed1 --- /dev/null +++ b/Pilz.UI/PaintingControl/DefaultLineCapProps.cs @@ -0,0 +1,17 @@ +using System.Drawing.Drawing2D; + +namespace Pilz.UI; + + +public class DefaultLineCapProps : LineCapProps +{ + + public LineCap LineCap { get; set; } = LineCap.Flat; + public CustomLineCap CustomLineCap { get; set; } = null; + + internal override LineCapConfigurationArgs Configure() + { + return new LineCapConfigurationArgs(LineCap, CustomLineCap); + } + +} \ No newline at end of file diff --git a/Pilz.UI/PaintingControl/EventArgs/PaintingObjectEventArgs.cs b/Pilz.UI/PaintingControl/EventArgs/PaintingObjectEventArgs.cs new file mode 100644 index 0000000..b0c0a39 --- /dev/null +++ b/Pilz.UI/PaintingControl/EventArgs/PaintingObjectEventArgs.cs @@ -0,0 +1,8 @@ +using System; + +namespace Pilz.UI; + +public class PaintingObjectEventArgs(PaintingObject[] paintingObjects) : EventArgs +{ + public PaintingObject[] PaintingObjects { get; private set; } = paintingObjects; +} \ No newline at end of file diff --git a/Pilz.UI/PaintingControl/EventArgs/PaintingObjectPaintEventArgs.cs b/Pilz.UI/PaintingControl/EventArgs/PaintingObjectPaintEventArgs.cs new file mode 100644 index 0000000..4d8b665 --- /dev/null +++ b/Pilz.UI/PaintingControl/EventArgs/PaintingObjectPaintEventArgs.cs @@ -0,0 +1,53 @@ +using System; +using System.Drawing; + +namespace Pilz.UI; + +public class PaintingObjectPaintEventArgs(PaintingObject obj, Graphics g, PointF offset) : EventArgs +{ + /// + /// The Painting Object to draw. + /// + /// + public PaintingObject PaintingObject { get; } = obj; + + /// + /// The current offset of the page on the screen. + /// + /// + public PointF Offset { get; } = offset; + + /// + /// The Grpahics from the parent PaintingControl. + /// + /// + public Graphics Graphics { get; } = g; + + /// + /// The position of the PaintingObject on Screen. + /// + /// + public PointF Location => new(X, Y); + + /// + /// The X position of the PaintingObject on Screen. + /// + /// + public float X => PaintingObject.X - Offset.X; + + /// + /// The Y position of the PaintingObject on Screen. + /// + /// + public float Y => PaintingObject.Y - Offset.Y; + + /// + /// The rectangle of the PaintingObject on Screen. + /// + /// + public RectangleF Rectangle => new RectangleF(X, Y, PaintingObject.Width, PaintingObject.Height); + + public PaintingObjectPaintEventArgs(PaintingObject obj, Graphics g) : this(obj, g, obj.Parent.Offset) + { + } +} \ No newline at end of file diff --git a/Pilz.UI/PaintingControl/IPaintingObjectContainer.cs b/Pilz.UI/PaintingControl/IPaintingObjectContainer.cs new file mode 100644 index 0000000..50a7fce --- /dev/null +++ b/Pilz.UI/PaintingControl/IPaintingObjectContainer.cs @@ -0,0 +1,7 @@ + +namespace Pilz.UI; + +public interface IPaintingObjectContainer +{ + +} \ No newline at end of file diff --git a/Pilz.UI/PaintingControl/ImageProperties.cs b/Pilz.UI/PaintingControl/ImageProperties.cs new file mode 100644 index 0000000..c92c615 --- /dev/null +++ b/Pilz.UI/PaintingControl/ImageProperties.cs @@ -0,0 +1,9 @@ + +namespace Pilz.UI; + +public class PaintingObjectImageProperties +{ + public bool FlipY { get; set; } = false; + public bool FlipX { get; set; } = false; + public ushort Rotate { get; set; } = 0; +} \ No newline at end of file diff --git a/Pilz.UI/PaintingControl/LineCapConfigurationArgs.cs b/Pilz.UI/PaintingControl/LineCapConfigurationArgs.cs new file mode 100644 index 0000000..7b7eef6 --- /dev/null +++ b/Pilz.UI/PaintingControl/LineCapConfigurationArgs.cs @@ -0,0 +1,26 @@ +using System.Drawing.Drawing2D; + +namespace Pilz.UI; + + +public class LineCapConfigurationArgs +{ + + public LineCap LineCap { get; private set; } + public CustomLineCap CustomLineCap { get; private set; } + + public LineCapConfigurationArgs(LineCap lineCap) : this(lineCap, null) + { + } + + public LineCapConfigurationArgs(CustomLineCap customLineCap) : this(default, customLineCap) + { + } + + public LineCapConfigurationArgs(LineCap lineCap, CustomLineCap customLineCap) + { + LineCap = lineCap; + CustomLineCap = customLineCap; + } + +} \ No newline at end of file diff --git a/Pilz.UI/PaintingControl/LineCapProps.cs b/Pilz.UI/PaintingControl/LineCapProps.cs new file mode 100644 index 0000000..58045b0 --- /dev/null +++ b/Pilz.UI/PaintingControl/LineCapProps.cs @@ -0,0 +1,9 @@ + +namespace Pilz.UI; + +public abstract class LineCapProps +{ + + internal abstract LineCapConfigurationArgs Configure(); + +} \ No newline at end of file diff --git a/Pilz.UI/PaintingControl/PaintingControl.cs b/Pilz.UI/PaintingControl/PaintingControl.cs new file mode 100644 index 0000000..caf31c7 --- /dev/null +++ b/Pilz.UI/PaintingControl/PaintingControl.cs @@ -0,0 +1,785 @@ +using Microsoft.VisualBasic.CompilerServices; +using Pilz.Drawing; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Linq; +using System.Windows.Forms; + +namespace Pilz.UI; + + +public class PaintingControl : UserControl, IPaintingObjectContainer +{ + + private PaintingObject curObjMouseDown = null; + private Color bgColor = Color.White; + private Point startMousePos = default; + private Point lastMousePos = default; + private int lastHashCode = 0; + private Point calcOffset_MouseOnTab = Point.Empty; + private bool calcOffset_IsActive = false; + private PointF calcOffset_LastOffset = PointF.Empty; + + private new Color ForeColor { get; set; } + private new Font Font { get; set; } + private new string Text { get; set; } + + public PointF Offset { get; set; } = PointF.Empty; + public PaintingObjectList PaintingObjects { get; private set; } = new PaintingObjectList(this); + public bool VisibleForMouseEvents { get; set; } = true; + public bool AutoAreaSelection { get; set; } = true; + public bool AutoSingleSelection { get; set; } = true; + public bool AutoMultiselection { get; set; } = true; + public bool AutoRemoveSelection { get; set; } = true; + public DashStyle AreaSelectionDashStyle { get; set; } = DashStyle.DashDot; + public Color AreaSelectionColor { get; set; } = Color.CornflowerBlue; + public bool AutoMoveObjects { get; set; } = true; + private bool _IsAreaSelecting = false; + public bool IsMovingObjects { get; private set; } = false; + public bool GridEnabled { get; set; } = true; + public bool GridVisible { get; set; } = false; + public Size GridChunkSize { get; set; } = new Size(20, 20); + public Color GridColor { get; set; } = Color.LightGray; + public DelegateDrawPaintingControlGridMethode DrawGridMethode { get; set; } = DefaultDrawMethodes.DrawGrid; + public DelegateDrawPaintingControlAreaSelectionMethode DrawAreaSelectionMethode { get; set; } = DefaultDrawMethodes.DrawAreaSelection; + private SizeF _ZoomFactor = new SizeF(1f, 1f); + + private int _stopDrawing = -1; + private Image bufferedImg = null; + + private bool pressedShift = false; + private bool pressedControl = false; + private bool pressedAlt = false; + + private Dictionary savedPos = new Dictionary(); + + public event SelectionChangedEventHandler SelectionChanged; + + public delegate void SelectionChangedEventHandler(object sender, PaintingObjectEventArgs e); + public event PaintingObjectAddedEventHandler PaintingObjectAdded; + + public delegate void PaintingObjectAddedEventHandler(object sender, PaintingObjectEventArgs e); + public event PaintingObjectRemovedEventHandler PaintingObjectRemoved; + + public delegate void PaintingObjectRemovedEventHandler(object sender, PaintingObjectEventArgs e); + public event AfterScrollingDoneEventHandler AfterScrollingDone; + + public delegate void AfterScrollingDoneEventHandler(object sender, EventArgs e); + public event ZoomFactorChangedEventHandler ZoomFactorChanged; + + public delegate void ZoomFactorChangedEventHandler(object sender, EventArgs e); + + public PaintingObject[] SelectedObjects + { + get + { + var objs = new List(); + + foreach (PaintingObject obj in PaintingObjects) + { + if (obj.Selected) + objs.Add(obj); + } + + return objs.ToArray(); + } + } + + public bool IsLayoutSuspended + { + get + { + return Conversions.ToInteger(GetType().GetField("layoutSuspendCount", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(this)) != 0; + } + } + + public bool StopDrawing + { + get + { + return _stopDrawing > -1; + } + } + + public override Color BackColor + { + get + { + return bgColor; + } + set + { + bgColor = value; + base.BackColor = value; + // If value <> Color.Transparent Then + // MyBase.BackColor = value + // End If + } + } + public bool IsAreaSelecting + { + get + { + return _IsAreaSelecting && startMousePos != lastMousePos; + } + } + public SizeF ZoomFactor + { + get + { + return _ZoomFactor; + } + set + { + if (_ZoomFactor != value) + { + _ZoomFactor = value; + ResetAllBufferedImages(); + ZoomFactorChanged?.Invoke(this, new EventArgs()); + } + } + } + + public PaintingControl() + { + PaintingObjectResizing.CheckEnabled += PaintingObjectResizing_CheckEnabled; + DoubleBuffered = true; + KeyDown += CheckKeyDown; + KeyUp += CheckKeyDown; + MouseClick += CheckMouseClick; + MouseDown += CheckMouseDown; + MouseUp += CheckMouseUp; + MouseMove += CheckMouseMove; + PaintingObjectAdded += PaintingControl_PaintingObjectAdded; + PaintingObjectRemoved += PaintingControl_PaintingObjectAdded; + MouseWheel += CheckMouseWheel; + } + + ~PaintingControl() + { + PaintingObjectResizing.CheckEnabled -= PaintingObjectResizing_CheckEnabled; + } + + private void PaintingObjectResizing_CheckEnabled(PaintingObjectResizing sender, ref bool enabled) + { + if (PaintingObjects.Contains(sender.PaintingObject) && pressedAlt) + enabled = false; + } + + private void ResetAllBufferedImages() + { + foreach (PaintingObject ob in PaintingObjects) + ob.ResetImageBuffer(); + Refresh(); + } + + protected void CheckKeyDown(object sender, KeyEventArgs e) + { + pressedShift = e.Shift; + pressedControl = e.Control; + pressedAlt = e.Alt; + } + + internal RectangleF AreaSelectionRectangle + { + get + { + return HelpfulDrawingFunctions.GetRectangle(startMousePos, lastMousePos); + } + } + + protected void CheckMouseClick(object sender, MouseEventArgs e) + { + foreach (PaintingObject obj in GetObjects(new Point((int)Math.Round(e.X + Offset.X), (int)Math.Round(e.Y + Offset.Y)))) + { + if (!obj.MouseTransparency) + obj.RaiseMouseClick(GetMouseEventArgs(e, obj)); + } + } + + protected void CheckMouseDown(object sender, MouseEventArgs e) + { + lastMousePos = new Point((int)Math.Round(e.X + Offset.X), (int)Math.Round(e.Y + Offset.Y)); + + curObjMouseDown = GetObjects(lastMousePos).Where(n => !n.MouseTransparency).LastOrDefault(); + curObjMouseDown?.RaiseMouseDown(GetMouseEventArgs(e, curObjMouseDown)); + + if (curObjMouseDown is null || !curObjMouseDown.Selected || pressedControl) + { + bool hasMovedObjects = false; + if (IsMovingObjects) + { + foreach (PaintingObject obj in GetSelectedObjects()) + { + if (HelpfulDrawingFunctions.IsPointInRectangle(lastMousePos, obj.Rectangle)) + { + hasMovedObjects = true; + break; + } + } + } + + if (!hasMovedObjects && !_IsAreaSelecting) + { + var selChanged = new List(); + + if (AutoRemoveSelection && !pressedControl) + { + foreach (PaintingObject obj in PaintingObjects) + { + if (obj.Selected) + { + obj.SelectedDirect = false; + if (!selChanged.Contains(obj)) + selChanged.Add(obj); + } + } + } + + if (AutoSingleSelection && curObjMouseDown is not null) + { + var objtosel = curObjMouseDown; + if (objtosel.EnableSelection) + { + objtosel.SelectedDirect = !objtosel.Selected; + if (!selChanged.Contains(objtosel)) + selChanged.Add(objtosel); + else + { + selChanged.Remove(objtosel); + } + } + } + + SelectionChanged?.Invoke(this, new PaintingObjectEventArgs(selChanged.ToArray())); + } + } + + if (pressedAlt) + { + + calcOffset_MouseOnTab = new Point(e.X, e.Y); + calcOffset_LastOffset = Offset; + calcOffset_IsActive = true; + Cursor = Cursors.Arrow; + } + + else + { + + switch (e.Button) + { + case MouseButtons.Left: + { + savedPos.Clear(); + if (AutoMoveObjects) + SaveObjectPositions(e, GetSelectedObjects()); + if (savedPos.Count > 0) + IsMovingObjects = true; + else if (AutoAreaSelection) + { + startMousePos = new Point((int)Math.Round(e.X + Offset.X), (int)Math.Round(e.Y + Offset.Y)); + lastMousePos = startMousePos; // New Point(e.X - Offset.X, e.Y - Offset.Y) + _IsAreaSelecting = true; + } + + break; + } + } + + } + } + public void RaiseSelectionChanged() + { + SelectionChanged?.Invoke(this, new PaintingObjectEventArgs(SelectedObjects)); + } + protected void CheckMouseUp(object sender, MouseEventArgs e) + { + if (_IsAreaSelecting) + _IsAreaSelecting = false; + + if (IsMovingObjects) + { + IsMovingObjects = false; + foreach (PaintingObject obj in GetSelectedObjects()) + obj.RaiseMoved(new EventArgs()); + AutoArrangeToGrid(); + } + + if (curObjMouseDown is not null) + { + if (!curObjMouseDown.MouseTransparency) + curObjMouseDown.RaiseMouseUp(GetMouseEventArgs(e, curObjMouseDown)); + curObjMouseDown = null; + } + + if (calcOffset_IsActive) + { + calcOffset_IsActive = false; + Cursor = Cursors.Default; + CalcNewOffset(e.Location); + AfterScrollingDone?.Invoke(this, new EventArgs()); + } + } + protected void CheckMouseMove(object sender, MouseEventArgs e) + { + if (_IsAreaSelecting || IsMovingObjects) + lastMousePos = new Point((int)Math.Round(e.X + Offset.X), (int)Math.Round(e.Y + Offset.Y)); + + if (_IsAreaSelecting) + SelectControlsInArea(); + + if (IsMovingObjects) + UpdateObjectPositions(e); + + foreach (PaintingObject obj in GetObjects(new Point((int)Math.Round(e.X + Offset.X), (int)Math.Round(e.Y + Offset.Y)))) + { + if (!obj.MouseTransparency) + obj.RaiseMouseMove(GetMouseEventArgs(e, obj)); + } + + var topObj = GetObject(new Point((int)Math.Round(e.X + Offset.X), (int)Math.Round(e.Y + Offset.Y)), true); + if (topObj is not null) + Cursor = topObj.Cursor; + else if (calcOffset_IsActive) + { + Cursor = Cursors.Arrow; + } + else + { + Cursor = Cursors.Default; + } + + if (calcOffset_IsActive) + { + if (pressedAlt) + CalcNewOffset(e.Location); + else + { + calcOffset_IsActive = false; + } + } + + Refresh(); + } + + private void CalcNewOffset(Point newMousePos) + { + Offset = new PointF(calcOffset_LastOffset.X - (newMousePos.X - calcOffset_MouseOnTab.X), calcOffset_LastOffset.Y - (newMousePos.Y - calcOffset_MouseOnTab.Y)); + if (Offset.X < 0f) + Offset = new PointF(0f, Offset.Y); + if (Offset.Y < 0f) + Offset = new PointF(Offset.X, 0f); + } + + private PaintingObject[] GetSelectedObjects() + { + var objs = new List(); + + foreach (PaintingObject obj in PaintingObjects) + { + if (obj.Selected) + objs.Add(obj); + } + + return objs.ToArray(); + } + + private void SaveObjectPositions(MouseEventArgs e, IList objs) + { + foreach (PaintingObject obj in objs) + { + if (!obj.HardcodedLocation && !savedPos.ContainsKey(obj)) + { + savedPos.Add(obj, new PointF(e.X - obj.Location.X + Offset.X, e.Y - obj.Location.Y + Offset.Y)); + SaveObjectPositions(e, obj.PinnedObjects); + } + } + } + + private void UpdateObjectPositions(MouseEventArgs e) + { + UpdateObjectPositions(e, GetSelectedObjects()); + } + + private void UpdateObjectPositions(MouseEventArgs e, IList objs, List movedObjs = null) + { + if (IsResizingObjs(objs)) + return; + if (movedObjs is null) + movedObjs = new List(); + + SuspendDrawing(); + + foreach (PaintingObject obj in objs) + { + var sp = savedPos[obj]; + + if (!movedObjs.Contains(obj)) + { + if (UpdateObjectPosition(e, obj, sp)) + movedObjs.Add(obj); + } + + if (obj.PinnedObjects.Count > 0) + { + UpdateObjectPositions(e, obj.PinnedObjects, movedObjs); + movedObjs.AddRange(obj.PinnedObjects.ToArray()); + } + } + + ResumeDrawing(false); + } + + private bool UpdateObjectPosition(MouseEventArgs e, PaintingObject obj, PointF sp) + { + bool moved = false; + var cancel = new CancelEventArgs(false); + obj.RaiseMovingBeforePositionUpdated(cancel); + + if (!cancel.Cancel) + { + obj.Location = new Point((int)Math.Round(e.X - sp.X + Offset.X), (int)Math.Round(e.Y - sp.Y + Offset.Y)); + obj.RaiseMoving(new EventArgs()); + moved = true; + } + + return moved; + } + + private bool IsResizingObjs(IList objs) + { + foreach (PaintingObject obj in objs) + { + if (obj.IsResizing) + return true; + } + return false; + } + + private MouseEventArgs GetMouseEventArgs(MouseEventArgs e, PaintingObject obj) + { + return new MouseEventArgs(e.Button, e.Clicks, (int)Math.Round(e.X - obj.X + Offset.X), (int)Math.Round(e.Y - obj.Y + Offset.Y), e.Delta); + } + + public PaintingObject GetObject(PointF p, bool UseExtRect = false) + { + PaintingObject val = null; + + for (int i = PaintingObjects.Count - 1; i >= 0; i -= 1) + { + var obj = PaintingObjects[i]; + + if (val is null) + { + if (UseExtRect) + { + if (HelpfulDrawingFunctions.IsPointInRectangle(p, obj.RectangleExtended)) + val = obj; + } + else if (HelpfulDrawingFunctions.IsPointInRectangle(p, obj.Rectangle)) + { + val = obj; + } + } + } + + return val; + } + + public PaintingObject[] GetObjects(Point p) + { + var objs = new List(); + + foreach (PaintingObject obj in PaintingObjects) + { + if (HelpfulDrawingFunctions.IsPointInRectangle(p, obj.RectangleExtended)) + objs.Add(obj); + } + + return objs.ToArray(); + } + + public PaintingObject[] GetObjects(Point startPoint, Point endPoint) + { + return GetObjects(new Rectangle(startPoint, (Size)(endPoint - (Size)startPoint))); + } + + public PaintingObject[] GetObjects(Rectangle rect) + { + var objs = new List(); + + foreach (PaintingObject obj in PaintingObjects) + { + var objRect = obj.Rectangle; + if (HelpfulDrawingFunctions.IsPointInRectangle(objRect.Location, rect) || HelpfulDrawingFunctions.IsPointInRectangle(objRect.Location + objRect.Size, rect) || HelpfulDrawingFunctions.IsPointInRectangle(new PointF(objRect.Left, objRect.Bottom), rect) || HelpfulDrawingFunctions.IsPointInRectangle(new PointF(objRect.Right, objRect.Top), rect)) + objs.Add(obj); + } + + return objs.ToArray(); + } + + protected override CreateParams CreateParams + { + get + { + var cp = base.CreateParams; + + // If EnableRealTransparency Then + // cp.ExStyle = cp.ExStyle Or &H20 'WS_EX_TRANSPARENT + // End If + + return cp; + } + } + + /// + /// Sorg dafür, dass Events durch dieses Control durchdringen zum Parnet-Control. + /// + /// + protected override void WndProc(ref Message m) + { + const int WM_NCHITTEST = 0x84; + const int HTTRANSPARENT = -1; + + if (!VisibleForMouseEvents && m.Msg == WM_NCHITTEST) + m.Result = HTTRANSPARENT; + else + { + base.WndProc(ref m); + } + } + + protected override void OnPaintBackground(PaintEventArgs e) + { + // Stop Drawing directly to the parent + SuspendLayout(); + + // Draw Background + // If Not EnableRealTransparency Then + base.OnPaintBackground(e); + // End If + } + + protected override void OnPaint(PaintEventArgs e) + { + // Draw PaintingObjects stuff + if (StopDrawing) + e.Graphics.DrawImage(bufferedImg, Point.Empty); + else + { + { + var withBlock = e.Graphics; + withBlock.SmoothingMode = SmoothingMode.HighQuality; + withBlock.PixelOffsetMode = PixelOffsetMode.HighQuality; + withBlock.PageUnit = GraphicsUnit.Pixel; + withBlock.InterpolationMode = InterpolationMode.HighQualityBicubic; + } + + if (GridVisible) + DrawGridMethode?.Invoke(e, this, Offset); + + var baserect = new RectangleF(Offset, Size); + + foreach (PaintingObject obj in PaintingObjects) + { + if (obj.Visible && HelpfulDrawingFunctions.OverlapsTwoRectangles(obj.Rectangle, baserect)) + obj.Draw(e, Offset); + } + + if (_IsAreaSelecting) + DrawAreaSelectionMethode?.Invoke(e, this, new PointF(startMousePos.X - Offset.X, startMousePos.Y - Offset.Y), new PointF(lastMousePos.X - Offset.X, lastMousePos.Y - Offset.Y)); + } + + // Do default Drawing Methode + base.OnPaint(e); + + // Start Drawing directly to the Form + ResumeLayout(false); + } + + public new Graphics CreateGraphics() + { + return base.CreateGraphics(); + } + + public void PaintFullView(Graphics g) + { + foreach (PaintingObject obj in PaintingObjects) + { + if (obj.Visible) + obj.Draw(g, PointF.Empty); + } + } + + private SizeF CalcTextSize(PaintingObject obj) + { + return CalcTextSize(obj, Parent.CreateGraphics()); + } + + private SizeF CalcTextSize(PaintingObject obj, Graphics g) + { + return g.MeasureString(obj.Text, obj.TextFont, (int)Math.Round(obj.Width)); + } + + private void SelectControlsInArea() + { + var rect = HelpfulDrawingFunctions.GetRectangle(startMousePos, lastMousePos); + foreach (PaintingObject obj in PaintingObjects) + obj.Selected = startMousePos.X >= lastMousePos.X ? HelpfulDrawingFunctions.OverlapsTwoRectangles(obj.Rectangle, rect) : HelpfulDrawingFunctions.RectangleContainsRectangle(rect, obj.Rectangle); + } + + public void ArrangeToGrid(PaintingObject obj, bool snapPinnedObjects) + { + if (snapPinnedObjects || !IsPinnedObject(obj)) + { + var zoomedGridChunkSize = new SizeF(GridChunkSize.Width * ZoomFactor.Width, GridChunkSize.Height * ZoomFactor.Height); + + int modTop = (int)Math.Round(obj.Y % zoomedGridChunkSize.Height); + int modLeft = (int)Math.Round(obj.X % zoomedGridChunkSize.Width); + + int halfHeight = (int)Math.Round(zoomedGridChunkSize.Height / 2f); + int halfWidth = (int)Math.Round(zoomedGridChunkSize.Width / 2f); + + void zoomLocation(PaintingObject obj2) + { + if (modTop > halfHeight) + obj2.Y += zoomedGridChunkSize.Height - modTop; + else + { + obj2.Y -= modTop; + } + + if (modLeft > halfWidth) + obj2.X += zoomedGridChunkSize.Width - modLeft; + else + { + obj2.X -= modLeft; + } + }; + + zoomLocation(obj); + + foreach (PaintingObject pinned in obj.PinnedObjects) + zoomLocation(pinned); + + int modH = (int)Math.Round(obj.Height % zoomedGridChunkSize.Height); + int modW = (int)Math.Round(obj.Width % zoomedGridChunkSize.Width); + + + void zoomSize(PaintingObject obj2) { if (obj2.EnableResize && !obj2.HardcodedSize) { if (modH > halfHeight) obj2.Height += zoomedGridChunkSize.Height - modH; else { obj2.Height -= modH; } if (modW > halfWidth) obj2.Width += zoomedGridChunkSize.Width - modW; else { obj2.Width -= modW; } } }; + + zoomSize(obj); + + foreach (PaintingObject pinned in obj.PinnedObjects) + zoomSize(pinned); + } + } + + public bool IsPinnedObject(PaintingObject o) + { + foreach (PaintingObject obj in PaintingObjects) + { + if (obj.PinnedObjects.Contains(o)) + return true; + } + return false; + } + + public void AutoArrangeToGrid() + { + if (GridEnabled) + { + foreach (PaintingObject obj in GetSelectedObjects()) + { + if (obj.AutoAlignToGrid) + ArrangeToGrid(obj, false); + } + if (!StopDrawing) + Refresh(); + } + } + + public SizeF GetFullSize() + { + return GetFullSize(PaintingObjects); + } + + public static SizeF GetFullSize(IEnumerable objects) + { + float curX = 0f; + float curY = 0f; + + foreach (PaintingObject po in objects) + { + float myX = po.X + po.Width; + if (curX < myX) + curX = myX; + float myY = po.Y + po.Height; + if (curY < myY) + curY = myY; + } + + return new SizeF(curX + 20f, curY + 20f); + } + + internal void RaisePaintingObjectAdded(PaintingObjectEventArgs args) + { + PaintingObjectAdded?.Invoke(this, args); + } + internal void RaisePaintingObjectRemoved(PaintingObjectEventArgs args) + { + PaintingObjectRemoved?.Invoke(this, args); + } + + private void PaintingControl_PaintingObjectAdded(object sender, PaintingObjectEventArgs e) + { + // CalculateScrollValues() + } + + private void CheckMouseWheel(object sender, MouseEventArgs e) + { + if (pressedAlt) + { + float val = (float)(e.Delta / 120d / 10d); + ZoomFactor = new SizeF((float)Math.Max((double)(ZoomFactor.Width + val), 0.25d), (float)Math.Max((double)(ZoomFactor.Height + val), 0.25d)); + Refresh(); + } + else + { + // ... + } + } + + public void SuspendDrawing() + { + if (_stopDrawing < 0) + // bufferedImg = New Bitmap(Width, Height) + // DrawToBitmap(bufferedImg, New Rectangle(0, 0, bufferedImg.Width, bufferedImg.Height)) + Utils.DrawingControl.SuspendDrawing(this); + _stopDrawing += 1; + } + + public void ResumeDrawing() + { + ResumeDrawing(true); + } + + public void ResumeDrawing(bool executeRefresh) + { + if (_stopDrawing >= 0) + _stopDrawing -= 1; + + if (_stopDrawing == -1) + // bufferedImg.Dispose() + // bufferedImg = Nothing + // If executeRefresh Then Refresh() + Utils.DrawingControl.ResumeDrawing(this, executeRefresh); + } + +} \ No newline at end of file diff --git a/Pilz.UI/PaintingControl/PaintingControlDelegates.cs b/Pilz.UI/PaintingControl/PaintingControlDelegates.cs new file mode 100644 index 0000000..14d04f6 --- /dev/null +++ b/Pilz.UI/PaintingControl/PaintingControlDelegates.cs @@ -0,0 +1,11 @@ +using System.Drawing; +using System.Windows.Forms; + +namespace Pilz.UI; + + +public delegate void DelegateDrawPaintingObjectMethode(PaintingObjectPaintEventArgs e); + +public delegate void DelegateDrawPaintingControlGridMethode(PaintEventArgs e, PaintingControl pc, PointF offset); + +public delegate void DelegateDrawPaintingControlAreaSelectionMethode(PaintEventArgs e, PaintingControl pc, PointF startMousePos, PointF lastMousePos); \ No newline at end of file diff --git a/Pilz.UI/PaintingControl/PaintingObject.cs b/Pilz.UI/PaintingControl/PaintingObject.cs new file mode 100644 index 0000000..5134016 --- /dev/null +++ b/Pilz.UI/PaintingControl/PaintingObject.cs @@ -0,0 +1,826 @@ +using Microsoft.VisualBasic.CompilerServices; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Linq; +using System.Reflection; +using System.Windows.Forms; + +namespace Pilz.UI; + + +[Serializable] +public class PaintingObject : ICloneable, IPaintingObjectContainer +{ + + private PaintingObjectResizing resizeEngine = null; + private bool _Selected = false; + + private PaintingControl _Parent = null; + public Color FillColor { get; set; } = Color.Blue; + public Color OutlineColor { get; set; } = Color.DarkBlue; + public float OutlineThicknes { get; set; } = 1f; + public DashStyle OutlineDashStyle { get; set; } = DashStyle.Solid; + public LineCapProps LineStartCap { get; set; } = null; + public LineCapProps LineEndCap { get; set; } = null; + [JsonProperty] + private string _Text = ""; + public TextPosition TextPosition { get; set; } = TextPosition.FullCenter; + public StringAlignment VerticalTextAlignment { get; set; } = StringAlignment.Center; + public StringAlignment HorizontalTextAlignment { get; set; } = StringAlignment.Center; + public Font TextFont { get; set; } = new Font(FontFamily.GenericSansSerif, 8.25f); + public Color TextColor { get; set; } = Color.Black; + [JsonProperty] + private PointF _Location = new PointF(50f, 50f); + [JsonProperty] + private SizeF _Size = new SizeF(50f, 80f); + public bool EnableFill { get; set; } = true; + public bool EnableOutline { get; set; } = true; + public Color SelectionColor { get; set; } = Color.CornflowerBlue; + public DashStyle SelectionDashStyle { get; set; } = DashStyle.Dot; + [JsonProperty] + private bool _EnableSelection = true; + public Image Image { get; set; } = null; + [JsonIgnore] + public Image BufferedImage { get; set; } = null; + public ImageSizeMode ImageSizeMode { get; set; } + public PaintingObjectImageProperties ImageProperties { get; set; } = new PaintingObjectImageProperties(); + [JsonIgnore] + public object Tag { get; set; } = null; + public string Name { get; set; } = ""; + public List PinnedObjects { get; private set; } = new List(); + [JsonIgnore] + public List DrawMethodes { get; private set; } = new List(); + [JsonIgnore] + public DelegateDrawPaintingObjectMethode DrawSelectionMethode { get; private set; } = DefaultDrawMethodes.DrawSelection; + public Cursor Cursor { get; set; } = Cursors.Default; + public bool HardcodedSize { get; set; } = false; + public bool HardcodedLocation { get; set; } = false; + [JsonProperty] + private bool _Visible = true; + [JsonProperty] + private bool _AutoAlignToGrid = false; + public bool MouseTransparency { get; set; } = false; + public PaintingObjectLayering Layering { get; private set; } = new PaintingObjectLayering(this); + public PaintingObjectList PaintingObjects { get; private set; } = new PaintingObjectList(_Parent) { EnableRaisingEvents = false }; + [JsonIgnore] + public ulong ErrorsAtDrawing { get; private set; } = 0UL; + + public event MouseClickEventHandler MouseClick; + + public delegate void MouseClickEventHandler(PaintingObject sender, MouseEventArgs e); + public event MouseDownEventHandler MouseDown; + + public delegate void MouseDownEventHandler(PaintingObject sender, MouseEventArgs e); + public event MouseUpEventHandler MouseUp; + + public delegate void MouseUpEventHandler(PaintingObject sender, MouseEventArgs e); + public event MouseMoveEventHandler MouseMove; + + public delegate void MouseMoveEventHandler(PaintingObject sender, MouseEventArgs e); + public event SelectedChangedEventHandler SelectedChanged; + + public delegate void SelectedChangedEventHandler(PaintingObject sender, EventArgs e); + public event PaintEventHandler Paint; + + public delegate void PaintEventHandler(PaintingObject sender, PaintEventArgs e); + public event ParentChangedEventHandler ParentChanged; + + public delegate void ParentChangedEventHandler(PaintingObject sender, EventArgs e); + public event VisibleChangedEventHandler VisibleChanged; + + public delegate void VisibleChangedEventHandler(PaintingObject sender, EventArgs e); + public event MovedEventHandler Moved; + + public delegate void MovedEventHandler(PaintingObject sender, EventArgs e); + public event MovingEventHandler Moving; + + public delegate void MovingEventHandler(PaintingObject sender, EventArgs e); + public event MovingBeforePositionUpdatedEventHandler MovingBeforePositionUpdated; + + public delegate void MovingBeforePositionUpdatedEventHandler(PaintingObject sender, CancelEventArgs e); + + public PaintingObject() + { + } + + public PaintingObject(PaintingObjectType @type) + { + Type = type; + } + + public PaintingObject(PaintingObjectType @type, DelegateDrawPaintingObjectMethode[] drawMethodes) : this(type) + { + DrawMethodes.AddRange(drawMethodes); + } + + internal void RaiseMouseClick(MouseEventArgs e) + { + MouseClick?.Invoke(this, e); + } + internal void RaiseMouseDown(MouseEventArgs e) + { + MouseDown?.Invoke(this, e); + } + internal void RaiseMouseUp(MouseEventArgs e) + { + MouseUp?.Invoke(this, e); + } + internal void RaiseMouseMove(MouseEventArgs e) + { + MouseMove?.Invoke(this, e); + } + private void RaisePaint(PaintEventArgs e) + { + Paint?.Invoke(this, e); + } + internal void RaiseMoved(EventArgs e) + { + Moved?.Invoke(this, e); + } + internal void RaiseMoving(EventArgs e) + { + Moving?.Invoke(this, e); + } + internal void RaiseMovingBeforePositionUpdated(EventArgs e) + { + MovingBeforePositionUpdated?.Invoke(this, (CancelEventArgs)e); + } + + public PaintingObjectType Type + { + get + { + var tt = PaintingObjectType.Custom; + + foreach (DelegateDrawPaintingObjectMethode d in DrawMethodes) + { + if (ReferenceEquals(d.Method.DeclaringType, typeof(DefaultDrawMethodes))) + { + switch (d.Method.Name ?? "") + { + case "DrawPicture": + { + tt = tt | PaintingObjectType.Picture; + break; + } + case "DrawText": + { + tt = tt | PaintingObjectType.Text; + break; + } + case "DrawRectangle": + { + tt = tt | PaintingObjectType.Rectangle; + break; + } + case "DrawEllipse": + { + tt = tt | PaintingObjectType.Elipse; + break; + } + case "DrawTriangle": + { + tt = tt | PaintingObjectType.Triangle; + break; + } + case "DrawLine": + { + tt = tt | PaintingObjectType.Line; + break; + } + } + } + } + + return tt; + } + set + { + DrawMethodes.Clear(); + + if ((value & PaintingObjectType.Picture) == PaintingObjectType.Picture) + DrawMethodes.Add(DefaultDrawMethodes.DrawPicture); + + if ((value & PaintingObjectType.Rectangle) == PaintingObjectType.Rectangle) + DrawMethodes.Add(DefaultDrawMethodes.DrawRectangle); + + if ((value & PaintingObjectType.Elipse) == PaintingObjectType.Elipse) + DrawMethodes.Add(DefaultDrawMethodes.DrawEllipse); + + if ((value & PaintingObjectType.Triangle) == PaintingObjectType.Triangle) + DrawMethodes.Add(DefaultDrawMethodes.DrawTriangle); + + if ((value & PaintingObjectType.Line) == PaintingObjectType.Line) + DrawMethodes.Add(DefaultDrawMethodes.DrawLine); + + if ((value & PaintingObjectType.Text) == PaintingObjectType.Text) + DrawMethodes.Add(DefaultDrawMethodes.DrawText); + } + } + + [JsonIgnore] + public PointF Location + { + get + { + if (Parent is not null) + return new PointF(_Location.X * Parent.ZoomFactor.Width, _Location.Y * Parent.ZoomFactor.Height); + else + { + return _Location; + } + } + set + { + if (Parent is not null) + _Location = new PointF(value.X / Parent.ZoomFactor.Width, value.Y / Parent.ZoomFactor.Height); + else + { + _Location = value; + } + } + } + + [JsonIgnore] + public PointF LocationDirect + { + get + { + return _Location; + } + set + { + _Location = value; + } + } + + [JsonIgnore] + public SizeF Size + { + get + { + if (Parent is not null) + return new SizeF(_Size.Width * Parent.ZoomFactor.Width, _Size.Height * Parent.ZoomFactor.Height); + else + { + return _Size; + } + } + set + { + if (Parent is not null) + _Size = new SizeF(value.Width / Parent.ZoomFactor.Width, value.Height / Parent.ZoomFactor.Height); + else + { + _Size = value; + } + ResetImageBuffer(); + } + } + + [JsonIgnore] + public SizeF SizeDirect + { + get + { + return _Size; + } + set + { + _Size = value; + ResetImageBuffer(); + } + } + + [JsonIgnore] + public bool AutoAlignToGrid + { + get + { + return _AutoAlignToGrid; + } + set + { + _AutoAlignToGrid = value; + if (value) + ArrangeToGrid(); + } + } + + [JsonIgnore] + public bool IsResizing + { + get + { + if (resizeEngine is null) + return false; + else + { + return (bool)resizeEngine?.IsResizing; + } + } + } + + [JsonIgnore] + public PaintingControl Parent + { + get + { + return _Parent; + } + set + { + bool re = !ReferenceEquals(value, _Parent); + _Parent = value; + if (re) + ParentChanged?.Invoke(this, new EventArgs()); + } + } + + [JsonIgnore] + public bool Visible + { + get + { + return _Visible; + } + set + { + if (value != _Visible) + { + _Visible = value; + if (!value && !_EnableSelection) + EnableResize = false; + VisibleChanged?.Invoke(this, new EventArgs()); + } + } + } + + [JsonIgnore] + public bool Selected + { + get + { + return _Selected; + } + set + { + SetSelection(value, true); + } + } + + [JsonIgnore] + public bool SelectedDirect + { + get + { + return Selected; + } + set + { + SetSelection(value, false); + } + } + + private void SetSelection(bool value, bool raiseEventOnParent) + { + if (EnableSelection) + { + if (_Selected != value) + { + _Selected = value; + SelectedChanged?.Invoke(this, new EventArgs()); + if (raiseEventOnParent) + Parent.RaiseSelectionChanged(); + } + } + else + { + _Selected = false; + } + } + + [JsonIgnore] + public float Width + { + get + { + return Size.Width; + } + set + { + Size = new SizeF(value, Size.Height); + } + } + [JsonIgnore] + public float Height + { + get + { + return Size.Height; + } + set + { + Size = new SizeF(Size.Width, value); + } + } + + [JsonIgnore] + public float X + { + get + { + return Location.X; + } + set + { + Location = new PointF(value, Location.Y); + } + } + [JsonIgnore] + public float Y + { + get + { + return Location.Y; + } + set + { + Location = new PointF(Location.X, value); + } + } + + [JsonIgnore] + public string Text + { + get + { + return _Text; + } + set + { + _Text = value; + } + } + + [JsonIgnore] + public RectangleF Rectangle + { + get + { + return new RectangleF(Location, Size); + } + set + { + Location = value.Location; + Size = value.Size; + } + } + + [JsonIgnore] + public bool EnableSelection + { + get + { + return _EnableSelection; + } + set + { + _EnableSelection = value; + if (!value && !_Visible) + EnableResize = false; + if (!value) + Selected = false; + } + } + + [JsonIgnore] + public Rectangle RectangleExtended + { + get + { + return new Rectangle((int)Math.Round(X - 12f), (int)Math.Round(Y - 12f), (int)Math.Round(Width + 12f + 12f), (int)Math.Round(Height + 12f + 12f)); + } + set + { + X = value.X + 12; + Y = value.Y + 12; + Width = value.Width - 12 - 12; + Height = value.Height - 12 - 12; + } + } + + public void FitSizeToText() + { + if (Parent is null) + throw new Exception("You have to put that PaintingObject to a PaintingControl before."); + + var g = Parent.CreateGraphics(); + var newSize = g.MeasureString(Text, TextFont); + SizeDirect = newSize + new SizeF(1f, 0f); + } + + public void SetBounds(int x, int y, int width, int height) + { + Location = new Point(x, y); + Size = new Size(width, height); + } + + [JsonIgnore] + public int Left + { + get + { + return (int)Math.Round(X); + } + set + { + X = value; + } + } + + [JsonIgnore] + public int Top + { + get + { + return (int)Math.Round(Y); + } + set + { + Y = value; + } + } + + [JsonIgnore] + public int Right + { + get + { + return (int)Math.Round(X + Width); + } + set + { + X = value - Width; + } + } + + [JsonIgnore] + public int Bottom + { + get + { + return (int)Math.Round(Y + Height); + } + set + { + Y = value - Height; + } + } + + [JsonProperty(nameof(Tag))] + public string TagString + { + get + { + if (Tag is string) + return Conversions.ToString(Tag); + else + { + return string.Empty; + } + } + set + { + Tag = value; + } + } + + public bool EnableResize + { + get + { + if (resizeEngine is null) + return false; + else + { + return resizeEngine.Enabled; + } + } + set + { + if (resizeEngine is null && value) + resizeEngine = new PaintingObjectResizing(this); + else if (resizeEngine is not null) + { + resizeEngine.Enabled = value; + } + } + } + + public void Remove() + { + Parent?.PaintingObjects.Remove(this); + } + + public void AutoArrangeToGrid() + { + if (((Parent?.GridEnabled) is var arg1 && !arg1.HasValue || arg1.Value) && AutoAlignToGrid && arg1.HasValue) + ArrangeToGrid(); + } + + public void ArrangeToGrid() + { + if (Parent is not null) + { + Parent.ArrangeToGrid(this, true); + if (!Parent.StopDrawing) + Parent.Refresh(); + } + } + + public void Draw(PaintEventArgs e) + { + Draw(e, e.ClipRectangle.Location); + } + + public void Draw(PaintEventArgs e, PointF offset) + { + Draw(e.Graphics, offset); + + if (Visible) + RaisePaint(e); + } + + public void Draw(Graphics g, PointF offset) + { + if (Visible) + { + var poevargs = new PaintingObjectPaintEventArgs(this, g, offset); + + foreach (DelegateDrawPaintingObjectMethode dm in DrawMethodes) + { + try + { + dm?.Invoke(poevargs); + } + catch (Exception ex) + { + ErrorsAtDrawing = (ulong)Math.Round(ErrorsAtDrawing + 1m); + } + } + + if (Selected && DrawSelectionMethode is not null) + DrawSelectionMethode?.Invoke(poevargs); + } + } + + public object Clone() + { + return Clone(true); + } + + public object Clone(bool includePinnedObject) + { + var obj = new PaintingObject(); + var metype = GetType(); + string[] blackField = new[] { nameof(PinnedObjects), nameof(resizeEngine), nameof(_Parent), nameof(BufferedImage), nameof(ImageProperties) }; + + void copyFields(object source, object dest, string[] blackFields, Type t) + { + var fields = new List(t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.IgnoreCase | BindingFlags.Instance)); + foreach (FieldInfo @field in fields) + { + if (!blackFields.Contains(@field.Name)) + @field.SetValue(dest, @field.GetValue(source)); + } + }; + + copyFields(this, obj, blackField, metype); + copyFields(ImageProperties, obj.ImageProperties, Array.Empty(), ImageProperties.GetType()); + + if (includePinnedObject) + obj.PinnedObjects.AddRange(PinnedObjects); + + obj.EnableResize = EnableResize; + + return obj; + } + + [Obsolete("Use Layering.BringToTop() instead!")] + public void BringToFront() + { + Layering.BringToTop(); + } + + [Obsolete("Use Layering.SendToBack() instead!")] + public void SendToBack() + { + Layering.SendToBack(); + } + + public void ResetImageBuffer() + { + BufferedImage = null; + } + +} + +public class PaintingObjectList : List +{ + + [JsonIgnore] + internal PaintingControl MyParent { get; private set; } + internal bool EnableRaisingEvents { get; set; } = true; + [JsonIgnore] + public PaintingObjectListLayering Layering { get; private set; } = new PaintingObjectListLayering(this); + + public PaintingObjectList() : this(null) + { + } + + public PaintingObjectList(PaintingControl parent) + { + MyParent = parent; + } + + public new void Add(PaintingObject item) + { + item.Parent = MyParent; + base.Add(item); + item.AutoArrangeToGrid(); + if (EnableRaisingEvents) + MyParent?.RaisePaintingObjectAdded(new PaintingObjectEventArgs(new[] { item })); + } + + public void AddRange(PaintingObject[] items) + { + foreach (PaintingObject item in items) + item.Parent = MyParent; + base.AddRange(items); + foreach (PaintingObject item in items) + item.AutoArrangeToGrid(); + if (EnableRaisingEvents) + MyParent?.RaisePaintingObjectAdded(new PaintingObjectEventArgs(items)); + } + + public new void Insert(int index, PaintingObject item) + { + item.Parent = MyParent; + base.Insert(index, item); + MyParent?.AutoArrangeToGrid(); + if (EnableRaisingEvents) + MyParent?.RaisePaintingObjectAdded(new PaintingObjectEventArgs(new[] { item })); + } + + public new void Remove(PaintingObject item) + { + item.Parent = null; + base.Remove(item); + if (EnableRaisingEvents) + MyParent?.RaisePaintingObjectRemoved(new PaintingObjectEventArgs(new[] { item })); + } + + public new void RemoveAt(int index) + { + this[index].Parent = null; + var item = this[index]; + base.RemoveAt(index); + if (EnableRaisingEvents) + MyParent?.RaisePaintingObjectRemoved(new PaintingObjectEventArgs(new[] { item })); + } + +} + +public enum PaintingObjectType +{ + Custom = 0, + Text = 1, + Picture = 2, + Line = 4, + Triangle = 8, + Rectangle = 16, + Elipse = 32 +} + +public enum ImageSizeMode +{ + Fit, + Zoom, + Original +} + +public enum TextPosition +{ + HLeft = 0x1, + HRight = 0x2, + HCenter = 0x4, + VUp = 0x10, + VDown = 0x20, + VCenter = 0x40, + FullCenter = HCenter | VCenter +} \ No newline at end of file diff --git a/Pilz.UI/PaintingControl/PaintingObjectJsonSerializer.cs b/Pilz.UI/PaintingControl/PaintingObjectJsonSerializer.cs new file mode 100644 index 0000000..6cfbabc --- /dev/null +++ b/Pilz.UI/PaintingControl/PaintingObjectJsonSerializer.cs @@ -0,0 +1,7 @@ + +namespace Pilz.UI; + +public class PaintingObjectJsonSerializer +{ + +} \ No newline at end of file diff --git a/Pilz.UI/PaintingControl/PaintingObjectLayering.cs b/Pilz.UI/PaintingControl/PaintingObjectLayering.cs new file mode 100644 index 0000000..eeac487 --- /dev/null +++ b/Pilz.UI/PaintingControl/PaintingObjectLayering.cs @@ -0,0 +1,112 @@ +using Newtonsoft.Json; + +namespace Pilz.UI; + + +public class PaintingObjectLayering +{ + + // + private readonly PaintingObject _PaintingObject; + + [JsonIgnore] + public PaintingObject PaintingObject + { + get + { + return _PaintingObject; + } + } + + /// + /// Get the current object list from the painting object. + /// + /// Returns the current object list from the painting object. + [JsonIgnore] + public PaintingObjectList ObjectList + { + get + { + return PaintingObject.Parent.PaintingObjects; + } + } + + /// + /// Create a new instance of object layer managing. + /// + /// + public PaintingObjectLayering(PaintingObject obj) + { + _PaintingObject = obj; + } + + /// + /// Moves the object by the given number of indicies. + /// + /// The number how many objects it should be moved. + public void MoveObject(int count) + { + int oldIndex = ObjectList.IndexOf(PaintingObject); + int newIndex = oldIndex + count; + MoveObjectTo(newIndex); + } + + /// + /// Moves the object to the new index. + /// + /// + public void MoveObjectTo(int newIndex) + { + var list = ObjectList; + + // Check & make index valid + if (newIndex >= ObjectList.Count) + newIndex = ObjectList.Count - 1; + else if (newIndex < 0) + { + newIndex = 0; + } + + // Remove object + list.Remove(PaintingObject); + + // Insert object at new index + list.Insert(newIndex, PaintingObject); + + // Order all objects again + list.Layering.OrderAll(); + } + + /// + /// Moves the object to the front. + /// + public void BringToTop() + { + MoveObjectTo(ObjectList.Count - 1); + } + + /// + /// Moves the object to the back. + /// + public void SendToBack() + { + MoveObjectTo(0); + } + + /// + /// Moves the object fordward by one + /// + public void OneToTop() + { + MoveObject(+1); + } + + /// + /// Moves the object backward by one + /// + public void OneToBack() + { + MoveObject(-1); + } + +} \ No newline at end of file diff --git a/Pilz.UI/PaintingControl/PaintingObjectListLayering.cs b/Pilz.UI/PaintingControl/PaintingObjectListLayering.cs new file mode 100644 index 0000000..19f7df2 --- /dev/null +++ b/Pilz.UI/PaintingControl/PaintingObjectListLayering.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; + +namespace Pilz.UI; + +public class PaintingObjectListLayering +{ + + + public PaintingObjectList ObjectList { get; private set; } + public Dictionary> Conditions { get; private set; } = new Dictionary>(); + + /// + /// Get the order function will checkout the conditions. + /// + /// Returns true, if conditions are aviable, otherwise false. + public bool EnableConditions + { + get + { + return Conditions.Any(); + } + } + + /// + /// Create a new instance of object list layer managing. + /// + /// + public PaintingObjectListLayering(PaintingObjectList list) + { + ObjectList = list; + } + + /// + /// Order all objects using the conditions. If no conditions are setted, this method will do nothing. + /// + public void OrderAll() + { + if (EnableConditions) + OrderAllPrivate(); + } + + private void OrderAllPrivate() + { + var list = ObjectList; + var listOld = list.ToList(); + var toRemove = new List(); + + // Disable raising events + ObjectList.EnableRaisingEvents = false; + + // Clear list + list.Clear(); + + // Add ordered + foreach (var kvp in Conditions.OrderBy(n => n.Key)) + { + var func = kvp.Value; + + foreach (PaintingObject obj in listOld) + { + if (func(obj)) + { + // Add to list + list.Add(obj); + + // Add to remove + toRemove.Add(obj); + } + } + + // Remove remembered objects + foreach (PaintingObject obj in toRemove) + listOld.Remove(obj); + toRemove.Clear(); + } + + // Enable raising events + ObjectList.EnableRaisingEvents = true; + + // Refresh + ObjectList.MyParent?.Refresh(); + } + +} \ No newline at end of file diff --git a/Pilz.UI/PaintingControl/PaintingObjectResizing.cs b/Pilz.UI/PaintingControl/PaintingObjectResizing.cs new file mode 100644 index 0000000..5372a22 --- /dev/null +++ b/Pilz.UI/PaintingControl/PaintingObjectResizing.cs @@ -0,0 +1,346 @@ +using Pilz.Drawing; +using System; +using System.Drawing; +using System.Runtime.CompilerServices; +using System.Windows.Forms; + +namespace Pilz.UI; + + +[Serializable] +internal class PaintingObjectResizing +{ + + public static event CheckEnabledEventHandler CheckEnabled; + + public delegate void CheckEnabledEventHandler(PaintingObjectResizing sender, ref bool enabled); + + private PaintingObject _mObj; + + private PaintingObject mObj + { + [MethodImpl(MethodImplOptions.Synchronized)] + get + { + return _mObj; + } + + [MethodImpl(MethodImplOptions.Synchronized)] + set + { + if (_mObj != null) + { + _mObj.MouseDown -= mControl_MouseDown; + _mObj.MouseUp -= mControl_MouseUp; + _mObj.Paint -= mControl_Paint; + _mObj.SelectedChanged -= mControl_MouseLeave; + _mObj.ParentChanged -= mObj_ParentChanged; + } + + _mObj = value; + if (_mObj != null) + { + _mObj.MouseDown += mControl_MouseDown; + _mObj.MouseUp += mControl_MouseUp; + _mObj.Paint += mControl_Paint; + _mObj.SelectedChanged += mControl_MouseLeave; + _mObj.ParentChanged += mObj_ParentChanged; + } + } + } + private Control _mObjParent; + + private Control mObjParent + { + [MethodImpl(MethodImplOptions.Synchronized)] + get + { + return _mObjParent; + } + + [MethodImpl(MethodImplOptions.Synchronized)] + set + { + _mObjParent = value; + } + } + private Control _mObjControl; + + private Control mObjControl + { + [MethodImpl(MethodImplOptions.Synchronized)] + get + { + return _mObjControl; + } + + [MethodImpl(MethodImplOptions.Synchronized)] + set + { + if (_mObjControl != null) + { + _mObjControl.MouseMove -= mControl_MouseMove; + _mObjControl.ParentChanged -= mObjParent_ParentChanged; + } + + _mObjControl = value; + if (_mObjControl != null) + { + _mObjControl.MouseMove += mControl_MouseMove; + _mObjControl.ParentChanged += mObjParent_ParentChanged; + } + } + } + private bool mMouseDown = false; + private EdgeEnum mEdge = EdgeEnum.None; + private int mWidth = 4; + private int qWidth = 4 * 4; + private Rectangle rect = new Rectangle(); + + public bool Enabled { get; set; } = true; + public SizeF MinimumSize { get; set; } = new SizeF(15f, 15f); + + [Serializable] + private enum EdgeEnum + { + None, + Right, + Left, + Top, + Bottom, + TopLeft, + TopRight, + BottomLeft, + BottomRight + } + + public bool IsResizing + { + get + { + return mMouseDown && mEdge != EdgeEnum.None; + } + } + + public PaintingObject PaintingObject + { + get + { + return mObj; + } + } + + public PaintingObjectResizing(PaintingObject obj) + { + mObjParent = null; + mObjControl = null; + mObj = obj; + mObjControl = mObj.Parent; + } + + private bool IsEnabled() + { + bool enabled = Enabled; + + CheckEnabled?.Invoke(this, ref enabled); + + return enabled; + } + + private void mControl_MouseDown(object sender, MouseEventArgs e) + { + if (e.Button == MouseButtons.Left) + mMouseDown = true; + } + + private void mControl_MouseUp(object sender, MouseEventArgs e) + { + mMouseDown = false; + + if (mObj.Selected) + mObj.AutoArrangeToGrid(); + } + + private void KeepInRange(ref SizeF size) + { + if (size.Height < MinimumSize.Height || size.Width < MinimumSize.Width) + size = new SizeF(Math.Max(size.Width, MinimumSize.Width), Math.Max(size.Height, MinimumSize.Height)); + } + + private void mControl_MouseMove(object sender, MouseEventArgs e) + { + if (mMouseDown && mEdge != EdgeEnum.None) + { + + int eX = (int)Math.Round(e.X + mObj.Parent.Offset.X); + int eY = (int)Math.Round(e.Y + mObj.Parent.Offset.Y); + + switch (mEdge) + { + case EdgeEnum.TopLeft: + { + mObj.SetBounds(eX, eY, (int)Math.Round(mObj.Width + (mObj.Left - eX)), (int)Math.Round(mObj.Height + (mObj.Top - eY))); + break; + } + case EdgeEnum.TopRight: + { + mObj.SetBounds(mObj.Left, eY, eX - mObj.Left, (int)Math.Round(mObj.Height + (mObj.Top - eY))); + break; + } + case EdgeEnum.BottomRight: + { + mObj.SetBounds(mObj.Left, mObj.Top, eX - mObj.Left, eY - mObj.Top); + break; + } + case EdgeEnum.BottomLeft: + { + mObj.SetBounds(eX, mObj.Top, (int)Math.Round(mObj.Width + (mObj.Left - eX)), eY - mObj.Top); + break; + } + case EdgeEnum.Left: + { + mObj.SetBounds(eX, mObj.Top, (int)Math.Round(mObj.Width + (mObj.Left - eX)), (int)Math.Round(mObj.Height)); + break; + } + case EdgeEnum.Right: + { + mObj.SetBounds(mObj.Left, mObj.Top, eX - mObj.Left, (int)Math.Round(mObj.Height)); + break; + } + case EdgeEnum.Top: + { + mObj.SetBounds(mObj.Left, eY, (int)Math.Round(mObj.Width), (int)Math.Round(mObj.Height + (mObj.Top - eY))); + break; + } + case EdgeEnum.Bottom: + { + mObj.SetBounds(mObj.Left, mObj.Top, (int)Math.Round(mObj.Width), eY - mObj.Top); + break; + } + } + + var argsize = mObj.Size; + KeepInRange(ref argsize); + mObj.Size = argsize; + } + + else if (!mMouseDown) + { + + int eXo = e.X; + int eYo = e.Y; + int realX = (int)Math.Round(eXo + mObj.Parent.Offset.X); + int realY = (int)Math.Round(eYo + mObj.Parent.Offset.Y); + int eXwo = (int)Math.Round(eXo - mObj.X); + int eYwo = (int)Math.Round(eYo - mObj.Y); + int eX = (int)Math.Round(eXwo + mObj.Parent.Offset.X); + int eY = (int)Math.Round(eYwo + mObj.Parent.Offset.Y); + var eLocation = new Point(eX, eY); + RectangleF extRect = mObj.RectangleExtended; + var oldRect = mObj.Rectangle; + + var newRect = new RectangleF(); + newRect.X = extRect.X - oldRect.X; + newRect.Y = extRect.Y - oldRect.Y; + newRect.Width = (extRect.Width - oldRect.Width) / 2f; + newRect.Height = (extRect.Height - oldRect.Height) / 2f; + + bool setToNone = false; + bool isOnTop = ReferenceEquals(mObj.Parent.GetObject(new PointF(realX, realY), true), mObj); + + if (IsEnabled() && isOnTop) + { + switch (true) + { + case object _ when HelpfulDrawingFunctions.IsPointInRectangle(eLocation, new Rectangle((int)Math.Round(newRect.X), (int)Math.Round(newRect.Y), (int)Math.Round(newRect.Width), (int)Math.Round(newRect.Height))): + { + mObj.Cursor = Cursors.SizeNWSE; + mEdge = EdgeEnum.TopLeft; + break; + } + case object _ when HelpfulDrawingFunctions.IsPointInRectangle(eLocation, new Rectangle((int)Math.Round(mObj.Width), (int)Math.Round(newRect.Y), (int)Math.Round(newRect.Width), (int)Math.Round(newRect.Height))): + { + mObj.Cursor = Cursors.SizeNESW; + mEdge = EdgeEnum.TopRight; + break; + } + case object _ when HelpfulDrawingFunctions.IsPointInRectangle(eLocation, new Rectangle((int)Math.Round(mObj.Width), (int)Math.Round(mObj.Height), (int)Math.Round(newRect.Width), (int)Math.Round(newRect.Height))): + { + mObj.Cursor = Cursors.SizeNWSE; + mEdge = EdgeEnum.BottomRight; + break; + } + case object _ when HelpfulDrawingFunctions.IsPointInRectangle(eLocation, new Rectangle((int)Math.Round(newRect.X), (int)Math.Round(mObj.Height), (int)Math.Round(newRect.Width), (int)Math.Round(newRect.Height))): + { + mObj.Cursor = Cursors.SizeNESW; + mEdge = EdgeEnum.BottomLeft; + break; + } + case object _ when HelpfulDrawingFunctions.IsPointInRectangle(eLocation, new Rectangle((int)Math.Round(-newRect.Width), 0, (int)Math.Round(newRect.Width), (int)Math.Round(mObj.Height))): + { + mObj.Cursor = Cursors.SizeWE; + mEdge = EdgeEnum.Left; + break; + } + case object _ when HelpfulDrawingFunctions.IsPointInRectangle(eLocation, new Rectangle((int)Math.Round(mObj.Width), 0, (int)Math.Round(newRect.Width), (int)Math.Round(mObj.Height))): + { + mObj.Cursor = Cursors.SizeWE; + mEdge = EdgeEnum.Right; + break; + } + case object _ when HelpfulDrawingFunctions.IsPointInRectangle(eLocation, new Rectangle(0, (int)Math.Round(-newRect.Height), (int)Math.Round(mObj.Width), (int)Math.Round(newRect.Height))): + { + mObj.Cursor = Cursors.SizeNS; + mEdge = EdgeEnum.Top; + break; + } + case object _ when HelpfulDrawingFunctions.IsPointInRectangle(eLocation, new Rectangle(0, (int)Math.Round(mObj.Height), (int)Math.Round(mObj.Width), (int)Math.Round(newRect.Height))): + { + mObj.Cursor = Cursors.SizeNS; + mEdge = EdgeEnum.Bottom; + break; + } + + default: + { + setToNone = true; + break; + } + } + } + else + { + setToNone = true; + } + + if (setToNone) + { + mObj.Cursor = Cursors.Default; + mEdge = EdgeEnum.None; + } + } + } + + private void mControl_Paint(PaintingObject sender, PaintEventArgs e) + { + // e.Graphics.FillRectangle(brush, rect) + } + + private void mControl_MouseLeave(PaintingObject sender, EventArgs e) + { + if (!sender.Selected) + mEdge = EdgeEnum.None; + } + + private void mObjParent_ParentChanged(object sender, EventArgs e) + { + mObjParent = mObjControl.Parent; + } + + private void mObj_ParentChanged(PaintingObject sender, EventArgs e) + { + mObjControl = mObj.Parent; + mObjParent = mObjControl?.Parent; + } + +} \ No newline at end of file diff --git a/Pilz.UI/Pilz.UI.csproj b/Pilz.UI/Pilz.UI.csproj new file mode 100644 index 0000000..28bf18e --- /dev/null +++ b/Pilz.UI/Pilz.UI.csproj @@ -0,0 +1,85 @@ + + + Windows + net6.0-windows;net8.0-windows + 42016,41999,42017,42018,42019,42032,42036,42020,42021,42022 + Pilz.UI.xml + true + $(DefaultItemExcludes);$(ProjectDir)**\*.vb + latest + TRACE + + + DEBUG + + + true + + + On + + + Binary + + + Off + + + On + + + bin\$(Platform)\$(Configuration)\ + MinimumRecommendedRules.ruleset + true + DEBUG + + + bin\$(Platform)\$(Configuration)\ + true + MinimumRecommendedRules.ruleset + + + 2.0.0 + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Pilz.UI.My.Resources + Designer + + + PaintingControl.cs + + + + + + + \ No newline at end of file diff --git a/Pilz.UI/Resources.Designer.cs b/Pilz.UI/Resources.Designer.cs new file mode 100644 index 0000000..92cdce2 --- /dev/null +++ b/Pilz.UI/Resources.Designer.cs @@ -0,0 +1,70 @@ +// ------------------------------------------------------------------------------ +// +// Dieser Code wurde von einem Tool generiert. +// Laufzeitversion:4.0.30319.42000 +// +// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn +// der Code erneut generiert wird. +// +// ------------------------------------------------------------------------------ + + +using System.Diagnostics; +using Microsoft.VisualBasic; + +namespace Pilz.UI.My.Resources +{ + + // Diese Klasse wurde von der StronglyTypedResourceBuilder automatisch generiert + // -Klasse über ein Tool wie ResGen oder Visual Studio automatisch generiert. + // Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen + // mit der /str-Option erneut aus, oder Sie erstellen Ihr VS-Projekt neu. + /// + /// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw. + /// + [System.CodeDom.Compiler.GeneratedCode("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [DebuggerNonUserCode()] + [System.Runtime.CompilerServices.CompilerGenerated()] + [HideModuleName()] + internal static class Resources + { + + private static System.Resources.ResourceManager resourceMan; + + private static System.Globalization.CultureInfo resourceCulture; + + /// + /// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird. + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Resources.ResourceManager ResourceManager + { + get + { + if (ReferenceEquals(resourceMan, null)) + { + var temp = new System.Resources.ResourceManager("Pilz.UI.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle + /// Ressourcenzuordnungen, die diese stark typisierte Ressourcenklasse verwenden. + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} \ No newline at end of file diff --git a/Pilz.UI/My Project/Resources.resx b/Pilz.UI/Resources.resx similarity index 100% rename from Pilz.UI/My Project/Resources.resx rename to Pilz.UI/Resources.resx diff --git a/Pilz.UI/Utilities/DrawingControl.cs b/Pilz.UI/Utilities/DrawingControl.cs new file mode 100644 index 0000000..9659f4d --- /dev/null +++ b/Pilz.UI/Utilities/DrawingControl.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using System.Windows.Forms; + +namespace Pilz.UI.Utils; + + +public static class DrawingControl +{ + + private const int WM_SETREDRAW = 11; + private readonly static Dictionary dicSuspendCount = new Dictionary(); + + public static void SuspendDrawing(this Control control) + { + if (!dicSuspendCount.ContainsKey(control.Handle)) + dicSuspendCount.Add(control.Handle, 1); + else + { + dicSuspendCount[control.Handle] += 1; + } + + if (dicSuspendCount[control.Handle] == 1) + User32Bridge.SendMessage(control.Handle, WM_SETREDRAW, false, 0); + } + + public static void ResumeDrawing(this Control control) + { + control.ResumeDrawing(true); + } + + public static void ResumeDrawing(this Control control, bool redraw) + { + bool doRedraw = true; + + if (dicSuspendCount.ContainsKey(control.Handle)) + { + dicSuspendCount[control.Handle] -= 1; + if (dicSuspendCount[control.Handle] >= 1) + doRedraw = false; + } + + if (doRedraw) + { + User32Bridge.SendMessage(control.Handle, WM_SETREDRAW, true, 0); + if (redraw) + control.Refresh(); + } + } + +} \ No newline at end of file diff --git a/Pilz.UI/Utilities/User32Bridge.cs b/Pilz.UI/Utilities/User32Bridge.cs new file mode 100644 index 0000000..499c075 --- /dev/null +++ b/Pilz.UI/Utilities/User32Bridge.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Pilz.UI; + +public static class User32Bridge +{ + [DllImport("user32.dll", CharSet = CharSet.Auto)] + internal static extern int SendMessage(nint hWnd, int Msg, bool wParam, int lParam); +} \ No newline at end of file