1 #include "gui/windows/CurveDialog.h"
2 #include "gui/Utils.h"
3 #include "post/Plot.h"
4 #include "post/Point.h"
5 #include <wx/graphics.h>
6
7 NAMESPACE_SPH_BEGIN
8
CurvePanel(wxWindow * parent)9 CurvePanel::CurvePanel(wxWindow* parent)
10 : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(800, 600)) {
11 this->Connect(wxEVT_PAINT, wxPaintEventHandler(CurvePanel::onPaint));
12 this->Connect(wxEVT_MOTION, wxMouseEventHandler(CurvePanel::onMouseMotion));
13 this->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(CurvePanel::onLeftDown));
14 this->Connect(wxEVT_LEFT_UP, wxMouseEventHandler(CurvePanel::onLeftUp));
15 this->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(CurvePanel::onRightUp));
16 }
17
18 const float radius = 6;
19
onPaint(wxPaintEvent & UNUSED (evt))20 void CurvePanel::onPaint(wxPaintEvent& UNUSED(evt)) {
21 wxPaintDC dc(this);
22
23 dc.SetPen(*wxWHITE_PEN);
24
25 wxGraphicsContext* gc = wxGraphicsContext::Create(dc);
26 wxPen pen = *wxWHITE_PEN;
27 pen.SetWidth(2);
28 gc->SetPen(pen);
29 wxBrush brush = *wxWHITE_BRUSH;
30 brush.SetColour(wxColour(100, 100, 100));
31 gc->SetBrush(brush);
32
33 // draw the curve
34 for (Size i = 0; i < curve.getPointCnt() - 1; ++i) {
35 wxGraphicsPath path = gc->CreatePath();
36 const int x1 = curveToWindow<wxPoint>(curve.getPoint(i)).x;
37 const int x2 = curveToWindow<wxPoint>(curve.getPoint(i + 1)).x;
38
39 if (i == highlightSegment) {
40 pen.SetColour(wxColour(255, 100, 50));
41 } else {
42 pen.SetColour(wxColour(180, 180, 180));
43 }
44 gc->SetPen(pen);
45
46 for (int x = x1; x <= x2; ++x) {
47 const float f = float(curve(windowToCurve(wxPoint(x, 0)).x));
48 const float y = float(curveToWindow<wxPoint2DDouble, double>(CurvePoint{ 0.f, f }).m_y);
49 const wxPoint2DDouble p(x, y);
50 if (x > padding) {
51 path.AddLineToPoint(p);
52 } else {
53 path.MoveToPoint(p);
54 }
55 }
56 gc->StrokePath(path);
57 }
58
59 // draw the points
60 pen.SetColour(wxColour(180, 180, 180));
61 gc->SetPen(pen);
62 for (Size i = 0; i < curve.getPointCnt(); ++i) {
63 const wxPoint p = curveToWindow<wxPoint>(curve.getPoint(i));
64 if (highlightIdx == i) {
65 brush.SetColour(wxColour(255, 100, 50));
66 } else {
67 brush.SetColour(wxColour(100, 100, 100));
68 }
69 gc->SetBrush(brush);
70 gc->DrawEllipse(p.x - radius, p.y - radius, 2 * radius, 2 * radius);
71 }
72
73 // draw axes
74 wxSize size = this->GetSize() - wxSize(2 * padding, 2 * padding);
75 dc.DrawLine(wxPoint(padding, padding + size.y), wxPoint(padding + size.x, padding + size.y));
76 dc.DrawLine(wxPoint(padding, padding), wxPoint(padding, padding + size.y));
77
78 // draw tics
79 const Interval rangeX = curve.rangeX();
80 Array<Float> ticsX = getLinearTics(rangeX, 4);
81 for (Size i = 0; i < ticsX.size(); ++i) {
82 const String label = toPrintableString(ticsX[i], 1);
83 const int x = padding + i * size.x / (ticsX.size() - 1);
84 drawTextWithSubscripts(dc, label, wxPoint(x - 6, size.y + padding + 6));
85 dc.DrawLine(wxPoint(x, size.y + padding - 2), wxPoint(x, size.y + padding + 2));
86 }
87 const Interval rangeY = curve.rangeY();
88 Array<Float> ticsY = getLinearTics(rangeY, 3);
89 for (Size i = 0; i < ticsY.size(); ++i) {
90 const String label = toPrintableString(ticsY[i], 1);
91 const int y = padding + size.y - i * size.y / (ticsY.size() - 1);
92 drawTextWithSubscripts(dc, label, wxPoint(2, y - 8));
93 dc.DrawLine(wxPoint(padding - 2, y), wxPoint(padding + 2, y));
94 }
95
96 // draw mouse position and curve value
97 if (mousePosition != wxDefaultPosition) {
98 dc.SetPen(*wxGREY_PEN);
99 // project the mouse position to the curve
100 CurvePoint curvePos = windowToCurve(mousePosition);
101 curvePos.y = curve(curvePos.x);
102 const wxPoint center = curveToWindow<wxPoint>(curvePos);
103 dc.DrawLine(wxPoint(padding, center.y), wxPoint(padding + size.x, center.y));
104 dc.DrawLine(wxPoint(center.x, padding), wxPoint(center.x, padding + size.y));
105 dc.SetTextForeground(wxColour(128, 128, 128));
106 wxFont font = dc.GetFont().Smaller();
107 dc.SetFont(font);
108 const String labelX = toPrintableString(curvePos.x, 2);
109 const String labelY = toPrintableString(curvePos.y, 2);
110 drawTextWithSubscripts(dc, L"(" + labelX, center + wxPoint(-65, -15));
111 drawTextWithSubscripts(dc, labelY + L")", center + wxPoint(5, -15));
112 }
113
114 delete gc;
115 }
116
onMouseMotion(wxMouseEvent & evt)117 void CurvePanel::onMouseMotion(wxMouseEvent& evt) {
118 mousePosition = evt.GetPosition();
119 highlightIdx = this->getIdx(mousePosition);
120 highlightSegment = this->getSegment(mousePosition);
121 if (evt.Dragging() && lockedIdx) {
122 CurvePoint newPos = windowToCurve(evt.GetPosition());
123 curve.setPoint(lockedIdx.value(), newPos);
124 }
125 this->Refresh();
126 }
127
onLeftDown(wxMouseEvent & evt)128 void CurvePanel::onLeftDown(wxMouseEvent& evt) {
129 mousePosition = evt.GetPosition();
130 Optional<Size> idx = this->getIdx(mousePosition);
131 if (idx) {
132 lockedIdx = idx;
133 } else {
134 const CurvePoint newPos = windowToCurve(mousePosition);
135 curve.addPoint(newPos);
136 lockedIdx = this->getIdx(mousePosition);
137 }
138 this->Refresh();
139 }
140
onLeftUp(wxMouseEvent & UNUSED (evt))141 void CurvePanel::onLeftUp(wxMouseEvent& UNUSED(evt)) {
142 lockedIdx = NOTHING;
143 }
144
onRightUp(wxMouseEvent & evt)145 void CurvePanel::onRightUp(wxMouseEvent& evt) {
146 mousePosition = evt.GetPosition();
147 if (Optional<Size> pointIdx = this->getIdx(mousePosition)) {
148 curve.deletePoint(pointIdx.value());
149 } else if (Optional<Size> segmentIdx = this->getSegment(mousePosition)) {
150 curve.setSegment(segmentIdx.value(), !curve.getSegment(segmentIdx.value()));
151 }
152 this->Refresh();
153 }
154
155 template <typename TPoint, typename T>
156 TPoint CurvePanel::curveToWindow(const CurvePoint& p) const {
157 wxSize size = this->GetSize() - wxSize(2 * padding, 2 * padding);
158 const Interval rangeX = curve.rangeX();
159 const Interval rangeY = curve.rangeY();
160
161 return TPoint(T(padding + (p.x - rangeX.lower()) / rangeX.size() * size.x),
162 T(padding + size.y - (p.y - rangeY.lower()) / rangeY.size() * size.y));
163 }
164
windowToCurve(const wxPoint2DDouble p) const165 CurvePoint CurvePanel::windowToCurve(const wxPoint2DDouble p) const {
166 wxSize size = this->GetSize() - wxSize(2 * padding, 2 * padding);
167 const Interval rangeX = curve.rangeX();
168 const Interval rangeY = curve.rangeY();
169 return CurvePoint{ float((p.m_x - padding) * rangeX.size() / size.x + rangeX.lower()),
170 float(rangeY.size() - (p.m_y - padding) * rangeY.size() / size.y + rangeY.lower()) };
171 }
172
getIdx(const wxPoint mousePos) const173 Optional<Size> CurvePanel::getIdx(const wxPoint mousePos) const {
174 for (Size i = 0; i < curve.getPointCnt(); ++i) {
175 wxPoint dist = curveToWindow<wxPoint>(curve.getPoint(i)) - mousePos;
176 if (sqr(dist.x) + sqr(dist.y) < sqr(radius)) {
177 return i;
178 }
179 }
180 return NOTHING;
181 }
182
getSegment(const wxPoint mousePos) const183 Optional<Size> CurvePanel::getSegment(const wxPoint mousePos) const {
184 for (Size i = 0; i < curve.getPointCnt() - 1; ++i) {
185 const wxPoint p1 = curveToWindow<wxPoint>(curve.getPoint(i));
186 const wxPoint p2 = curveToWindow<wxPoint>(curve.getPoint(i + 1));
187 if (mousePos.x > p1.x && mousePos.x < p2.x) {
188 CurvePoint m = windowToCurve(mousePos);
189 m.y = curve(m.x);
190 const wxPoint projPos = curveToWindow<wxPoint>(m);
191 if (abs(mousePos.y - projPos.y) < radius) {
192 return i;
193 }
194 }
195 }
196 return NOTHING;
197 }
198
CreateControls(wxPropertyGrid * propgrid,wxPGProperty * property,const wxPoint & pos,const wxSize & size) const199 wxPGWindowList CurveEditor::CreateControls(wxPropertyGrid* propgrid,
200 wxPGProperty* property,
201 const wxPoint& pos,
202 const wxSize& size) const {
203 (void)propgrid;
204 (void)property;
205 (void)pos;
206 (void)size;
207 CurveProperty* curve = dynamic_cast<CurveProperty*>(property);
208 CurveDialog* dialog =
209 new CurveDialog(propgrid, curve->getCurve(), [propgrid, property](const Curve& curve) {
210 wxPropertyGridEvent* changeEvent = new wxPropertyGridEvent(wxEVT_PG_CHANGED);
211 changeEvent->SetProperty(property);
212 CurveProperty* curveProp = dynamic_cast<CurveProperty*>(property);
213 SPH_ASSERT(curveProp);
214 curveProp->setCurve(curve);
215 propgrid->GetEventHandler()->ProcessEvent(*changeEvent);
216 });
217 dialog->Show();
218 return wxPGWindowList(nullptr);
219 }
220
UpdateControl(wxPGProperty * property,wxWindow * ctrl) const221 void CurveEditor::UpdateControl(wxPGProperty* property, wxWindow* ctrl) const {
222 (void)property;
223 (void)ctrl;
224 }
225
DrawValue(wxDC & dc,const wxRect & rect,wxPGProperty * property,const wxString & text) const226 void CurveEditor::DrawValue(wxDC& dc,
227 const wxRect& rect,
228 wxPGProperty* property,
229 const wxString& text) const {
230 dc.SetBrush(*wxBLACK_BRUSH);
231 dc.DrawRectangle({ 0, 0 }, { 200, 100 });
232 (void)rect;
233 (void)property;
234 (void)text;
235 }
236
OnEvent(wxPropertyGrid * propgrid,wxPGProperty * property,wxWindow * wnd_primary,wxEvent & event) const237 bool CurveEditor::OnEvent(wxPropertyGrid* propgrid,
238 wxPGProperty* property,
239 wxWindow* wnd_primary,
240 wxEvent& event) const {
241 (void)propgrid;
242 (void)property;
243 (void)wnd_primary;
244 (void)event;
245 return true;
246 }
247
CurveDialog(wxWindow * parent,const Curve & curve,Function<void (const Curve &)> curveChanged)248 CurveDialog::CurveDialog(wxWindow* parent, const Curve& curve, Function<void(const Curve&)> curveChanged)
249 : wxFrame(parent, wxID_ANY, "Curve", wxDefaultPosition, wxSize(600, 450))
250 , curveChanged(curveChanged) {
251 wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
252 CurvePanel* panel = new CurvePanel(this);
253 panel->setCurve(curve);
254 sizer->Add(panel, 1, wxEXPAND | wxALL);
255
256 wxBoxSizer* buttonSizer = new wxBoxSizer(wxHORIZONTAL);
257 wxButton* okButton = new wxButton(this, wxID_ANY, "OK");
258 okButton->Bind(wxEVT_BUTTON, [this, panel](wxCommandEvent& UNUSED(evt)) {
259 this->curveChanged(panel->getCurve());
260 this->Close();
261 });
262 buttonSizer->Add(okButton, 0);
263
264 wxButton* cancelButton = new wxButton(this, wxID_ANY, "Cancel");
265 cancelButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent& UNUSED(evt)) { this->Close(); });
266 buttonSizer->Add(cancelButton, 0);
267
268 sizer->Add(buttonSizer);
269
270 this->SetSizer(sizer);
271 this->Layout();
272 }
273
274 NAMESPACE_SPH_END
275