ultimatepp/uppsrc/RichText/DiagramShape.cpp
2025-07-27 09:35:53 +02:00

351 lines
No EOL
8.7 KiB
C++

#include "RichText.h"
namespace Upp {
Index<String> DiagramItem::Shape = { "line", "rect", "round_rect",
"ellipse", "diamond", "oval", "parallelogram",
"cylinder",
"triangle1", "triangle2",
"arrow_left", "arrow_right", "arrow_horz",
"arrow_down", "arrow_up", "arrow_vert",
"svgpath",
};
Vector<Pointf> DiagramItem::GetConnections() const
{
Vector<Pointf> p;
if(shape > SHAPE_CYLINDER)
return 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<Pointf> *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<double> dashes[5] = { { 0 }, { 1, 1 }, { 2 }, { 1, 2 }, { 1, 2 } };
auto DoDash = [&] {
if(dash) {
Vector<double> 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);
}
Rectf r(pt[0], pt[1]);
r.Normalize();
r.Deflate(width / 2);
Rect text_rect = r.Deflated(width + 2, 0);
Pointf c = r.CenterPoint();
double sz = min(r.Width(), r.Height());
double arrow_width = min(r.Width() / 3, r.Height() / 2);
double h = r.GetHeight();
double h4 = h / 4;
double th4 = r.top + h4;
double bh4 = r.bottom - h4;
double arrow_height = min(r.Height() / 3, r.Width() / 2);
double w1 = r.GetWidth();
double w2 = w1 / 2;
double w4 = w1 / 4;
double lw4 = r.left + w4;
double rw4 = r.right - w4;
double hc, thc, bhc; // cylinder
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;
case SHAPE_CYLINDER:
text_rect.top += int(w1 / 4);
hc = h / 6;
thc = r.top + hc;
bhc = r.bottom - hc;
w.Move(r.left, thc)
.Arc(r.left + w2, thc, w2, hc, M_PI, M_PI)
.Line(r.right, bhc)
.Arc(r.left + w2, bhc, w2, hc, 0, M_PI)
.Line(r.left, bhc);
break;
case SHAPE_TRIANGLE: {
text_rect.left += int(r.Width() / 4);
text_rect.right -= int(r.Width() / 4);
text_rect.top += int(r.Width() / 3);
w.Move(r.left + r.Width() / 2, r.top)
.Line(r.right, r.bottom)
.Line(r.left, r.bottom)
.Close();
}
break;
case SHAPE_ITRIANGLE: {
text_rect.left += int(r.Width() / 4);
text_rect.right -= int(r.Width() / 4);
text_rect.bottom -= int(r.Width() / 3);
w.Move(r.left + r.Width() / 2, r.bottom)
.Line(r.right, r.top)
.Line(r.left, r.top)
.Close();
}
break;
case SHAPE_ARROWLEFT: {
double a = r.left + arrow_width;
text_rect.left += int(arrow_width / 3);
w.Move(r.left, r.top + r.Height() / 2)
.Line(a, r.top)
.Line(a, th4)
.Line(r.right, th4)
.Line(r.right, bh4)
.Line(a, bh4)
.Line(a, r.bottom)
.Close();
}
break;
case SHAPE_ARROWRIGHT:
{
double a = r.right - arrow_width;
text_rect.right -= int(arrow_width / 3);
w.Move(r.right, r.top + r.Height() / 2)
.Line(a, r.top)
.Line(a, th4)
.Line(r.left, th4)
.Line(r.left, bh4)
.Line(a, bh4)
.Line(a, r.bottom)
.Close();
}
break;
case SHAPE_ARROWHORZ:
{
double a1 = r.left + arrow_width;
text_rect.left += int(arrow_width / 3);
double a2 = r.right - arrow_width;
text_rect.right -= int(arrow_width / 3);
w.Move(r.left, r.top + r.Height() / 2)
.Line(a1, r.top)
.Line(a1, th4)
.Line(a2, th4)
.Line(a2, r.top)
.Line(r.right, r.top + r.Height() / 2)
.Line(a2, r.bottom)
.Line(a2, bh4)
.Line(a1, bh4)
.Line(a1, r.bottom)
.Close();
}
break;
case SHAPE_ARROWUP: {
double a = r.top + arrow_height;
text_rect.left += w4;
text_rect.right -= w4;
text_rect.top += 3 * arrow_height / 4;
w.Move(r.left + r.Width() / 2, r.top)
.Line(r.right, a)
.Line(rw4, a)
.Line(rw4, r.bottom)
.Line(lw4, r.bottom)
.Line(lw4, a)
.Line(r.left, a)
.Close();
}
break;
case SHAPE_ARROWDOWN: {
double a = r.bottom - arrow_height;
text_rect.left += w4;
text_rect.right -= w4;
text_rect.bottom -= 3 * arrow_height / 4;
w.Move(r.left + r.Width() / 2, r.bottom)
.Line(r.right, a)
.Line(rw4, a)
.Line(rw4, r.top)
.Line(lw4, r.top)
.Line(lw4, a)
.Line(r.left, a)
.Close();
}
break;
case SHAPE_ARROWVERT: {
double a1 = r.top + arrow_height;
double a2 = r.bottom - arrow_height;
text_rect.left += w4;
text_rect.right -= w4;
w.Move(r.left + r.Width() / 2, r.top)
.Line(r.right, a1)
.Line(rw4, a1)
.Line(rw4, a2)
.Line(r.right, a2)
.Line(r.left + r.Width() / 2, r.bottom)
.Line(r.left, a2)
.Line(lw4, a2)
.Line(lw4, a1)
.Line(r.left, a1)
.Close();
}
break;
case SHAPE_SVGPATH:
if(data.GetCount() && !IsNull(size)) {
w.Begin();
w.Offset(r.TopLeft());
w.Scale(w1 / size.cx, h / size.cy);
w.Path(data);
}
break;
default:
w.Rectangle(r);
break;
}
DoDash();
w.Fill(paper);
Stroke();
switch(shape) {
case SHAPE_CYLINDER:
w.Move(r.left, thc)
.Arc(r.left + w2, thc, w2, hc, M_PI, -M_PI);
DoDash();
Stroke();
break;
case SHAPE_SVGPATH:
if(data.GetCount() && !IsNull(size))
w.End();
break;
}
int txt_cy = txt.GetHeight(pi.zoom, text_rect.GetWidth());
txt.Paint(w, text_rect.left, text_rect.top + (text_rect.GetHeight() - txt_cy) / 2, text_rect.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());
}
}
}
}