1 // =================================
  2 // Copyright (c) 2024 Seppo Laakko
  3 // Distributed under the MIT license
  4 // =================================
  5 
  6 using System;
  7 using System.Collections;
  8 using System.Windows.API;
  9 
 10 namespace System.Windows
 11 {
 12     public ControlCreateParams& ScrollableControlControlCreateParams(ControlCreateParams& controlCreateParamsControl* child)
 13     {
 14         return controlCreateParams.SetWindowClassName("System.Windows.ScrollableControl").SetBackgroundColor(child->BackgroundColor());
 15     }
 16 
 17     public class ScrollableControlCreateParams
 18     {
 19         public ScrollableControlCreateParams(ControlCreateParams& controlCreateParams_Control* child_) : 
 20             controlCreateParams(controlCreateParams_)child(child_)
 21         {
 22         }
 23         public ScrollableControlCreateParams& Defaults()
 24         {
 25             return *this;
 26         }
 27         public ControlCreateParams& controlCreateParams;
 28         public Control* child;
 29     }
 30 
 31     public class ScrollableControl : Control
 32     {
 33         public ScrollableControl(Control* child_const Point& locationconst Size& sizeDock dockAnchors anchors) : 
 34             base("System.Windows.ScrollableControl"DefaultWindowClassStyle()DefaultChildWindowStyle()DefaultExtendedWindowStyle()
 35             child_->BackgroundColor()"scrollableControl"locationsizedockanchors)container(this)child(child_)
 36             verticalScrollUnit(0)horizontalScrollUnit(0)vpage(0u)vpos(0)vmin(0)vmax(0)hpage(0u)hpos(0)hmin(0)hmax(0)
 37             verticalScrollBarShown(false)horizontalScrollBarShown(false)
 38         {
 39             auto result = container.AddChild(child);
 40             if (result.Error())
 41             {
 42                 SetErrorId(result.GetErrorId());
 43             }
 44         }
 45         public ScrollableControl(ScrollableControlCreateParams& createParams) : 
 46             base(createParams.controlCreateParams)container(this)child(createParams.child)
 47             verticalScrollUnit(0)horizontalScrollUnit(0)vpage(0u)vpos(0)vmin(0)vmax(0)hpage(0u)hpos(0)hmin(0)hmax(0)
 48             verticalScrollBarShown(false)horizontalScrollBarShown(false)
 49         {
 50             auto result = container.AddChild(child);
 51             if (result.Error())
 52             {
 53                 SetErrorId(result.GetErrorId());
 54             }
 55         }
 56         protected override bool IsDecoratorControl() const
 57         {
 58             return true;
 59         }
 60         [nodiscard]
 61         protected override Result<bool> OnLocationChanged()
 62         {
 63             auto result = base->OnLocationChanged();
 64             if (result.Error()) return result;
 65             result = child->SetLocation(Point());
 66             if (result.Error()) return result;
 67             result = child->SetSize(GetSize());
 68             if (result.Error()) return result;
 69             return Result<bool>(true);
 70         }
 71         [nodiscard]
 72         protected override Result<bool> OnSizeChanged(SizeChangedEventArgs& args)
 73         {
 74             auto result = base->OnSizeChanged(args);
 75             if (result.Error()) return result;
 76             result = child->SetLocation(Point());
 77             if (result.Error()) return result;
 78             result = child->SetSize(GetSize());
 79             if (result.Error()) return result;
 80             return Result<bool>(true);
 81         }
 82         [nodiscard]
 83         protected override Result<bool> OnChildSizeChanged(ControlEventArgs& args)
 84         {
 85             auto result = base->OnChildSizeChanged(args);
 86             if (result.Error()) return result;
 87             result = ChildSizeOrContentSizeChanged(args);
 88             if (result.Error()) return result;
 89             return Result<bool>(true);
 90         }
 91         protected override void OnChildContentChanged(ControlEventArgs& args)
 92         {
 93             base->OnChildContentChanged(args);
 94             hpos = 0;
 95             vpos = 0;
 96         }
 97         protected override void OnChildContentLocationChanged(ControlEventArgs& args)
 98         {
 99             base->OnChildContentLocationChanged(args);
100             scrolledChild = args.control;
101             Point childContentLocation = scrolledChild->ContentLocation();
102             Pair<intint> scrollUnits = scrolledChild->GetScrollUnits();
103             int verticalScrollUnit = scrollUnits.first;
104             int horizontalScrollUnit = scrollUnits.second;
105             vpos = childContentLocation.y / verticalScrollUnit;
106             SetScrollInfo(Handle()ScrollBar.SB_VERTScrollInfoMask.SIF_POStrue0uvpos00);
107             hpos = childContentLocation.x / horizontalScrollUnit;
108             SetScrollInfo(Handle()ScrollBar.SB_HORZScrollInfoMask.SIF_POStrue0uhpos00);
109         }
110         [nodiscard]
111         private Result<bool> ChildSizeOrContentSizeChanged(ControlEventArgs& args)
112         {
113             scrolledChild = args.control;
114             Pair<intint> scrollUnits = scrolledChild->GetScrollUnits();
115             verticalScrollUnit = scrollUnits.first;
116             horizontalScrollUnit = scrollUnits.second;
117             Size scrolledChildClientSize = scrolledChild->GetSize();
118             Size scrolledChildContentSize = scrolledChild->ContentSize();
119             if (scrolledChildContentSize.h > scrolledChildClientSize.h)
120             {
121                 vmin = 0;
122                 vmax = 1;
123                 vpage = 1u;
124                 if (verticalScrollUnit > 0)
125                 {
126                     vmax = cast<int>(scrolledChildContentSize.h / verticalScrollUnit);
127                     vpage = cast<uint>(scrolledChildClientSize.h / verticalScrollUnit);
128                 }
129                 SetScrollInfo(Handle()ScrollBar.SB_VERTcast<ScrollInfoMask>(ScrollInfoMask.SIF_POS | ScrollInfoMask.SIF_PAGE | ScrollInfoMask.SIF_RANGE)true
130                     vpagevposvminvmax);
131                 auto result = ShowScrollBar(Handle()ScrollBar.SB_VERTtrue);
132                 if (result.Error()) return result;
133                 verticalScrollBarShown = true;
134             }
135             else
136             {
137                 auto result = ShowScrollBar(Handle()ScrollBar.SB_VERTfalse);
138                 if (result.Error()) return result;
139                 verticalScrollBarShown = false;
140             }
141             if (scrolledChildContentSize.w > scrolledChildClientSize.w)
142             {
143                 hmin = 0;
144                 hmax = 1;
145                 hpage = 1u;
146                 if (horizontalScrollUnit > 0)
147                 {
148                     hmax = cast<int>(scrolledChildContentSize.w / horizontalScrollUnit);
149                     hpage = cast<uint>(scrolledChildClientSize.w / horizontalScrollUnit);
150                 }
151                 SetScrollInfo(Handle()ScrollBar.SB_HORZcast<ScrollInfoMask>(ScrollInfoMask.SIF_POS | ScrollInfoMask.SIF_PAGE | ScrollInfoMask.SIF_RANGE)true
152                     hpagehposhminhmax);
153                 auto result = ShowScrollBar(Handle()ScrollBar.SB_HORZtrue);
154                 if (result.Error()) return result;
155                 horizontalScrollBarShown = true;
156             }
157             else
158             {
159                 auto result = ShowScrollBar(Handle()ScrollBar.SB_HORZfalse);
160                 if (result.Error()) return result;
161                 horizontalScrollBarShown = false;
162             }
163             return Result<bool>(true);
164         }
165         [nodiscard]
166         protected override Result<bool> OnChildContentSizeChanged(ControlEventArgs& args)
167         {
168             auto result = base->OnChildContentSizeChanged(args);
169             if (result.Error()) return result;
170             result = ChildSizeOrContentSizeChanged(args);
171             if (result.Error()) return result;
172             return Result<bool>(true);
173         }
174         [nodiscard]
175         protected override Result<bool> OnChildGotFocus(ControlEventArgs& args)
176         {
177             auto result = base->OnChildGotFocus(args);
178             if (result.Error()) return result;
179             Control* parentControl = ParentControl();
180             if (parentControl != null)
181             {
182                 result = parentControl->OnChildGotFocus(args);
183                 if (result.Error()) return result;
184             }
185             return Result<bool>(true);
186         }
187         [nodiscard]
188         protected override Result<bool> OnChildLostFocus(ControlEventArgs& args)
189         {
190             auto result = base->OnChildLostFocus(args);
191             if (result.Error()) return result;
192             Control* parentControl = ParentControl();
193             if (parentControl != null)
194             {
195                 result = parentControl->OnChildLostFocus(args);
196                 if (result.Error()) return result;
197             }
198             return Result<bool>(true);
199         }
200         [nodiscard]
201         protected override Result<bool> OnHScroll(ScrollEventArgs& args)
202         {
203             auto result = base->OnHScroll(args);
204             if (result.Error()) return result;
205             if (args.errorId != 0) return Result<bool>(ErrorId(args.errorId));
206             int trackPos;
207             result = GetScrollInfo(Handle()ScrollBar.SB_HORZhpagehposhminhmaxtrackPos);
208             if (result.Error()) return result;
209             int prevHPos = hpos;
210             switch (args.request)
211             {
212                 case SB_LINELEFT:
213                 {
214                     --hpos;
215                     break;
216                 }
217                 case SB_LINERIGHT:
218                 {
219                     ++hpos;
220                     break;
221                 }
222                 case SB_PAGELEFT:
223                 {
224                     hpos = hpos - cast<int>(hpage);
225                     break;
226                 }
227                 case SB_PAGERIGHT:
228                 {
229                     hpos = hpos + cast<int>(hpage);
230                     break;
231                 }
232                 case SB_THUMBTRACK:
233                 {
234                     hpos = trackPos;
235                     break;
236                 }
237             }
238             SetScrollInfo(Handle()ScrollBar.SB_HORZScrollInfoMask.SIF_POStrue0uhpos00);
239             result = GetScrollInfo(Handle()ScrollBar.SB_HORZhpagehposhminhmaxtrackPos);
240             if (result.Error()) return result;
241             if (prevHPos != hpos)
242             {
243                 scrolledChild->SetContentLocationInternal(Point(horizontalScrollUnit * hposverticalScrollUnit * vpos));
244                 int xAmount = horizontalScrollUnit * (prevHPos - hpos);
245                 Rect updateRect = MakeUpdateRect(xAmount0);
246                 auto result = ScrollWindowEx(scrolledChild->Handle()xAmount0nullnullupdateRect);
247                 if (result.Error()) return result;
248                 if (scrolledChild->IsDoubleBuffered())
249                 {
250                     result = scrolledChild->Invalidate();
251                     if (result.Error()) return result;
252                 }
253             }
254             return Result<bool>(true);
255         }
256         [nodiscard]
257         public override Result<bool> ScrollLineDown()
258         {
259             int trackPos;
260             auto result = GetScrollInfo(Handle()ScrollBar.SB_VERTvpagevposvminvmaxtrackPos);
261             if (result.Error()) return result;
262             int prevVPos = vpos;
263             if (vpos != vmax)
264             {
265                 ++vpos;
266                 SetScrollInfo(Handle()ScrollBar.SB_VERTScrollInfoMask.SIF_POStrue0uvpos00);
267                 result = GetScrollInfo(Handle()ScrollBar.SB_VERTvpagevposvminvmaxtrackPos);
268                 if (result.Error()) return result;
269                 if (prevVPos != vpos)
270                 {
271                     scrolledChild->SetContentLocationInternal(Point(horizontalScrollUnit * hposverticalScrollUnit * vpos));
272                     int yAmount = verticalScrollUnit * (prevVPos - vpos);
273                     Rect updateRect = MakeUpdateRect(0yAmount);
274                     auto result = ScrollWindowEx(scrolledChild->Handle()0yAmountnullnullupdateRect);
275                     if (result.Error()) return result;
276                     if (scrolledChild->IsDoubleBuffered())
277                     {
278                         result = scrolledChild->Invalidate();
279                         if (result.Error()) return result;
280                     }
281                 }
282             }
283             return Result<bool>(true);
284         }
285         [nodiscard]
286         public override Result<bool> ScrollLineUp()
287         {
288             int trackPos;
289             auto result = GetScrollInfo(Handle()ScrollBar.SB_VERTvpagevposvminvmaxtrackPos);
290             if (result.Error()) return result;
291             int prevVPos = vpos;
292             if (vpos != vmin)
293             {
294                 --vpos;
295                 SetScrollInfo(Handle()ScrollBar.SB_VERTScrollInfoMask.SIF_POStrue0uvpos00);
296                 result = GetScrollInfo(Handle()ScrollBar.SB_VERTvpagevposvminvmaxtrackPos);
297                 if (result.Error()) return result;
298                 if (prevVPos != vpos)
299                 {
300                     scrolledChild->SetContentLocationInternal(Point(horizontalScrollUnit * hposverticalScrollUnit * vpos));
301                     int yAmount = verticalScrollUnit * (prevVPos - vpos);
302                     Rect updateRect = MakeUpdateRect(0yAmount);
303                     result = ScrollWindowEx(scrolledChild->Handle()0yAmountnullnullupdateRect);
304                     if (result.Error()) return result;
305                     if (scrolledChild->IsDoubleBuffered())
306                     {
307                         result = scrolledChild->Invalidate();
308                         if (result.Error()) return result;
309                     }
310                 }
311             }
312             return Result<bool>(true);
313         }
314         protected override Result<bool> OnVScroll(ScrollEventArgs& args)
315         {
316             auto result = base->OnVScroll(args);
317             if (result.Error()) return result;
318             int trackPos;
319             result = GetScrollInfo(Handle()ScrollBar.SB_VERTvpagevposvminvmaxtrackPos);
320             if (result.Error()) return result;
321             int prevVPos = vpos;
322             switch (args.request)
323             {
324                 case SB_TOP:
325                 {
326                     vpos = vmin;
327                     break;
328                 }
329                 case SB_BOTTOM:
330                 {
331                     vpos = vmax;
332                     break;
333                 }
334                 case SB_LINEUP:
335                 {
336                     --vpos;
337                     break;
338                 }
339                 case SB_LINEDOWN:
340                 {
341                     ++vpos;
342                     break;
343                 }
344                 case SB_PAGEUP:
345                 {
346                     vpos = vpos - cast<int>(vpage);
347                     break;
348                 }
349                 case SB_PAGEDOWN:
350                 {
351                     vpos = vpos + cast<int>(vpage);
352                     break;
353                 }
354                 case SB_THUMBTRACK:
355                 {
356                     vpos = trackPos;
357                     break;
358                 }
359             }
360             SetScrollInfo(Handle()ScrollBar.SB_VERTScrollInfoMask.SIF_POStrue0uvpos00);
361             result = GetScrollInfo(Handle()ScrollBar.SB_VERTvpagevposvminvmaxtrackPos);
362             if (result.Error()) return result;
363             if (prevVPos != vpos)
364             {
365                 scrolledChild->SetContentLocationInternal(Point(horizontalScrollUnit * hposverticalScrollUnit * vpos));
366                 int yAmount = verticalScrollUnit * (prevVPos - vpos);
367                 Rect updateRect = MakeUpdateRect(0yAmount);
368                 result = ScrollWindowEx(scrolledChild->Handle()0yAmountnullnullupdateRect);
369                 if (result.Error()) return result;
370                 if (scrolledChild->IsDoubleBuffered())
371                 {
372                     result = scrolledChild->Invalidate();
373                     if (result.Error()) return result;
374                 }
375             }
376             return Result<bool>(true);
377         }
378         [nodiscard]
379         protected override Result<bool> OnMouseWheel(MouseWheelEventArgs& args)
380         {
381             auto result = base->OnMouseWheel(args);
382             if (result.Error()) return result;
383             if (!args.handled)
384             {
385                 if (verticalScrollBarShown)
386                 {
387                     int trackPos;
388                     result = GetScrollInfo(Handle()ScrollBar.SB_VERTvpagevposvminvmaxtrackPos);
389                     if (result.Error()) return result;
390                     int prevVPos = vpos;
391                     vpos = cast<int>(vpos - args.distance / (2.000000 * verticalScrollUnit));
392                     SetScrollInfo(Handle()ScrollBar.SB_VERTScrollInfoMask.SIF_POStrue0uvpos00);
393                     result = GetScrollInfo(Handle()ScrollBar.SB_VERTvpagevposvminvmaxtrackPos);
394                     if (result.Error()) return result;
395                     if (prevVPos != vpos)
396                     {
397                         Point contentLocation(horizontalScrollUnit * hposverticalScrollUnit * vpos);
398                         scrolledChild->SetContentLocationInternal(contentLocation);
399                         int yAmount = verticalScrollUnit * (prevVPos - vpos);
400                         Rect updateRect = MakeUpdateRect(0yAmount);
401                         result = ScrollWindowEx(scrolledChild->Handle()0yAmountnullnullupdateRect);
402                         if (result.Error()) return result;
403                         if (scrolledChild->IsDoubleBuffered())
404                         {
405                             result = scrolledChild->Invalidate();
406                             if (result.Error()) return result;
407                         }
408                     }
409                     args.handled = true;
410                 }
411             }
412             return Result<bool>(true);
413         }
414         protected override Result<bool> TranslateChildGraphics(Graphics& graphics)
415         {
416             int dx = -hpos * horizontalScrollUnit;
417             int dy = -vpos * verticalScrollUnit;
418             if (dx != 0 || dy != 0)
419             {
420                 auto result = graphics.TranslateTransform(dxdy);
421                 if (result.Error())
422                 {
423                     return Result<bool>(ErrorId(result.GetErrorId()));
424                 }
425             }
426             return Result<bool>(true);
427         }
428         protected override void TranslateMousePos(Point& location)
429         {
430             int dx = hpos * horizontalScrollUnit;
431             int dy = vpos * verticalScrollUnit;
432             location.x = location.x + dx;
433             location.y = location.y + dy;
434         }
435         protected override void TranslateContentLocation(Point& location)
436         {
437             int dx = hpos * horizontalScrollUnit;
438             int dy = vpos * verticalScrollUnit;
439             location.x = location.x - dx;
440             location.y = location.y - dy;
441         }
442         internal override Control* GetFirstEnabledTabStopControl() const
443         {
444             return child->GetFirstEnabledTabStopControl();
445         }
446         internal override Control* GetLastEnabledTabStopControl() const
447         {
448             return child->GetLastEnabledTabStopControl();
449         }
450         private Rect MakeUpdateRect(int xAmountint yAmount)
451         {
452             Point loc(00);
453             Size size = scrolledChild->GetSize();
454             if (xAmount < 0)
455             {
456                 loc.x = size.w + xAmount;
457             }
458             if (xAmount != 0)
459             {
460                 size.w = Abs(xAmount);
461             }
462             if (yAmount < 0)
463             {
464                 loc.y = size.h + yAmount;
465             }
466             if (yAmount != 0)
467             {
468                 size.h = Abs(yAmount);
469             }
470             Rect updateRect(locsize);
471             updateRect.Inflate(horizontalScrollUnitverticalScrollUnit);
472             return updateRect;
473         }
474         private ComponentContainer container;
475         private Control* child;
476         private Control* scrolledChild;
477         private int verticalScrollUnit;
478         private int horizontalScrollUnit;
479         private uint vpage;
480         private int vpos;
481         private int vmin;
482         private int vmax;
483         private uint hpage;
484         private int hpos;
485         private int hmin;
486         private int hmax;
487         private bool verticalScrollBarShown;
488         private bool horizontalScrollBarShown;
489     }