From 8cb222770a239c8166ca86b4dfc2703d09294bd1 Mon Sep 17 00:00:00 2001 From: Ivan Mahonin Date: Aug 11 2015 19:41:40 +0000 Subject: Diagram --- diff --git a/mono/Diagram/.gitignore b/mono/Diagram/.gitignore new file mode 100644 index 0000000..6dd813a --- /dev/null +++ b/mono/Diagram/.gitignore @@ -0,0 +1 @@ +/obj/ diff --git a/mono/Diagram/ActiveDiagram.cs b/mono/Diagram/ActiveDiagram.cs new file mode 100644 index 0000000..49d07ed --- /dev/null +++ b/mono/Diagram/ActiveDiagram.cs @@ -0,0 +1,394 @@ +using System; +using System.Collections.Generic; +using System.Data.Linq; +using System.Drawing; +using System.IO; +using System.Globalization; + +namespace Diagram { + + public class ActiveBlock { + public Block block; + public readonly List links = new List(); + public PointF position; + + public Font captionFont; + public Font textFont; + public float margin; + public float padding; + public Pen pen; + public Brush brush; + + public string caption; + public string text; + public SizeF captionSize; + public SizeF textSize; + public float distance; + + public SizeF size; + public SizeF clientSize; + + public PointF getCenter() { + return new PointF( + position.X + 0.5f*size.Width, + position.Y + 0.5f*size.Height ); + } + + public void measure() { + float width = captionFont.GetHeight() * 15f; + caption = TextUtils.wrap(block.caption, width, captionFont); + captionSize = TextUtils.measure(caption, captionFont); + text = TextUtils.wrap(block.text, width, textFont); + textSize = TextUtils.measure(text, textFont); + if (caption != "" && text != "") + distance = 0.3f * Math.Max(captionFont.GetHeight(), textFont.GetHeight()); + clientSize = new SizeF( + Math.Max(captionSize.Width, textSize.Width), + captionSize.Height + distance + textSize.Height ); + size = new SizeF(clientSize.Width + 2f*padding, clientSize.Height + 2f*padding); + } + + public void draw(Graphics g) { + Shapes.drawRoundRect(g, new RectangleF(position, size), 2f*padding, pen, brush); + + g.DrawString( + caption, + captionFont, + Brushes.Black, + new RectangleF( + position.X + 0.5f*(size.Width - captionSize.Width), + position.Y + padding, + captionSize.Width, + captionSize.Height )); + + g.DrawString( + text, + textFont, + Brushes.Black, + new RectangleF( + position.X + padding, + position.Y + padding + captionSize.Height + distance, + clientSize.Width, + clientSize.Height - captionSize.Height - distance )); + } + + class LinkDesc { + public ActiveLink link; + public PointF src; + public PointF dst; + } + + class SideDesc { + public PointF a; + public PointF b; + public PointF normal; + public readonly List links = new List(); + + public SideDesc(PointF a, PointF b) { + this.a = a; + this.b = b; + float l = Geometry.lineLength(a, b); + normal = new PointF((b.Y - a.Y)/l, (a.X - b.X)/l); + } + } + + public void placeLinks() { + PointF lt = position; + PointF rb = new PointF(lt.X + size.Width, lt.Y + size.Height); + PointF lb = new PointF(lt.X, rb.Y); + PointF rt = new PointF(rb.X, lt.Y); + PointF center = getCenter(); + + List sides = new List() { + new SideDesc(lt, rt), + new SideDesc(rt, rb), + new SideDesc(rb, lb), + new SideDesc(lb, lt) }; + + foreach(ActiveLink link in links) { + link.visible = false; + LinkDesc linkDesc = new LinkDesc(); + linkDesc.link = link; + if (link.src == this) { + linkDesc.src = center; + linkDesc.dst = link.dst.getCenter(); + } else { + linkDesc.src = link.src.getCenter(); + linkDesc.dst = center; + } + foreach(SideDesc side in sides) { + PointF p; + if (Geometry.findIntersection(side.a, side.b, linkDesc.src, linkDesc.dst, out p)) { + link.visible = true; + linkDesc.src = p; + side.links.Add(linkDesc); + break; + } + } + } + + foreach(SideDesc side in sides) { + side.links.Sort(delegate(LinkDesc a, LinkDesc b) { + return Geometry.comparePointsAtLine(a.src, b.src, side.a, side.b); }); + for(int i = 0; i < side.links.Count; ++i) { + ActiveLink link = side.links[i].link; + PointF p = Geometry.pointAtLine(side.a, side.b, i, side.links.Count, padding); + if (link.src == this) { + link.srcBase = p; + link.srcTangent = side.normal; + link.pen = pen; + } else { + link.dstBase = p; + link.dstTangent = new PointF(-side.normal.X, -side.normal.Y); + } + } + } + } + } + + public class ActiveLink { + public Link link; + public ActiveBlock src; + public ActiveBlock dst; + + public SizeF arrowSize; + public Pen pen; + + public bool visible; + public PointF srcBase; + public PointF srcTangent; + public PointF dstBase; + public PointF dstTangent; + + public PointF[] bezier; + public PointF[] arrow; + + public static readonly PointF[] arrowTemplate = new PointF[] { + new PointF(0f, 0f), + new PointF(1f, 1f), + new PointF(0f, 0.75f) }; + + public static readonly float arrowOffset = 0.75f; + + public static PointF[] makeArrow(PointF point, PointF tangent, SizeF size) { + PointF[] arrow = new PointF[2*arrowTemplate.Length]; + PointF tx = new PointF( -0.5f*tangent.Y*size.Width, 0.5f*tangent.X*size.Width ); + PointF ty = new PointF( -tangent.X*size.Height, -tangent.Y*size.Height ); + for(int i = 0; i < arrowTemplate.Length; ++i) { + arrow[i] = new PointF( + point.X + tx.X*arrowTemplate[i].X + ty.X*arrowTemplate[i].Y, + point.Y + tx.Y*arrowTemplate[i].X + ty.Y*arrowTemplate[i].Y ); + arrow[2*arrowTemplate.Length - i - 1] = new PointF( + point.X - tx.X*arrowTemplate[i].X + ty.X*arrowTemplate[i].Y, + point.Y - tx.Y*arrowTemplate[i].X + ty.Y*arrowTemplate[i].Y ); + } + return arrow; + } + + public void place() { + if (!visible) return; + + float dx = 0.5f*Math.Abs(dstBase.X - srcBase.X); + float dy = 0.5f*Math.Abs(dstBase.Y - srcBase.Y); + float l = (float)Math.Sqrt(dx*dx + dy*dy); + if (l < Geometry.precision) { visible = false; return; } + dx = Math.Max(dx, 0.25f*l); + dy = Math.Max(dy, 0.25f*l); + + float sl = l; + if (Math.Abs(srcTangent.X) > Geometry.precision) + sl = Math.Min(sl, dx/Math.Abs(srcTangent.X)); + if (Math.Abs(srcTangent.Y) > Geometry.precision) + sl = Math.Min(sl, dy/Math.Abs(srcTangent.Y)); + PointF st = new PointF(srcTangent.X*sl, srcTangent.Y*sl); + + float dl = l; + if (Math.Abs(dstTangent.X) > Geometry.precision) + dl = Math.Min(dl, dx/Math.Abs(dstTangent.X)); + if (Math.Abs(dstTangent.Y) > Geometry.precision) + dl = Math.Min(dl, dy/Math.Abs(dstTangent.Y)); + PointF dt = new PointF(dstTangent.X*sl, dstTangent.Y*sl); + + bezier = new PointF[] { + srcBase, + new PointF(srcBase.X + st.X, srcBase.Y + st.Y), + new PointF(dstBase.X - dt.X, dstBase.Y - dt.Y), + new PointF(dstBase.X - dstTangent.X*(arrowOffset*arrowSize.Height + 0.5f*pen.Width), + dstBase.Y - dstTangent.Y*(arrowOffset*arrowSize.Height + 0.5f*pen.Width)) + }; + + arrow = makeArrow( + new PointF(dstBase.X - 0.5f*dstTangent.X*pen.Width, + dstBase.Y - 0.5f*dstTangent.Y*pen.Width), + dstTangent, + arrowSize ); + } + + public void draw(Graphics g) { + if (!visible) return; + g.DrawBeziers(pen, bezier); + g.FillPolygon(new SolidBrush(pen.Color), arrow); + //g.DrawPolygon(pen, arrow); + } + } + + public class ActiveDiagram { + public Diagram diagram; + + public readonly Dictionary positions = new Dictionary(); + + public Font captionFont; + public Font textFont; + public float margin; + public float padding; + public SizeF arrowSize; + public Pen pen; + public Brush brush; + + public readonly Dictionary blocks = new Dictionary(); + public readonly Dictionary links = new Dictionary(); + + public RectangleF bounds; + + public void savePositions(string filename) { + remeberPositions(); + CultureInfo ci = new CultureInfo("en-US"); + List lines = new List(); + foreach(KeyValuePair pair in positions) { + lines.Add(pair.Key); + lines.Add(pair.Value.X.ToString(ci)); + lines.Add(pair.Value.Y.ToString(ci)); + } + File.Create(filename).Close(); + File.WriteAllLines(filename, lines); + } + + public void loadPositions(string filename) { + CultureInfo ci = new CultureInfo("en-US"); + string[] lines = File.ReadAllLines(filename); + for(int i = 0; i < lines.Length - 2; i += 3) { + string k = lines[i]; + PointF p = new PointF( + float.Parse(lines[i+1], ci), + float.Parse(lines[i+2], ci) ); + if (positions.ContainsKey(k)) + positions[k] = p; + else + positions.Add(k, p); + } + restorePositions(); + } + + public void remeberPositions() { + foreach(KeyValuePair pair in blocks) + if (positions.ContainsKey(pair.Key)) + positions[pair.Key] = pair.Value.position; + else + positions.Add(pair.Key, pair.Value.position); + } + + public void restorePositions() { + foreach(KeyValuePair pair in blocks) + if (positions.ContainsKey(pair.Key)) + pair.Value.position = positions[pair.Key]; + placeLinks(); + } + + // insert/remove blocks and links calculate sizes + public void reloadDiagram() { + remeberPositions(); + + bool retry; + + // blocks + foreach(KeyValuePair pair in blocks) + pair.Value.block = null; + foreach(KeyValuePair pair in diagram.blocks) { + if (!blocks.ContainsKey(pair.Key)) + blocks.Add(pair.Key, new ActiveBlock()); + ActiveBlock b = blocks[pair.Key]; + b.block = pair.Value; + b.links.Clear(); + b.captionFont = captionFont; + b.textFont = textFont; + b.margin = margin; + b.padding = padding; + b.pen = new Pen(new SolidBrush(b.block.color), pen.Width); + b.brush = brush; + } + retry = true; + while(retry) { + retry = false; + foreach(KeyValuePair pair in blocks) + if (pair.Value.block == null) + { blocks.Remove(pair.Key); retry = true; break; } + } + + // links + foreach(KeyValuePair pair in links) + pair.Value.link = null; + foreach(KeyValuePair pair in diagram.links) { + if ( blocks.ContainsKey(pair.Value.srcId) + && blocks.ContainsKey(pair.Value.dstId) ) + { + if (!links.ContainsKey(pair.Key)) + links.Add(pair.Key, new ActiveLink()); + ActiveLink l = links[pair.Key]; + l.link = pair.Value; + l.src = blocks[l.link.srcId]; + l.src.links.Add(l); + l.dst = blocks[l.link.dstId]; + l.dst.links.Add(l); + l.arrowSize = arrowSize; + l.pen = pen; + } + } + retry = true; + while(retry) { + retry = false; + foreach(KeyValuePair pair in links) + if (pair.Value.link == null) + { links.Remove(pair.Key); retry = true; break; } + } + + measureBlocks(); + } + + public void measureBlocks() { + foreach(KeyValuePair pair in blocks) + pair.Value.measure(); + placeLinks(); + } + + public void placeLinks() { + foreach(KeyValuePair pair in blocks) + pair.Value.placeLinks(); + foreach(KeyValuePair pair in links) + pair.Value.place(); + recalcBounds(); + } + + public void recalcBounds() { + if (blocks.Count == 0) { bounds = new RectangleF(); return; } + float minx = float.PositiveInfinity; + float miny = float.PositiveInfinity; + float maxx = float.NegativeInfinity; + float maxy = float.NegativeInfinity; + foreach(KeyValuePair pair in blocks) { + minx = Math.Min(minx, pair.Value.position.X - pair.Value.margin); + miny = Math.Min(miny, pair.Value.position.Y - pair.Value.margin); + maxx = Math.Max(maxx, pair.Value.position.X + pair.Value.size.Width + pair.Value.margin); + maxy = Math.Max(maxy, pair.Value.position.Y + pair.Value.size.Height + pair.Value.margin); + } + bounds = new RectangleF(minx, miny, maxx - minx, maxy - miny); + } + + public void draw(Graphics g) { + foreach(KeyValuePair pair in links) + pair.Value.draw(g); + foreach(KeyValuePair pair in blocks) + pair.Value.draw(g); + } + } +} + diff --git a/mono/Diagram/DiaSynfig.cs b/mono/Diagram/DiaSynfig.cs new file mode 100644 index 0000000..a22c94d --- /dev/null +++ b/mono/Diagram/DiaSynfig.cs @@ -0,0 +1,91 @@ +using System; +using System.Drawing; + +namespace Diagram { + public class DiaSynfig { + public static Diagram build() { + Color colorSW = Color.DarkRed; + Color colorCommon = Color.Black; + Color colorGL = Color.DarkBlue; + + return new Diagram() + .addBlock( + @"glContext", + @"Windowless OpenGL context", + @"(using EGL)", + colorGL, + new string[] { "glStorage" } + ) + .addBlock( + @"glStorage", + @"Common storage GL resources", + @"Store handles of context, shaders, textures in the RendererGL class instance", + colorGL, + new string[] { "glBlend" } + ) + .addBlock( + @"swBlend", + @"«Blend Task»", + @"for Software rendering", + colorSW, + new string[] { "cGroup" } + ) + .addBlock( + @"cGroup", + @"Render «Groups» (Layer_PasteCanvas)", + @"Build task-trees using «Blend Task» to link tasks of sub-layers.", + colorCommon, + new string[] { "swLayer" } + ) + .addBlock( + @"glBlend", + @"«Blend Task»", + @"for OpenGL", + colorGL, + new string[] { "cAllLayers" } + ) + .addBlock( + @"swLayer", + @"«Render Layer Task»", + @"for layers which yet not supports new rendering engine (Software)", + colorSW, + new string[] { "cAllLayers" } + ) + .addBlock( + @"cThreadSafe", + @"Thread-safe rendering", + @"", + colorCommon, + new string[] { "cAllLayers", "swMultithread", "glMultithread" } + ) + .addBlock( + @"cAllLayers", + @"New rendering for all layers", + @"", + colorCommon, + new string[] { "cFuture" } + ) + .addBlock( + @"swMultithread", + @"Multithreaded software rendering", + @"We have dependency tree of rendering tasks, so we can run several tasks at same time.", + colorSW, + new string[] { "cFuture" } + ) + .addBlock( + @"glMultithread", + @"Multithreaded OpenGL rendering (optional)", + @"There is some restrictions related to hardware acceleration, and multithreading for it will not so effective. PC usually have only one GPU. In best case we can use up to five GPUs - four PCI-x video cards and one integrated video (very expensive!). Also, GPUs uses own memory, so we must remember about transferring data between them.", + colorGL, + new string[] { "cFuture" } + ) + .addBlock( + @"cFuture", + @"Future", + @"Abstraction layer to provide access to rendering tasks for script language (python, lua, etc…), so we can build task-tree for renderer via external script. By this way we can write layers at python, and connect it dynamically without restarting of SynfigStudio.", + colorCommon + ); + } + } +} + diff --git a/mono/Diagram/Diagram.cs b/mono/Diagram/Diagram.cs new file mode 100644 index 0000000..3062ab5 --- /dev/null +++ b/mono/Diagram/Diagram.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace Diagram { + public class Block { + public string id = ""; + public string caption = ""; + public string text = ""; + public Color color = Color.Black; + } + + public class Link { + public string id = ""; + public string srcId = ""; + public string dstId = ""; + } + + public class Diagram { + public readonly Dictionary blocks = new Dictionary(); + public readonly Dictionary links = new Dictionary(); + + public Diagram addBlock(Block block) { + blocks.Add(block.id, block); + return this; + } + + public Diagram addBlock(string id, string caption, string text, Color color, string[] links = null) { + addBlock(new Block() { id = id, caption = caption, text = text, color = color }); + if (links != null) + foreach(string link in links) + addLink(id, link); + return this; + } + + public Diagram addBlock(string id, string caption, string text, string[] links = null) { + addBlock(id, caption, text, Color.Black, links); + return this; + } + + public Diagram addLink(Link link, string srcId = "", string dstId = "") { + if (srcId != "") + link.srcId = srcId; + if (dstId != "") + link.dstId = dstId; + if (link.id == "") + link.id = link.srcId + link.dstId; + links.Add(link.id, link); + return this; + } + + public Diagram addLink(Link link) { + if (link.id == "") + link.id = link.srcId + link.dstId; + links.Add(link.id, link); + return this; + } + + public Diagram addLink(string srcId, string dstId) { + addLink(new Link(), srcId, dstId); + return this; + } + } +} + diff --git a/mono/Diagram/Diagram.csproj b/mono/Diagram/Diagram.csproj new file mode 100644 index 0000000..44f0aca --- /dev/null +++ b/mono/Diagram/Diagram.csproj @@ -0,0 +1,53 @@ + + + + Debug + x86 + {E69A3F38-7F4D-4397-B54E-00716712EAE5} + Exe + Diagram + Diagram + v4.5 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + false + x86 + + + full + true + bin\Release + prompt + 4 + false + x86 + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mono/Diagram/Diagram.sln b/mono/Diagram/Diagram.sln new file mode 100644 index 0000000..dab937d --- /dev/null +++ b/mono/Diagram/Diagram.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Diagram", "Diagram.csproj", "{E69A3F38-7F4D-4397-B54E-00716712EAE5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x86 = Debug|x86 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E69A3F38-7F4D-4397-B54E-00716712EAE5}.Debug|x86.ActiveCfg = Debug|x86 + {E69A3F38-7F4D-4397-B54E-00716712EAE5}.Debug|x86.Build.0 = Debug|x86 + {E69A3F38-7F4D-4397-B54E-00716712EAE5}.Release|x86.ActiveCfg = Release|x86 + {E69A3F38-7F4D-4397-B54E-00716712EAE5}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + StartupItem = Diagram.csproj + EndGlobalSection +EndGlobal diff --git a/mono/Diagram/Geometry.cs b/mono/Diagram/Geometry.cs new file mode 100644 index 0000000..b1d3fa8 --- /dev/null +++ b/mono/Diagram/Geometry.cs @@ -0,0 +1,102 @@ +/* + ......... 2015 Ivan Mahonin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +using System; +using System.Drawing; + +namespace Diagram { + public static class Geometry { + public static readonly float precision = 1e-5f; + + public static readonly float[][] splineMatrix = new float[][] { + new float[] { 2f, -2f, 1f, 1f }, + new float[] { -3f, 3f, -2f, -1f }, + new float[] { 0f, 0f, 1f, 0f }, + new float[] { 1f, 0f, 0f, 0f } }; + + // Compare to points place at line base0-base1 + public static int comparePointsAtLine(PointF a0, PointF a1, PointF base0, PointF base1) { + if (base0.X < base1.X && a0.X < a1.X) return -1; + if (base0.X < base1.X && a1.X < a0.X) return 1; + if (base1.X < base0.X && a0.X < a1.X) return 1; + if (base1.X < base0.X && a1.X < a0.X) return -1; + if (base0.Y < base1.Y && a0.Y < a1.Y) return -1; + if (base0.Y < base1.Y && a1.Y < a0.Y) return 1; + if (base1.Y < base0.Y && a0.Y < a1.Y) return 1; + if (base1.Y < base0.Y && a1.Y < a0.Y) return -1; + return 0; + } + + public static bool findIntersection(PointF a0, PointF a1, PointF b0, PointF b1, out PointF c) { + c = new PointF(0f, 0f); + PointF da = new PointF(a1.X - a0.X, a1.Y - a0.Y); + PointF db = new PointF(b1.X - b0.X, b1.Y - b0.Y); + + float divider = da.X*db.Y - db.X*da.Y; + if (Math.Abs(divider) < precision) return false; + float numeratorX = da.X*(b1.Y*b0.X - b0.Y*b1.X) + - db.X*(a1.Y*a0.X - a0.Y*a1.X); + float numeratorY = db.Y*(a1.X*a0.Y - a0.X*a1.Y) + - da.Y*(b1.X*b0.Y - b0.X*b1.Y); + PointF p = new PointF(numeratorX/divider, numeratorY/divider); + if ( comparePointsAtLine(p, a0, a0, a1) < 0 + || comparePointsAtLine(p, a1, a0, a1) > 0 + || comparePointsAtLine(p, b0, b0, b1) < 0 + || comparePointsAtLine(p, b1, b0, b1) > 0 ) + return false; + + c = p; + return true; + } + + public static float lineLength(PointF p0, PointF p1) { + return (float)Math.Sqrt( + (p1.X-p0.X)*(p1.X-p0.X) + + (p1.Y-p0.Y)*(p1.Y-p0.Y) ); + } + + public static PointF pointAtLine(PointF p0, PointF p1, int index = 0, int count = 1, float padding = 0f) { + float l = lineLength(p0, p1); + float px = l > precision ? (p1.X - p0.X)*padding/l : 0f; + float py = l > precision ? (p1.Y - p0.Y)*padding/l : 0f; + return new PointF( + (index+1)*(p1.X - p0.X - 2*px)/(count + 1) + p0.X + px, + (index+1)*(p1.Y - p0.Y - 2*py)/(count + 1) + p0.Y + py); + } + + public static PointF splineTangent(float s, PointF p0, PointF p1, PointF t0, PointF t1) { + float h1 = 3f*splineMatrix[0][0]*s*s + 2f*splineMatrix[1][0]*s + splineMatrix[2][0]; + float h2 = 3f*splineMatrix[0][1]*s*s + 2f*splineMatrix[1][1]*s + splineMatrix[2][1]; + float h3 = 3f*splineMatrix[0][2]*s*s + 2f*splineMatrix[1][2]*s + splineMatrix[2][2]; + float h4 = 3f*splineMatrix[0][3]*s*s + 2f*splineMatrix[1][3]*s + splineMatrix[2][3]; + return new PointF( + p0.X*h1 + p1.X*h2 + t0.X*h3 + t1.X*h4, + p0.Y*h1 + p1.Y*h2 + t0.Y*h3 + t1.Y*h4); + } + + public static PointF splinePoint(float s, PointF p0, PointF p1, PointF t0, PointF t1) { + float h1 = splineMatrix[0][0]*s*s*s + splineMatrix[1][0]*s*s + splineMatrix[2][0]*s + splineMatrix[3][0]; + float h2 = splineMatrix[0][1]*s*s*s + splineMatrix[1][1]*s*s + splineMatrix[2][1]*s + splineMatrix[3][1]; + float h3 = splineMatrix[0][2]*s*s*s + splineMatrix[1][2]*s*s + splineMatrix[2][2]*s + splineMatrix[3][2]; + float h4 = splineMatrix[0][3]*s*s*s + splineMatrix[1][3]*s*s + splineMatrix[2][3]*s + splineMatrix[3][3]; + return new PointF( + p0.X*h1 + p1.X*h2 + t0.X*h3 + t1.X*h4, + p0.Y*h1 + p1.Y*h2 + t0.Y*h3 + t1.Y*h4 ); + } + } +} + diff --git a/mono/Diagram/MainForm.cs b/mono/Diagram/MainForm.cs new file mode 100644 index 0000000..fe8d930 --- /dev/null +++ b/mono/Diagram/MainForm.cs @@ -0,0 +1,124 @@ +/* + ......... 2015 Ivan Mahonin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +using System; +using System.Windows.Forms; +using System.Collections.Generic; +using System.Linq; +using System.Drawing; + +namespace Diagram { + public class MainForm: Form { + Button bSave; + SaveFileDialog sfdSave; + + ActiveDiagram diagram; + ActiveBlock mouseBlock; + PointF mouseBlockOffset; + + public MainForm() { + diagram = Test.buildTestActiveDiagram(DiaSynfig.build()); + + Width = 800; + Height = 600; + + bSave = new Button(); + bSave.Left = 20; + bSave.Top = 20; + bSave.Text = "save"; + bSave.Click += bTestClicked; + Controls.Add(bSave); + + sfdSave = new SaveFileDialog(); + sfdSave.OverwritePrompt = true; + + Paint += onPaint; + MouseDown += onMouseDown; + MouseUp += onMouseUp; + MouseMove += onMouseMove; + FormClosed += onClose; + + try { diagram.loadPositions("positions.ini"); } catch (Exception) { } + Invalidate(); + } + + void onClose(object sender, EventArgs e) { + try { diagram.savePositions("positions.ini"); } + catch (Exception ex) { MessageBox.Show(ex.Message); } + diagram.savePositions("positions.ini"); + } + + void bTestClicked(object sender, EventArgs e) { + onClose(null, null); + if (sfdSave.ShowDialog() == DialogResult.OK) { + Bitmap b = new Bitmap( + (int)Math.Ceiling(diagram.bounds.Width), + (int)Math.Ceiling(diagram.bounds.Height) ); + Graphics g = Graphics.FromImage(b); + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; + g.FillRectangle(Brushes.White, new RectangleF(0f, 0f, b.Width, b.Height)); + g.TranslateTransform(-diagram.bounds.Left, -diagram.bounds.Top); + diagram.draw(g); + g.Flush(); + b.Save(sfdSave.FileName); + } + } + + void onMouseDown(Object sender, MouseEventArgs e) { + if (e.Button == MouseButtons.Left) { + mouseBlock = null; + + foreach(KeyValuePair pair in diagram.blocks) { + ActiveBlock b = pair.Value; + if ( e.X >= b.position.X + && e.Y >= b.position.Y + && e.X <= b.position.X + b.size.Width + && e.Y <= b.position.Y + b.size.Height ) + { + mouseBlock = b; + mouseBlockOffset = new PointF( + e.X - mouseBlock.position.X, + e.Y - mouseBlock.position.Y ); + //break; + } + } + } + } + + void onMouseUp(Object sender, MouseEventArgs e) { + if (e.Button == MouseButtons.Left) { + mouseBlock = null; + } + } + + void onMouseMove(Object sender, MouseEventArgs e) { + if (mouseBlock != null) { + mouseBlock.position = new PointF( + e.X - mouseBlockOffset.X, + e.Y - mouseBlockOffset.Y ); + diagram.placeLinks(); + Invalidate(); + } + } + + public void onPaint(Object sender, PaintEventArgs e) { + e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; + diagram.draw(e.Graphics); + } + } +} + diff --git a/mono/Diagram/Program.cs b/mono/Diagram/Program.cs new file mode 100644 index 0000000..9e6edca --- /dev/null +++ b/mono/Diagram/Program.cs @@ -0,0 +1,31 @@ +/* + ......... 2015 Ivan Mahonin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +using System; +using System.Windows.Forms; + +namespace Diagram { + public class Program { + [STAThread] + static void Main() { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MainForm()); + } + } +} + diff --git a/mono/Diagram/Shapes.cs b/mono/Diagram/Shapes.cs new file mode 100644 index 0000000..ac3b021 --- /dev/null +++ b/mono/Diagram/Shapes.cs @@ -0,0 +1,29 @@ +using System; +using System.Drawing; + +namespace Diagram { + public class Shapes { + public static void drawRoundRect(Graphics g, RectangleF rect, float radius, Pen pen, Brush brush) { + radius = Math.Min(radius, 0.5f*rect.Width); + radius = Math.Min(radius, 0.5f*rect.Height); + float d = 2f*radius; + + g.FillPie(brush, rect.Left, rect.Top, d, d, -180f, 90f); + g.FillPie(brush, rect.Right-d, rect.Top, d, d, -90f, 90f); + g.FillPie(brush, rect.Right-d, rect.Bottom-d, d, d, 0f, 90f); + g.FillPie(brush, rect.Left, rect.Bottom-d, d, d, 90f, 90f); + g.FillRectangle(brush, rect.Left+radius, rect.Top, rect.Width-d, rect.Height); + g.FillRectangle(brush, rect.Left, rect.Top+radius, rect.Width, rect.Height-d); + + g.DrawArc(pen, rect.Left, rect.Top, d, d, -180f, 90f); + g.DrawArc(pen, rect.Right-d, rect.Top, d, d, -90f, 90f); + g.DrawArc(pen, rect.Right-d, rect.Bottom-d, d, d, 0f, 90f); + g.DrawArc(pen, rect.Left, rect.Bottom-d, d, d, 90f, 90f); + g.DrawLine(pen, rect.Left+radius, rect.Top, rect.Right-radius, rect.Top); + g.DrawLine(pen, rect.Left+radius, rect.Bottom, rect.Right-radius, rect.Bottom); + g.DrawLine(pen, rect.Left, rect.Top+radius, rect.Left, rect.Bottom-radius); + g.DrawLine(pen, rect.Right, rect.Top+radius, rect.Right, rect.Bottom-radius); + } + } +} + diff --git a/mono/Diagram/Test.cs b/mono/Diagram/Test.cs new file mode 100644 index 0000000..9ef18d6 --- /dev/null +++ b/mono/Diagram/Test.cs @@ -0,0 +1,64 @@ +using System; +using System.Drawing; + +namespace Diagram { + public class Test { + public static Diagram buildTestDiagram() { + return new Diagram() + .addBlock( + "b1", + "Block Number One", + "Very cool block, with the best number One. Important thing." + ) + .addBlock( + "b2", + "Block Number Two", + "Very cool block, with the best number Two. Important thing." + ) + .addBlock( + "b3", + "Block Number Three", + "Very cool block, with the best number Three. Important thing.", + Color.Red + ) + .addBlock( + "b4", + "Block Number Four", + "Very cool block, with the best number Four. Important thing." + ) + .addBlock( + "b5", + "Block Number Five", + "Very cool block, with the best number Five. Important thing." + ) + .addLink("b1", "b3") + .addLink("b1", "b4") + .addLink("b2", "b3") + .addLink("b3", "b4") + .addLink("b4", "b5"); + } + + public static ActiveDiagram buildTestActiveDiagram(Diagram diagram) { + ActiveDiagram d = new ActiveDiagram(); + + d.diagram = diagram; + + d.captionFont = new Font(FontFamily.GenericSansSerif, 12f, FontStyle.Bold); + d.textFont = new Font(FontFamily.GenericSansSerif, 10f); + d.margin = d.captionFont.GetHeight(); + d.padding = d.textFont.GetHeight(); + d.arrowSize = new SizeF(10f, 15f); + d.pen = new Pen(Brushes.Black, 3f); + d.brush = Brushes.White; + + d.reloadDiagram(); + + return d; + } + + public static ActiveDiagram buildTestActiveDiagram() { + return buildTestActiveDiagram(buildTestDiagram()); + } + } +} + diff --git a/mono/Diagram/TextUtils.cs b/mono/Diagram/TextUtils.cs new file mode 100644 index 0000000..2f9b13b --- /dev/null +++ b/mono/Diagram/TextUtils.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Data.Linq; +using System.Drawing; + +namespace Diagram { + public class TextUtils { + static Bitmap tmpBitmap = new Bitmap(10, 10); + static Graphics tmpGraphics = Graphics.FromImage(tmpBitmap); + + public static string wrap(string text, double width, Font font) { + string wrapped = ""; + string line = ""; + int wordStart = 0; + for(int i = 0; i <= text.Length; ++i) { + char c = i < text.Length ? text[i] : ' '; + if (char.IsWhiteSpace(c)) { + bool newLine = c == '\n'; + string word = text.Substring(wordStart, i - wordStart); + if (word != "") { + if (line == "") { + line = word; + word = ""; + } else + if (tmpGraphics.MeasureString(line, font).Width <= width) { + line += " " + word; + word = ""; + } else { + newLine = true; + } + } + if (newLine) { + if (wrapped != "") wrapped += "\r\n"; + wrapped += line; + line = word; + } + wordStart = i + 1; + } + } + + if (line != "") { + if (wrapped != "") wrapped += "\r\n"; + wrapped += line; + } + return wrapped; + } + + public static SizeF measure(string text, Font font) { + return tmpGraphics.MeasureString(text, font); + } + } +} +