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 class delegate void SplitterEventHandler();
 12 
 13     public enum Orientation
 14     {
 15         horizontalvertical
 16     }
 17 
 18     public Dock SplitDock(Orientation orientation)
 19     {
 20         switch (orientation)
 21         {
 22             case Orientation.horizontal: return Dock.left;
 23             case Orientation.vertical: return Dock.top;
 24         }
 25         return Dock.none;
 26     }
 27 
 28     public Size SplitSize(Orientation orientationint width)
 29     {
 30         switch (orientation)
 31         {
 32             case Orientation.horizontal: return Size(width0);
 33             case Orientation.vertical: return Size(0width);
 34         }
 35         return Size();
 36     }
 37 
 38     public Color DefaultSplitterEdgeColor()
 39     {
 40         return Color(204u206u219u);
 41     }
 42 
 43     public const int defaultSplitterWidth = 5;
 44 
 45     public ControlCreateParams& SplitterControlCreateParams(ControlCreateParams& controlCreateParamsOrientation orientationint width)
 46     {
 47         return controlCreateParams.SetWindowClassName("System.Windows.Splitter").SetSize(SplitSize(orientationwidth)).SetDock(SplitDock(orientation));
 48     }
 49 
 50     public class SplitterCreateParams
 51     {
 52         public SplitterCreateParams(ControlCreateParams& controlCreateParams_) : 
 53             controlCreateParams(controlCreateParams_)
 54             orientation(Orientation.horizontal)
 55             edgeColor(DefaultSplitterEdgeColor())
 56         {
 57         }
 58         public SplitterCreateParams& Defaults()
 59         {
 60             return *this;
 61         }
 62         public SplitterCreateParams& SetOrientation(Orientation orientation_)
 63         {
 64             orientation = orientation_;
 65             return *this;
 66         }
 67         public SplitterCreateParams& SetEdgeColor(const Color& edgeColor_)
 68         {
 69             edgeColor = edgeColor_;
 70             return *this;
 71         }
 72         public ControlCreateParams& controlCreateParams;
 73         public Orientation orientation;
 74         public Color edgeColor;
 75     }
 76 
 77     public class Splitter : Control
 78     {
 79         private enum Flags : sbyte
 80         {
 81             none = 0moveSplitter = 1 << 0
 82         }
 83         public Splitter(Orientation orientation_const Color& backgroundColorconst Color& edgeColor_const Point& locationint width) : 
 84             base("System.Windows.Splitter"DefaultWindowClassStyle()DefaultChildWindowStyle()DefaultExtendedWindowStyle()
 85             backgroundColor"splitter"locationSplitSize(orientation_width)SplitDock(orientation_)Anchors.none)
 86             orientation(orientation_)horizontalSplitterCursor()verticalSplitterCursor()flags()container(null)
 87             edgeColor(edgeColor_)edgePen(edgeColor)
 88         {
 89             auto result = LoadCursors();
 90             if (result.Error())
 91             {
 92                 SetErrorId(result.GetErrorId());
 93             }
 94         }
 95         public Splitter(Orientation orientationconst Point& location) : 
 96             this(orientationDefaultControlBackgroundColor()DefaultSplitterEdgeColor()locationdefaultSplitterWidth)
 97         {
 98         }
 99         public Splitter(SplitterCreateParams& createParams) : 
100             base(createParams.controlCreateParams)
101             orientation(createParams.orientation)horizontalSplitterCursor()verticalSplitterCursor()flags()container(null)
102             edgeColor(createParams.edgeColor)edgePen(edgeColor)
103         {
104             auto result = LoadCursors();
105             if (result.Error())
106             {
107                 SetErrorId(result.GetErrorId());
108             }
109         }
110         private Result<bool> LoadCursors()
111         {
112             auto result = Application.GetResourceManager().GetCursor("horizontal.splitter.system.windows.cursor");
113             if (result.Error())
114             {
115                 return Result<bool>(ErrorId(result.GetErrorId()));
116             }
117             horizontalSplitterCursor = result.Value();
118             result = Application.GetResourceManager().GetCursor("vertical.splitter.system.windows.cursor");
119             if (result.Error())
120             {
121                 return Result<bool>(ErrorId(result.GetErrorId()));
122             }
123             verticalSplitterCursor = result.Value();
124             return Result<bool>(true);
125         }
126         public void SetContainer(SplitContainer* container_)
127         {
128             container = container_;
129         }
130         [nodiscard]
131         public override Result<bool> PrintWindowTree(int level)
132         {
133             LogView* log = Application.GetLogView();
134             if (log != null)
135             {
136                 auto hexStringResult = ToHexString(cast<ulong>(Handle()));
137                 if (hexStringResult.Error())
138                 {
139                     return Result<bool>(ErrorId(hexStringResult.GetErrorId()));
140                 }
141                 auto parentTextResult = ParentText();
142                 if (parentTextResult.Error())
143                 {
144                     return Result<bool>(ErrorId(parentTextResult.GetErrorId()));
145                 }
146                 auto result = log->WriteLine(string(' 'level) + "Splitter." + Text() + ".handle=" + hexStringResult.Value() + " " + 
147                     parentTextResult.Value() + "[" + Rect(Point()GetSize()).ToString() + "]");
148                 if (result.Error()) return result;
149             }
150             return Result<bool>(true);
151         }
152         [nodiscard]
153         protected override Result<bool> OnPaint(PaintEventArgs& args)
154         {
155             if (Debug.Paint())
156             {
157                 Rect r(Point()GetSize());
158                 LogView* log = Application.GetLogView();
159                 if (log != null)
160                 {
161                     auto result = log->WriteLine("Splitter.OnPaint: " + r.ToString());
162                     if (result.Error()) return result;
163                 }
164             }
165             auto result = args.graphics.Clear(BackgroundColor());
166             if (result.Error())
167             {
168                 return Result<bool>(ErrorId(result.GetErrorId()));
169             }
170             Size size = GetSize();
171             LogView* log = Application.GetLogView();
172             switch (orientation)
173             {
174                 case Orientation.horizontal:
175                 {
176                     auto result = args.graphics.DrawLine(edgePenPoint(00)Point(0size.h - 1));
177                     if (result.Error())
178                     {
179                         return Result<bool>(ErrorId(result.GetErrorId()));
180                     }
181                     result = args.graphics.DrawLine(edgePenPoint(size.w - 10)Point(size.w - 1size.h - 1));
182                     if (result.Error())
183                     {
184                         return Result<bool>(ErrorId(result.GetErrorId()));
185                     }
186                     break;
187                 }
188                 case Orientation.vertical:
189                 {
190                     auto result = args.graphics.DrawLine(edgePenPoint(00)Point(size.w - 10));
191                     if (result.Error())
192                     {
193                         return Result<bool>(ErrorId(result.GetErrorId()));
194                     }
195                     result = args.graphics.DrawLine(edgePenPoint(0size.h - 1)Point(size.w - 1size.h - 1));
196                     if (result.Error())
197                     {
198                         return Result<bool>(ErrorId(result.GetErrorId()));
199                     }
200                     break;
201                 }
202             }
203             return base->OnPaint(args);
204         }
205         protected override Result<bool> SetCursor()
206         {
207             switch (orientation)
208             {
209                 case Orientation.horizontal: SetCursor(horizontalSplitterCursor); break;
210                 case Orientation.vertical: SetCursor(verticalSplitterCursor); break;
211             }
212             return Result<bool>(true);
213         }
214         [nodiscard]
215         protected override Result<bool> OnMouseDown(MouseEventArgs& args)
216         {
217             auto result = base->OnMouseDown(args);
218             if (result.Error()) return result;
219             if (args.buttons == MouseButtons.lbutton)
220             {
221                 SetFlag(Flags.moveSplitter);
222                 switch (orientation)
223                 {
224                     case Orientation.horizontal:
225                     {
226                         x = args.location.x;
227                         break;
228                     }
229                     case Orientation.vertical:
230                     {
231                         y = args.location.y;
232                         break;
233                     }
234                 }
235                 WinSetCapture(Handle());
236             }
237             return Result<bool>(true);
238         }
239         [nodiscard]
240         protected override Result<bool> OnMouseMove(MouseEventArgs& args)
241         {
242             if (GetFlag(Flags.moveSplitter))
243             {
244                 switch (orientation)
245                 {
246                     case Orientation.horizontal:
247                     {
248                         int dx = args.location.x - x;
249                         auto result = container->SetSplitterDistance(container->SplitterDistance() + dx);
250                         if (result.Error()) return result;
251                         break;
252                     }
253                     case Orientation.vertical:
254                     {
255                         int dy = args.location.y - y;
256                         auto result = container->SetSplitterDistance(container->SplitterDistance() + dy);
257                         if (result.Error()) return result;
258                         break;
259                     }
260                 }
261             }
262             return Result<bool>(true);
263         }
264         [nodiscard]
265         protected override Result<bool> OnMouseUp(MouseEventArgs& args)
266         {
267             auto result = base->OnMouseUp(args);
268             if (result.Error()) return result;
269             if (GetFlag(Flags.moveSplitter))
270             {
271                 ResetFlag(Flags.moveSplitter);
272                 WinReleaseCapture();
273                 switch (orientation)
274                 {
275                     case Orientation.horizontal:
276                     {
277                         int dx = args.location.x - x;
278                         auto result = container->SetSplitterDistance(container->SplitterDistance() + dx);
279                         if (result.Error()) return result;
280                         break;
281                     }
282                     case Orientation.vertical:
283                     {
284                         int dy = args.location.y - y;
285                         auto result = container->SetSplitterDistance(container->SplitterDistance() + dy);
286                         if (result.Error()) return result;
287                         break;
288                     }
289                 }
290             }
291             return Result<bool>(true);
292         }
293         private inline void SetFlag(Flags flag)
294         {
295             flags = cast<Flags>(flags | flag);
296         }
297         private inline void ResetFlag(Flags flag)
298         {
299             flags = cast<Flags>(flags & ~flag);
300         }
301         private inline bool GetFlag(Flags flag) const
302         {
303             return (flags & flag) != 0;
304         }
305         private Orientation orientation;
306         private Cursor* horizontalSplitterCursor;
307         private Cursor* verticalSplitterCursor;
308         private Flags flags;
309         private int x;
310         private int y;
311         private SplitContainer* container;
312         private Color edgeColor;
313         private Pen edgePen;
314     }
315 
316     public ControlCreateParams& SplitContainerControlCreateParams(ControlCreateParams& controlCreateParams)
317     {
318         return controlCreateParams.SetWindowClassName("System.Windows.SplitContainer");
319     }
320 
321     public class SplitContainerCreateParams
322     {
323         public SplitContainerCreateParams(ControlCreateParams& controlCreateParams_
324             Orientation orientation_Control* pane1_Splitter* splitter_Control* pane2_int splitterDistance_) : 
325             controlCreateParams(controlCreateParams_)orientation(orientation_)pane1(pane1_)splitter(splitter_)pane2(pane2_)
326             splitterWidth(defaultSplitterWidth)splitterDistance(splitterDistance_)
327         {
328         }
329         public SplitContainerCreateParams& Defaults()
330         {
331             return *this;
332         }
333         public SplitContainerCreateParams& SetSplitterWidth(int splitterWidth_)
334         {
335             splitterWidth = splitterWidth_;
336             return *this;
337         }
338         public SplitContainerCreateParams& SetSplitterDistance(int splitterDistance_)
339         {
340             splitterDistance = splitterDistance_;
341             return *this;
342         }
343         public ControlCreateParams& controlCreateParams;
344         public Orientation orientation;
345         public Control* pane1;
346         public Splitter* splitter;
347         public Control* pane2;
348         public int splitterWidth;
349         public int splitterDistance;
350     }
351 
352     public class SplitContainer : ContainerControl
353     {
354         public SplitContainer(Orientation orientation_Control* pane1_Splitter* splitter_Control* pane2_int splitterWidthint splitterDistance_
355             const Point& locationconst Size& sizeDock dockAnchors anchors) : 
356             base("System.Windows.SplitContainer"DefaultWindowClassStyle()DefaultChildWindowStyle()DefaultExtendedWindowStyle()
357                 DefaultControlBackgroundColor()"splitContainer"locationsizedockanchors)
358                 orientation(orientation_)pane1(pane1_)splitter(splitter_)pane2(pane2_)splitterDistance(splitterDistance_)
359         {
360             Init();
361         }
362         public SplitContainer(Orientation orientationint splitterDistanceconst Point& locationconst Size& sizeDock dockAnchors anchors) : 
363             this(orientation
364                 new Panel(locationSplitSize(orientationsplitterDistance)SplitDock(orientation)Anchors.none)
365                 new Splitter(orientationlocation)
366                 new Panel(locationSplitSize(orientationsplitterDistance)SplitDock(orientation)Anchors.none)
367                 defaultSplitterWidthsplitterDistancelocationsizedockanchors)
368         {
369         }
370         public SplitContainer(SplitContainerCreateParams& createParams) : 
371             base(createParams.controlCreateParams)
372             orientation(createParams.orientation)
373             pane1(createParams.pane1)splitter(createParams.splitter)pane2(createParams.pane2)splitterDistance(createParams.splitterDistance)
374         {
375             Init();
376         }
377         private void Init()
378         {
379             auto result = pane1->SetSize(SplitSize(orientationsplitterDistance));
380             if (result.Error())
381             {
382                 SetErrorId(result.GetErrorId());
383                 return;
384             }
385             result = pane1->SetDock(SplitDock(orientation));
386             if (result.Error())
387             {
388                 SetErrorId(result.GetErrorId());
389                 return;
390             }
391             result = AddChild(pane1);
392             if (result.Error())
393             {
394                 SetErrorId(result.GetErrorId());
395                 return;
396             }
397             splitter->SetContainer(this);
398             result = AddChild(splitter);
399             if (result.Error())
400             {
401                 SetErrorId(result.GetErrorId());
402                 return;
403             }
404             auto locationResult = Location();
405             if (locationResult.Error())
406             {
407                 SetErrorId(locationResult.GetErrorId());
408                 return;
409             }
410             Point location = locationResult.Value();
411             result = pane2->SetLocation(location);
412             if (result.Error())
413             {
414                 SetErrorId(result.GetErrorId());
415                 return;
416             }
417             result = pane2->SetDock(Dock.fill);
418             if (result.Error())
419             {
420                 SetErrorId(result.GetErrorId());
421                 return;
422             }
423             result = AddChild(pane2);
424             if (result.Error())
425             {
426                 SetErrorId(result.GetErrorId());
427                 return;
428             }
429         }
430         [nodiscard]
431         public override Result<bool> OnPaint(PaintEventArgs& args)
432         {
433             Size size = GetSize();
434             if (splitterDistance == 0)
435             {
436                 int s = 0;
437                 switch (orientation)
438                 {
439                     case Orientation.horizontal: s = size.w; break;
440                     case Orientation.vertical: s = size.h; break;
441                 }
442                 int d = cast<int>((2.000000 / 3.000000) * s);
443                 auto result = SetSplitterDistance(d);
444                 if (result.Error()) return result;
445             }
446             return base->OnPaint(args);
447         }
448         [nodiscard]
449         public override Result<bool> PrintWindowTree(int level)
450         {
451             LogView* log = Application.GetLogView();
452             if (log != null)
453             {
454                 auto hexStringResult = ToHexString(cast<ulong>(Handle()));
455                 if (hexStringResult.Error())
456                 {
457                     return Result<bool>(ErrorId(hexStringResult.GetErrorId()));
458                 }
459                 auto parentTextResult = ParentText();
460                 if (parentTextResult.Error())
461                 {
462                     return Result<bool>(ErrorId(parentTextResult.GetErrorId()));
463                 }
464                 auto result = log->WriteLine(string(' 'level) + "SplitContainer." + Text() + ".handle=" + hexStringResult.Value() + " " + parentTextResult.Value() + 
465                     "[" + Rect(Point()GetSize()).ToString() + "]");
466                 if (result.Error()) return result;
467             }
468             Component* child = Children().FirstChild();
469             while (child != null)
470             {
471                 if (child is Control*)
472                 {
473                     Control* childControl = cast<Control*>(child);
474                     auto result = childControl->PrintWindowTree(level + 1);
475                     if (result.Error())
476                     {
477                         return Result<bool>(ErrorId(result.GetErrorId()));
478                     }
479                 }
480                 child = child->NextSibling();
481             }
482             return Result<bool>(true);
483         }
484         public inline Orientation GetOrientation() const
485         {
486             return orientation;
487         }
488         public inline ContainerControl* Pane1Container() const
489         {
490             return pane1->GetContainerControl();
491         }
492         public inline Splitter* GetSplitter() const
493         {
494             return splitter;
495         }
496         public inline ContainerControl* Pane2Container() const
497         {
498             return pane2->GetContainerControl();
499         }
500         public inline int SplitterDistance() const
501         {
502             return splitterDistance;
503         }
504         [nodiscard]
505         public Result<bool> SetSplitterDistance(int splitterDistance_)
506         {
507             int prevSplitterDistance = splitterDistance;
508             splitterDistance = splitterDistance_;
509             switch (orientation)
510             {
511                 case Orientation.horizontal:
512                 {
513                     auto result = pane1->SetSize(Size(splitterDistancepane1->GetSize().h));
514                     if (result.Error()) return result;
515                     break;
516                 }
517                 case Orientation.vertical:
518                 {
519                     auto result = pane1->SetSize(Size(pane1->GetSize().wsplitterDistance));
520                     if (result.Error()) return result;
521                     break;
522                 }
523             }
524             auto result = DockChildren();
525             if (result.Error()) return result;
526             result = Invalidate();
527             if (result.Error()) return result;
528             if (prevSplitterDistance != splitterDistance)
529             {
530                 OnSplitterDistanceChanged();
531             }
532             return Result<bool>(true);
533         }
534         public virtual void OnSplitterDistanceChanged()
535         {
536             splitterDistanceChangedEvent.Fire();
537         }
538         public Control* Pane1() const
539         {
540             return pane1;
541         }
542         public Control* Pane2() const
543         {
544             return pane2;
545         }
546         public Event<SplitterEventHandler>& SplitterDistanceChangedEvent() const
547         {
548             return splitterDistanceChangedEvent;
549         }
550         private Orientation orientation;
551         private int splitterDistance;
552         private Control* pane1;
553         private Splitter* splitter;
554         private Control* pane2;
555         private Event<SplitterEventHandler> splitterDistanceChangedEvent;
556     }