1 // =================================
  2 // Copyright (c) 2021 Seppo Laakko
  3 // Distributed under the MIT license
  4 // =================================
  5 
  6 using System;
  7 using System.Collections;
  8 
  9 namespace System.Windows
 10 {
 11     public nothrow Color DefaultPathViewTextColor()
 12     {
 13         return Color.Black();
 14     }
 15 
 16     public nothrow Color DefaultPathViewTickColor()
 17     {
 18         return Color.Black();
 19     }
 20 
 21     public nothrow Color DefaultPathViewMouseOverColor()
 22     {
 23         return Color(230u243u255u);
 24     }
 25 
 26     public nothrow Color DefaultPathViewMouseClickColor()
 27     {
 28         return Color(204u232u255u);
 29     }
 30 
 31     public nothrow Padding DefaultPathViewPathComponentPadding()
 32     {
 33         return Padding(2626);
 34     }
 35 
 36     public nothrow Padding DefaultPathViewTickPadding()
 37     {
 38         return Padding(4646);
 39     }
 40 
 41     public nothrow string DefaultPathViewFontFamilyName()
 42     {
 43         return "Segoe UI";
 44     }
 45 
 46     public nothrow float DefaultPathViewFontSize()
 47     {
 48         return 9.0f;
 49     }
 50 
 51     public nothrow float DefaultPathViewTickSizePercent()
 52     {
 53         return 40.0f;
 54     }
 55 
 56     public class PathComponentEventArgs
 57     {
 58         public nothrow PathComponentEventArgs(PathComponent* pathComponent_) : pathComponent(pathComponent_)
 59         {
 60         }
 61         public PathComponent* pathComponent;
 62     }
 63 
 64     public class delegate void PathComponentSelectedEventHandler(PathComponentEventArgs& args);
 65 
 66     public nothrow ControlCreateParams& PathViewControlCreateParams(ControlCreateParams& controlCreateParams)
 67     {
 68         return controlCreateParams.SetWindowClassName("System.Windows.PathView").
 69             SetWindowClassBackgroundColor(SystemColor.COLOR_WINDOW).
 70             SetBackgroundColor(Color.White());
 71     }
 72 
 73     public class PathViewCreateParams
 74     {
 75         public nothrow PathViewCreateParams(ControlCreateParams& controlCreateParams_) : 
 76             controlCreateParams(controlCreateParams_)
 77             fontFamilyName(DefaultPathViewFontFamilyName())
 78             fontSize(DefaultPathViewFontSize())
 79             textColor(DefaultPathViewTextColor())
 80             tickSizePercent(DefaultPathViewTickSizePercent())
 81             tickColor(DefaultPathViewTickColor())
 82             pathComponentPadding(DefaultPathViewPathComponentPadding())
 83             tickPadding(DefaultPathViewTickPadding())
 84             mouseOverColor(DefaultPathViewMouseOverColor())
 85             mouseClickColor(DefaultPathViewMouseClickColor())
 86         {
 87         }
 88         public nothrow PathViewCreateParams& Defaults()
 89         {
 90             return *this;
 91         }
 92         public nothrow PathViewCreateParams& SetFontFamilyName(const string& fontFamilyName_)
 93         {
 94             fontFamilyName = fontFamilyName_;
 95             return *this;
 96         }
 97         public nothrow PathViewCreateParams& SetFontSize(float fontSize_)
 98         {
 99             fontSize = fontSize_;
100             return *this;
101         }
102         public nothrow PathViewCreateParams& SetTextColor(const Color& textColor_)
103         {
104             textColor = textColor_;
105             return *this;
106         }
107         public nothrow PathViewCreateParams& SetTickSizePercent(float tickSizePercent_)
108         {
109             tickSizePercent = tickSizePercent_;
110             return *this;
111         }
112         public nothrow PathViewCreateParams& SetTickColor(const Color& tickColor_)
113         {
114             tickColor = tickColor_;
115             return *this;
116         }
117         public nothrow PathViewCreateParams& SetPathComponentPadding(const Padding& pathComponentPadding_)
118         {
119             pathComponentPadding = pathComponentPadding_;
120             return *this;
121         }
122         public nothrow PathViewCreateParams& SetTickPadding(const Padding& tickPadding_)
123         {
124             tickPadding = tickPadding_;
125             return *this;
126         }
127         public nothrow PathViewCreateParams& SetMouseOverColor(const Color& mouseOverColor_)
128         {
129             mouseOverColor = mouseOverColor_;
130             return *this;
131         }
132         public nothrow PathViewCreateParams& SetMouseClickColor(const Color& mouseClickColor_)
133         {
134             mouseClickColor = mouseClickColor_;
135             return *this;
136         }
137         public ControlCreateParams& controlCreateParams;
138         public string fontFamilyName;
139         public float fontSize;
140         public Color textColor;
141         public float tickSizePercent;
142         public Color tickColor;
143         public Padding pathComponentPadding;
144         public Padding tickPadding;
145         public Color mouseOverColor;
146         public Color mouseClickColor;
147     }
148 
149     public class PathView : Control
150     {
151         public nothrow PathView(PathViewCreateParams& createParams) : 
152             base(createParams.controlCreateParams)
153             pathComponents(this)
154             tickSizePercent(createParams.tickSizePercent)
155             textBrush(createParams.textColor)
156             tickBrush(createParams.tickColor)
157             mouseOverBrush(createParams.mouseOverColor)
158             mouseClickBrush(createParams.mouseClickColor)
159             pathComponentPadding(createParams.pathComponentPadding)
160             tickPadding(createParams.tickPadding)
161             textHeight(0)
162             tickHeight(0)
163             tickWidth(0)
164             sqrt3per2(0)
165             maxWidth(0)
166             mouseDownComponent(null)
167             mouseOverComponent(null)
168         {
169             SetFont(Font(FontFamily(createParams.fontFamilyName)createParams.fontSize));
170             sqrt3per2 = cast<float>(Sqrt(3.0) / 2);
171             SetSize(Size(1010));
172             Invalidate();
173         }
174         public nothrow void Clear()
175         {
176             mouseDownComponent = null;
177             mouseOverComponent = null;
178             if (!pathComponents.IsEmpty())
179             {
180                 while (!pathComponents.IsEmpty())
181                 {
182                     pathComponents.RemoveChild(pathComponents.FirstChild());
183                 }
184                 Invalidate();
185             }
186         }
187         public nothrow void SetMaxWidth(int maxWidth_)
188         {
189             if (maxWidth != maxWidth_)
190             {
191                 maxWidth = maxWidth_;
192                 Size sz = GetSize();
193                 sz.w = maxWidth;
194                 SetSize(sz);
195                 Invalidate();
196             }
197         }
198         public nothrow void AddPathComponent(const string& pathComponentNamevoid* data)
199         {
200             PathComponent* pathComponent = new PathComponent(thispathComponentNamedata);
201             pathComponents.AddChild(pathComponent);
202             Invalidate();
203         }
204         public nothrow void PushPathComponent(const string& pathComponentNamevoid* data)
205         {
206             PathComponent* pathComponent = new PathComponent(thispathComponentNamedata);
207             if (!pathComponents.IsEmpty())
208             {
209                 pathComponents.InsertBefore(pathComponentpathComponents.FirstChild());
210             }
211             else
212             {
213                 pathComponents.AddChild(pathComponent);
214             }
215             Invalidate();
216         }
217         public inline nothrow const Padding& GetPathComponentPadding() const
218         {
219             return pathComponentPadding;
220         }
221         public inline nothrow const Brush& GetTextBrush() const
222         {
223             return textBrush;
224         }
225         public inline nothrow const Brush& MouseOverBrush() const
226         {
227             return mouseOverBrush;
228         }
229         public inline nothrow const Brush& MouseClickBrush() const
230         {
231             return mouseClickBrush;
232         }
233         public nothrow void SetTextHeight(float textHeight_)
234         {
235             textHeight = Max(textHeighttextHeight_);
236         }
237         public nothrow Event<PathComponentSelectedEventHandlerPathComponentEventArgs>& PathComponentSelectedEvent()
238         {
239             return pathComponentSelectedEvent;
240         }
241         protected override void OnPaint(PaintEventArgs& args)
242         {
243             Measure(args.graphics);
244             Component* startChild = null;
245             if (maxWidth > 0)
246             {
247                 PointF origin(00);
248                 int width = 0;
249                 Component* child = pathComponents.LastChild();
250                 bool first = true;
251                 while (child != null)
252                 {
253                     if (child is PathComponent*)
254                     {
255                         if (first)
256                         {
257                             first = false;
258                         }
259                         else
260                         {
261                             width = cast<int>(width + tickWidth + tickPadding.Horizontal());
262                         }
263                         PathComponent* pathComponent = cast<PathComponent*>(child);
264                         Size sz = pathComponent->GetSize();
265                         if (width + sz.w > maxWidth)
266                         {
267                             break;
268                         }
269                         width = width + sz.w;
270                         startChild = child;
271                     }
272                     child = child->PrevSibling();
273                 }
274             }
275             else
276             {
277                 startChild = pathComponents.FirstChild();
278             }
279             args.graphics.Clear(BackgroundColor());
280             startChild = pathComponents.FirstChild();
281             PointF origin(00);
282             Component* child = startChild;
283             bool first = true;
284             while (child != null)
285             {
286                 if (child is PathComponent*)
287                 {
288                     if (first)
289                     {
290                         first = false;
291                     }
292                     else
293                     {
294                         DrawTick(args.graphicsorigin);
295                     }
296                     PathComponent* pathComponent = cast<PathComponent*>(child);
297                     pathComponent->SetLocation(Point(cast<int>(origin.x + 0.5f)cast<int>(origin.y + 0.5f)));
298                     pathComponent->Draw(args.graphics);
299                     Size sz = pathComponent->GetSize();
300                     origin.x = origin.x + sz.w;
301                 }
302                 child = child->NextSibling();
303             }
304         }
305         protected override void OnMouseDown(MouseEventArgs& args)
306         {
307             PathComponent* pathComponent = PathComponentAt(args.location);
308             if (pathComponent != null)
309             {
310                 mouseDownComponent = pathComponent;
311                 mouseDownComponent->SetState(PathComponent.State.mouseClick);
312             }
313             else
314             {
315                 mouseDownComponent = null;
316             }
317         }
318         protected override void OnMouseUp(MouseEventArgs& args)
319         {
320             PathComponent* pathComponent = PathComponentAt(args.location);
321             if (pathComponent != null)
322             {
323                 pathComponent->SetState(PathComponent.State.idle);
324                 if (pathComponent == mouseDownComponent)
325                 {
326                     PathComponentEventArgs args(pathComponent);
327                     OnPathComponentSelected(args);
328                 }
329             }
330             mouseDownComponent = null;
331         }
332         protected override void OnMouseMove(MouseEventArgs& args)
333         {
334             if (mouseOverComponent != null)
335             {
336                 mouseOverComponent->SetState(PathComponent.State.idle);
337                 mouseOverComponent = null;
338             }
339             PathComponent* pathComponent = PathComponentAt(args.location);
340             if (pathComponent != null)
341             {
342                 pathComponent->SetState(PathComponent.State.mouseOver);
343                 mouseOverComponent = pathComponent;
344             }
345         }
346         protected override void OnMouseEnter()
347         {
348             mouseDownComponent = null;
349             mouseOverComponent = null;
350         }
351         protected override void OnMouseLeave()
352         {
353             if (mouseDownComponent != null)
354             {
355                 mouseDownComponent->SetState(PathComponent.State.idle);
356                 mouseDownComponent = null;
357             }
358             if (mouseOverComponent != null)
359             {
360                 mouseOverComponent->SetState(PathComponent.State.idle);
361                 mouseOverComponent = null;
362             }
363         }
364         protected virtual void OnPathComponentSelected(PathComponentEventArgs& args)
365         {
366             pathComponentSelectedEvent.Fire(args);
367         }
368         private nothrow PathComponent* PathComponentAt(const Point& location)
369         {
370             Component* child = pathComponents.FirstChild();
371             while (child != null)
372             {
373                 if (child is PathComponent*)
374                 {
375                     PathComponent* pathComponent = cast<PathComponent*>(child);
376                     Rect r(pathComponent->Location()pathComponent->GetSize());
377                     if (r.Contains(location))
378                     {
379                         return pathComponent;
380                     }
381                 }
382                 child = child->NextSibling();
383             }
384             return null;
385         }
386         private void Measure(Graphics& graphics)
387         {
388             Component* child = pathComponents.FirstChild();
389             while (child != null)
390             {
391                 if (child is PathComponent*)
392                 {
393                     PathComponent* pathComponent = cast<PathComponent*>(child);
394                     pathComponent->Measure(graphics);
395                 }
396                 child = child->NextSibling();
397             }
398             if (textHeight > 0)
399             {
400                 tickHeight = (tickSizePercent / 100.0f) * textHeight;
401                 tickWidth = sqrt3per2 * tickHeight;
402             }
403         }
404         private void DrawTick(Graphics& graphicsPointF& origin)
405         {
406             SmoothingMode prevSmoothingMode = graphics.GetSmoothingModeChecked();
407             graphics.SetSmoothingModeChecked(SmoothingMode.highQuality);
408             PointF p0(tickPadding.left + origin.xtickPadding.top + origin.y + (textHeight - tickHeight) / 2.0f);
409             PointF p1(p0.xp0.y + tickHeight);
410             PointF p2(p0.x + tickWidthp0.y + tickHeight / 2.0f);
411             List<PointF> points;
412             points.Add(p0);
413             points.Add(p1);
414             points.Add(p2);
415             graphics.FillPolygonChecked(tickBrush3points.CBegin().Ptr());
416             origin.x = origin.x + tickWidth + tickPadding.Horizontal();
417             graphics.SetSmoothingModeChecked(prevSmoothingMode);
418         }
419         private Container pathComponents;
420         private float tickSizePercent;
421         private SolidBrush textBrush;
422         private SolidBrush tickBrush;
423         private SolidBrush mouseOverBrush;
424         private SolidBrush mouseClickBrush;
425         private Padding pathComponentPadding;
426         private Padding tickPadding;
427         private float textHeight;
428         private float tickHeight;
429         private float tickWidth;
430         private float sqrt3per2;
431         private int maxWidth;
432         private Event<PathComponentSelectedEventHandlerPathComponentEventArgs> pathComponentSelectedEvent;
433         private PathComponent* mouseDownComponent;
434         private PathComponent* mouseOverComponent;
435     }
436 
437     public class PathComponent : Component
438     {
439         public enum State : int
440         {
441             idlemouseOvermouseClick
442         }
443         public nothrow PathComponent(PathView* pathView_const string& name_void* data_) : 
444             pathView(pathView_)name(name_)data(data_)state(State.idle)location()size()
445         {
446         }
447         public inline nothrow State GetState() const
448         {
449             return state;
450         }
451         public nothrow void SetState(State state_)
452         {
453             if (state != state_)
454             {
455                 state = state_;
456                 pathView->Invalidate();
457             }
458         }
459         public inline nothrow void* Data() const
460         {
461             return data;
462         }
463         public void Draw(Graphics& graphics)
464         {
465             if (state == State.mouseOver)
466             {
467                 Rect rect(locationsize);
468                 graphics.FillRectangleChecked(pathView->MouseOverBrush()rect);
469             }
470             else if (state == State.mouseClick)
471             {
472                 Rect rect(locationsize);
473                 graphics.FillRectangleChecked(pathView->MouseClickBrush()rect);
474             }
475             Padding componentPadding = pathView->GetPathComponentPadding();
476             PointF origin(location.xlocation.y);
477             origin.x = origin.x + componentPadding.left;
478             origin.y = origin.y + componentPadding.top;
479             graphics.DrawStringChecked(namepathView->GetFont()originpathView->GetTextBrush());
480         }
481         public void Measure(Graphics& graphics)
482         {
483             Padding componentPadding = pathView->GetPathComponentPadding();
484             PointF origin(00);
485             StringFormat stringFormat;
486             RectF r = graphics.MeasureStringChecked(namepathView->GetFont()originstringFormat);
487             pathView->SetTextHeight(r.size.h);
488             size = Size(cast<int>(componentPadding.Horizontal() + r.size.w + 0.5f)cast<int>(componentPadding.Vertical() + r.size.h + 0.5f));
489         }
490         public inline nothrow const Point& Location() const
491         {
492             return location;
493         }
494         public nothrow void SetLocation(const Point& location_)
495         {
496             location = location_;
497         }
498         public inline nothrow const Size& GetSize() const
499         {
500             return size;
501         }
502         private PathView* pathView;
503         private string name;
504         private void* data;
505         private State state;
506         private Point location;
507         private Size size;
508     }
509 }