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