1
2
3
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(230u, 243u, 255u);
24 }
25
26 public Color DefaultPathViewMouseClickColor()
27 {
28 return Color(204u, 232u, 255u);
29 }
30
31 public Padding DefaultPathViewPathComponentPadding()
32 {
33 return Padding(2, 6, 2, 6);
34 }
35
36 public Padding DefaultPathViewTickPadding()
37 {
38 return Padding(4, 6, 4, 6);
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(10, 10));
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& pathComponentName, void* data)
218 {
219 PathComponent* pathComponent = new PathComponent(this, pathComponentName, data);
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& pathComponentName, void* data)
228 {
229 PathComponent* pathComponent = new PathComponent(this, pathComponentName, data);
230 if (!pathComponents.IsEmpty())
231 {
232 auto result = pathComponents.InsertBefore(pathComponent, pathComponents.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(textHeight, textHeight_);
263 }
264 public Event<PathComponentSelectedEventHandler, PathComponentEventArgs>& 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(0, 0);
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(0, 0);
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.graphics, origin);
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& graphics, PointF& 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.x, tickPadding.top + origin.y + (textHeight - tickHeight) / 2.000000f);
494 PointF p1(p0.x, p0.y + tickHeight);
495 PointF p2(p0.x + tickWidth, p0.y + tickHeight / 2.000000f);
496 List<PointF> points;
497 points.Add(p0);
498 points.Add(p1);
499 points.Add(p2);
500 result = graphics.FillPolygon(tickBrush, 3, points.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<PathComponentSelectedEventHandler, PathComponentEventArgs> pathComponentSelectedEvent;
527 private PathComponent* mouseDownComponent;
528 private PathComponent* mouseOverComponent;
529 }
530
531 public class PathComponent : Component
532 {
533 public enum State : int
534 {
535 idle, mouseOver, mouseClick
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(location, size);
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(location, size);
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.x, location.y);
583 origin.x = origin.x + componentPadding.left;
584 origin.y = origin.y + componentPadding.top;
585 auto result = graphics.DrawString(name, pathView->GetFont(), origin, pathView->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(0, 0);
597 StringFormat stringFormat;
598 auto result = graphics.MeasureStringRectF(name, pathView->GetFont(), origin, stringFormat);
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 }