#include "RichText.h" namespace Upp { Vector DiagramItem::GetConnections() const { Vector p; if(IsLine()) { p << pt[0] << pt[1]; return p; } Rectf r = GetRect(); p << r.TopCenter() << r.BottomCenter(); if(shape != SHAPE_PARALLELOGRAM) p << r.CenterLeft() << r.CenterRight(); return p; } void DiagramItem::Paint(Painter& w, dword style, const Index *conn) const { bool dark = style & DARK; auto SC = [&](Color c) { return dark ? DarkThemeCached(c) : c; }; Color ink = SC(this->ink); Color paper = SC(this->paper); PaintInfo pi; pi.darktheme = dark; pi.zoom = Diagram::TextZoom(); RichText txt = ParseQTF(qtf); static Vector dashes[5] = { { 0 }, { 1, 1 }, { 2 }, { 1, 2 }, { 1, 2 } }; auto DoDash = [&] { if(dash) { Vector d = clone(dashes[clamp(dash, 0, __countof(dashes))]); for(double& h : d) h *= width; w.Dash(d, 0); } }; RGBA sel1 = 150 * SColorHighlight(); RGBA sel2 = 150 * Gray(); auto Stroke = [&] { if(width) w.Stroke(width, ink); else if(style & GRID) w.Stroke(0.2, sel1); }; if(IsLine()) { Pointf v = pt[1] - pt[0]; if(style) { w.Move(pt[0]).Line(pt[1]).EndPath(); w.Begin(); if((style & EDITOR) && width == 0) w.Dash("5 1").Stroke(1, 100 * sel2); if(style & (Display::CURSOR | Display::SELECT)) { w.LineCap(LINECAP_ROUND).Stroke(width + 12, (style & Display::SELECT ? 30 : 200) * sel2); double r = (width + 12) / 2 - 1; w.Circle(pt[0], r).Fill(sel1); w.Circle(pt[1], r).Fill(sel1); } w.End(); } double d = Length(v); v = Upp::Normalize(v); Pointf a1 = pt[0]; Pointf a2 = pt[1]; if(d > 4 * width) { // enough length to have caps if(cap[0] == CAP_ARROW) a1 += v * 4 * width; if(cap[1] == CAP_ARROW) a2 -= v * 4 * width; } w.Move(a1).Line(a2); DoDash(); Stroke(); Pointf o = Orthogonal(v); if(d > 4 * width) { auto PaintCap = [&](int k, Pointf p, Pointf a) { Pointf oo = max(3.0, width * 2) * o; switch(k) { case CAP_NONE: w.Circle(p, width / 2).Fill(ink); break; case CAP_ARROW: w.Move(p).Line(a + oo).Line(a - oo).Fill(ink); break; case CAP_CIRCLE: w.Circle(p, 5).Fill(ink); break; } }; PaintCap(cap[0], pt[0], a1 + v); PaintCap(cap[1], pt[1], a2 - v); } int cx = Distance(pt[0], pt[1]); int txt_cy = txt.GetHeight(pi.zoom, cx); w.Begin(); double angle = Bearing(pt[1] - pt[0]); if(angle >= -M_PI / 2 && angle <= M_PI / 2) { w.Translate(pt[0] - o * (txt_cy + 10)); w.Rotate(angle); } else { w.Translate(pt[1] + o * (txt_cy + 10)); w.Rotate(angle + M_PI); } txt.Paint(w, 0, 0, cx, pi); w.End(); } else { if(style & (Display::CURSOR | Display::SELECT)) { w.RoundedRectangle(GetRect(), 5) .Fill((style & Display::SELECT ? 30 : 200) * sel2); w.RoundedRectangle(GetRect().Inflated(2), 5) .Stroke(6, (style & Display::SELECT ? 30 : 200) * sel1); } int txt_cy = txt.GetHeight(pi.zoom, GetRect().GetWidth()); Rectf r(pt[0], pt[1]); r.Normalize(); r.Deflate(width / 2); Pointf c = r.CenterPoint(); int sz = min(r.Width(), r.Height()); switch(shape) { case SHAPE_ROUNDRECT: w.RoundedRectangle(r.left, r.top, r.GetWidth(), r.GetHeight(), sz > 30 ? 8 : sz > 15 ? 4 : 2); break; case SHAPE_OVAL: if(r.GetWidth() > r.GetHeight()) { double ra = r.GetHeight() / 2; w.Move(r.left + ra, r.top) .Line(r.right - ra, r.top) .Arc(r.right - ra, r.top + ra, ra, -M_PI / 2, M_PI) .Line(r.left + ra, r.bottom) .Arc(r.left + ra, r.top + ra, ra, M_PI / 2, M_PI); break; } case SHAPE_ELLIPSE: w.Ellipse(r); break; case SHAPE_DIAMOND: w.Move(c.x, r.top).Line(r.right, c.y).Line(c.x, r.bottom).Line(r.left, c.y).Close(); break; case SHAPE_PARALLELOGRAM: w.Move(r.left + r.Width() / 6, r.top).Line(r.right, r.top) .Line(r.right - r.Width() / 6, r.bottom).Line(r.left, r.bottom).Close(); break; default: w.Rectangle(r); break; } DoDash(); w.Fill(paper); Stroke(); txt.Paint(w, r.left, r.top + (r.GetHeight() - txt_cy) / 2, r.GetWidth(), pi); if(style & GRID) for(Pointf p : GetConnections()) { w.Circle(p, 5); if(conn && conn->Find(p) >= 0) w.Fill(128 * SYellow()); w.Stroke(1, 190 * SColorHighlight()); } } } }