ecere/gui/controls/Stacker: Fixed issues with scrollers and client size
[sdk] / ecere / src / gui / controls / Stacker.ec
1 #ifdef BUILDING_ECERE_COM
2 namespace gui::controls;
3 import "Window"
4 import "Array"
5 #else
6 #ifdef ECERE_STATIC
7 public import static "ecere"
8 #else
9 public import "ecere"
10 #endif
11 #endif
12
13 // class RepButton WAS ALREADY DEFINED IN date.ec! The version here broke CalendarControl behavior.
14
15 static define stackerScrolling = 16;
16
17 class StackerBits
18 {
19    bool reverse:1, scrollable:1, flipSpring:1, autoSize:1, endButtons:1;
20
21    // internals
22    bool holdChildMonitoring:1;
23 }
24
25 public class Stacker : Window
26 {
27 public:
28
29    property ScrollDirection direction { set { direction = value; } get { return direction; } };
30    property int gap { set { gap = value; } get { return gap; } };
31    property bool reverse { set { bits.reverse = value; } get { return bits.reverse; } };
32
33    property bool scrollable
34    {
35       set
36       {
37          if(value != bits.scrollable)
38          {
39             bits.scrollable = value;
40             // how to recall these?
41             //GetDecorationsSize(...);
42             //SetWindowArea(...);
43             OnResize(clientSize.w, clientSize.h);
44          }
45       }
46       get { return bits.scrollable; }
47    }
48
49    property Array<Window> controls { get { return controls; } };
50
51    property Window flipper { set { flipper = value; } get { return flipper; } };
52    property bool flipSpring { set { bits.flipSpring = value; } get { return bits.flipSpring; } };
53    property bool autoSize
54    {
55       set
56       {
57          bits.autoSize = value;
58          if(value)
59          {
60             // Auto Size implementation conflicts with this base Window property, resulting in overhead and potential glitches:
61             // dontAutoScrollArea = false;
62             modifyVirtualArea = false;
63          }
64       }
65       get { return bits.autoSize; }
66    };
67    property int margin { set { margin = value; } get { return margin; } };
68    property bool endButtons
69    {
70       set
71       {
72          if(bits.endButtons && scrollable && !value)
73          {
74             left.visible = false;
75             right.visible = false;
76          }
77          bits.endButtons = value;
78          if(value && scrollable)
79          {
80             left.visible = true;
81             right.visible = true;
82          }
83       }
84       get { return bits.endButtons; }
85    };
86
87 private:
88    StackerBits bits;
89    ScrollDirection direction;
90    int gap;
91    int margin;
92    Array<Window> controls { };
93    Window flipper;
94
95    void OnVScroll(ScrollBarAction action, int position, Key key)
96    {
97       if(bits.endButtons)
98       {
99          bool ld = false, rd = false;
100          left.disabled = false;
101          right.disabled = false;
102          if(direction == horizontal)
103          {
104             if(position == 0) ld = true;
105             if(position + clientSize.w >= scrollArea.w) rd = true;
106          }
107          else
108          {
109             if(position == 0) ld = true;
110             if(position + clientSize.h >= scrollArea.h) rd = true;
111          }
112          if(left.disabled != ld)  { left.disabled = ld;  left.OnLeftButtonUp(-1,0,0); }
113          if(right.disabled != rd) { right.disabled = rd; right.OnLeftButtonUp(-1,0,0); }
114       }
115    }
116
117    RepButton left
118    {
119       nonClient = true, parent = this, visible = false, bevelOver = true, keyRepeat = true, opacity = 0; delay0 = 0.1;
120
121       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
122       {
123          if(direction == horizontal)
124          {
125             scroll.x -= stackerScrolling;
126             if(scroll.x == 0) { left.disabled = true; left.OnLeftButtonUp(-1,0,0); }
127          }
128          else
129          {
130             scroll.y -= stackerScrolling;
131             if(scroll.y == 0) { left.disabled = true; left.OnLeftButtonUp(-1,0,0); }
132          }
133          right.disabled = false;
134          size = size;   // TRIGGER SCROLLING UPDATE (Currently required since we aren't using Window scrollbars)
135          return true;
136       }
137    };
138    RepButton right
139    {
140       nonClient = true, parent = this, visible = false, bevelOver = true, keyRepeat = true, opacity = 0; delay0 = 0.1;
141
142       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
143       {
144          if(direction == horizontal)
145          {
146             scroll.x += stackerScrolling;
147             if(scroll.x + clientSize.w >= scrollArea.w) { right.disabled = true; right.OnLeftButtonUp(-1,0,0); }
148          }
149          else
150          {
151             scroll.y += stackerScrolling;
152             if(scroll.y + clientSize.h >= scrollArea.h) { right.disabled = true; right.OnLeftButtonUp(-1,0,0); }
153          }
154          left.disabled = false;
155          size = size;   // TRIGGER SCROLLING UPDATE (Currently required since we aren't using Window scrollbars)
156          return true;
157       }
158    };
159
160    bool inAutoSize;
161    bool needScrollers;
162
163    bool NeedScrollers(int width, int height)
164    {
165       bool result = false;
166       if(bits.scrollable && bits.endButtons)
167       {
168          int y = margin, c;
169          bool r = bits.reverse;
170          int inc = bits.reverse ? -1 : 1;
171          Window flip = null;
172
173          for(c = r ? controls.count-1 : 0; c<controls.count && c>-1; c += inc)
174          {
175             Window child = controls[c];
176             if(flip && child == flip) break;
177             if(child.nonClient || !child.visible) continue;
178             if(direction == vertical)
179                y += child.size.h + gap;
180             else
181                y += child.size.w + gap;
182
183             // If this child is the flipper, we flip
184             if(flipper && !flip && child == flipper)
185             {
186                flip = child;
187                if(r) { r = false; inc = 1; c = -1; }
188                else  { r = true;  inc =-1; c = controls.count; }
189                y = margin;
190             }
191          }
192          result = (y > ((direction == horizontal) ? width : height));
193       }
194       return result;
195    }
196
197    void GetDecorationsSize(MinMaxValue * w, MinMaxValue * h)
198    {
199       Window::GetDecorationsSize(w, h);
200       if(needScrollers)
201       {
202          if(direction == vertical) *h += left.size.h + right.size.h + 8; else *w += left.size.w + right.size.w + 8;
203       }
204    }
205
206    void SetWindowArea(int * x, int * y, MinMaxValue * w, MinMaxValue * h, MinMaxValue * cw, MinMaxValue * ch)
207    {
208       needScrollers = NeedScrollers(*w, *h);
209       Window::SetWindowArea(x, y, w, h, cw, ch);
210       if(needScrollers)
211       {
212          if(direction == vertical) *y += left.size.h + 4; else *x += left.size.w + 4;
213       }
214    }
215
216    ~Stacker()
217    {
218       controls.Free();
219    }
220
221    bool OnCreate()
222    {
223       bits.holdChildMonitoring = true;
224       return true;
225    }
226
227    bool OnPostCreate()
228    {
229       bits.holdChildMonitoring = false;
230       OnResize(clientSize.w, clientSize.h);
231
232       if(direction == vertical)
233       {
234          left.bitmap = { "<:ecere>elements/arrowUp.png" };
235          left.size.h = 10;
236          left.anchor = { top = 2, left = 2, right = 2 };
237
238          right.bitmap = { "<:ecere>elements/arrowDown.png" };
239          right.size.h = 10;
240          right.anchor = { bottom = 2, left = 2, right = 2 };
241       }
242       else
243       {
244          left.bitmap = { "<:ecere>elements/arrowLeft.png" };
245          left.size.w = 10;
246          left.anchor = { left = 2, top = 2, bottom = 2 };
247
248          right.bitmap = { "<:ecere>elements/arrowRight.png" };
249          right.size.w = 10;
250          right.anchor = { right = 2, top = 2, bottom = 2 };
251       }
252       return true;
253    }
254
255    gap = 5;
256    direction = vertical;
257    endButtons = true;
258
259    void OnChildAddedOrRemoved(Window child, bool removed)
260    {
261       if(!child.nonClient)
262       {
263          if(removed)
264          {
265             if((child.destroyed && !destroyed) || child.parent != this)
266             {
267                Iterator<Window> it { controls };
268                if(it.Find(child))
269                {
270                   it.Remove();
271                   delete child;
272                }
273             }
274          }
275          else
276          {
277             if((child.created || (!created && child.autoCreate)) && !child.destroyed && child.parent == this)
278             {
279                if(!controls.Find(child))
280                {
281                   controls.Add(child);
282                   incref child;
283                }
284             }
285          }
286          if(!bits.holdChildMonitoring)
287             DoResize(clientSize.w, clientSize.h);
288       }
289    }
290    void OnChildVisibilityToggled(Window child, bool visible)
291    {
292       DoResize(clientSize.w, clientSize.h); // todo: improve with DoPartialResize(size.w, size.h, client);
293       // size = size;   // TRIGGER SCROLLING UPDATE (Currently required since we aren't using Window scrollbars)
294    }
295    void OnChildResized(Window child, int x, int y, int w, int h)
296    {
297       DoResize(clientSize.w, clientSize.h); // todo: improve with DoPartialResize(size.w, size.h, client);
298       // size = size;   // TRIGGER SCROLLING UPDATE (Currently required since we aren't using Window scrollbars)
299    }
300
301    /*void UpdateControls()
302    {
303       Window child;
304       Array<Window> newControls { };
305       for(c : controls; !c.nonClient)
306       {
307          child = null;
308          if(!c.destroyed)
309          {
310             for(child = firstChild; child; child = child.next)
311             {
312                if(c == child)
313                {
314                   newControls.Add(child);
315                   break;
316                }
317             }
318          }
319          if(!child)
320          {
321             child = c;
322             delete child;
323          }
324       }
325       for(child = firstChild; child; child = child.next)
326       {
327          if(child.nonClient || child.destroyed || !child.created) continue;
328          if(!newControls.Find(child))
329          {
330             newControls.Add(child);
331             incref child;
332          }
333       }
334       delete controls;
335       controls = newControls;
336       newControls = null;
337    }*/
338
339    void OnResize(int width, int height)
340    {
341       if(!bits.holdChildMonitoring && !inAutoSize)
342          DoResize(width, height);
343    }
344
345    void DoResize(int width, int height)
346    {
347       // TOIMPROVE: this needs to maintain an order and allow for dynamically adding
348       //            children. inserting in the order should also be possible.
349       // TOIMPROVE: in Window.ec... it should be possible to change the order of children
350       //            at runtime. it should also be possible to choose where dynamically
351       //            created children are inserted.
352
353       if(created)
354       {
355          int y = margin, c;
356          bool r = bits.reverse;
357          int inc = bits.reverse ? -1 : 1;
358          Window flip = null;
359
360          for(c = r ? controls.count-1 : 0; c<controls.count && c>-1; c += inc)
361          {
362             Anchor anchor;
363             Window child = controls[c];
364             if(flip && child == flip) break;
365             if(child.nonClient || !child.visible) continue;
366             anchor = child.anchor;
367             if(direction == vertical)
368             {
369                if(r)
370                {
371                   if(!anchor.bottom.type || anchor.bottom.distance != y)
372                      child.anchor.bottom = y;
373                }
374                else
375                {
376                   if(!anchor.top.type || anchor.top.distance != y)
377                      child.anchor.top = y;
378                }
379                y += child.size.h + gap;
380             }
381             else
382             {
383                if(r)
384                {
385                   if(!anchor.right.type || anchor.right.distance != y)
386                      child.anchor.right = y;
387                }
388                else
389                {
390                   if(!anchor.left.type || anchor.left.distance != y)
391                      child.anchor.left = y;
392                }
393                y += child.size.w + gap;
394             }
395             // If this child is the flipper, we flip
396             if(flipper && !flip && child == flipper)
397             {
398                flip = child;
399                if(r) { r = false; inc = 1; c = -1; }
400                else  { r = true;  inc =-1; c = controls.count; }
401                y = margin;
402             }
403          }
404
405          if(needScrollers) //y > ((direction == horizontal) ? size.w : size.h))
406          {
407             scrollArea = (direction == horizontal) ? { y, 0 } : { 0, y };
408             if(bits.endButtons && !left.visible)
409             {
410                left.visible = true;
411                right.visible = true;
412             }
413          }
414          else
415          {
416             if(left.visible)
417             {
418                left.visible = false;
419                right.visible = false;
420             }
421             scrollArea = { 0, 0 };
422          }
423
424          if(flip)
425          {
426             if(bits.flipSpring)
427             {
428                if(direction == vertical)
429                {
430                   if(r) flip.anchor.bottom = y;
431                   else  flip.anchor.top = y;
432                }
433                else
434                {
435                   if(r) flip.anchor.right = y;
436                   else  flip.anchor.left = y;
437                }
438             }
439          }
440          else if(bits.autoSize)
441          {
442             inAutoSize = true;
443             if(direction == vertical)
444                //this.clientSize.h = y - gap + margin;
445                this.size.h = y - gap + margin + (this.size.h - this.clientSize.h);
446             else
447                //this.clientSize.w = y - gap + margin;
448                this.size.w = y - gap + margin + (this.size.w - this.clientSize.w);
449             inAutoSize = false;
450          }
451
452          if(bits.scrollable)
453          {
454             // FOR WHEN SCROLLING OCCURED
455             for(child : controls; !child.nonClient && child.visible)
456                child.anchor = child.anchor;
457
458             if(direction == horizontal)
459             {
460                left.disabled = (scroll.x == 0);
461                right.disabled = (scroll.x + clientSize.w >= scrollArea.w);
462             }
463             else
464             {
465                left.disabled = (scroll.y == 0);
466                right.disabled = (scroll.y + clientSize.h >= scrollArea.h);
467             }
468             if(left.disabled && left.buttonState == down) left.OnLeftButtonUp(-1,0,0);
469             if(right.disabled && right.buttonState == down) right.OnLeftButtonUp(-1,0,0);
470          }
471       }
472    }
473
474    public void DestroyChildren()
475    {
476       // This safe loop with 'left' will jam if the Stacker is destroyed
477       if(!destroyed && created)
478       {
479          bool left = true;
480          while(left)
481          {
482             left = false;
483             for(w : controls)
484             {
485                if(!w.destroyed && w.created)
486                {
487                   w.Destroy(0);
488                   left = true;
489                   break;
490                }
491             }
492          }
493       }
494       else
495       {
496          // If the stacker is already destroyed, just clear everything
497          Iterator<Window> it { controls };
498          while(it.pointer = null, it.Next())
499          {
500             Window w = it.data;
501             it.Remove();
502             w.Destroy(0);
503             delete w;
504          }
505       }
506    }
507
508    public void MakeControlVisible(Window control)
509    {
510       if(direction == horizontal)
511       {
512          int x;
513          if(control.position.x - stackerScrolling < scroll.x)
514          {
515             x = control.position.x;
516             if(clientSize.w > control.size.w)
517                x -=(clientSize.w - control.size.w - stackerScrolling) / 2;
518             scroll.x = Max(x, 0);
519             size = size;
520          }
521          else if(control.position.x + control.size.w + stackerScrolling > scroll.x + clientSize.w)
522          {
523             x = control.position.x;
524             if(clientSize.w > control.size.w)
525                x -=(clientSize.w - control.size.w + stackerScrolling) / 2;
526             scroll.x = Max(x, 0);
527             size = size;
528          }
529       }
530       else
531       {
532          int y;
533          if(control.position.y - stackerScrolling < scroll.y)
534          {
535             y = control.position.y;
536             if(clientSize.h > control.size.h)
537                y -=(clientSize.h - control.size.h - stackerScrolling) / 2;
538             scroll.y = Max(y, 0);
539             size = size;
540          }
541          else if(control.position.y + control.size.h + stackerScrolling > scroll.y + clientSize.h)
542          {
543             y = control.position.y;
544             if(clientSize.h > control.size.h)
545                y -=(clientSize.h - control.size.h + stackerScrolling) / 2;
546             scroll.y = Max(y, 0);
547             size = size;
548          }
549       }
550    }
551
552    public Window GetNextStackedItem(Window current, bool previous, Class filter)
553    {
554       Window result = null;
555       Window next = null;
556       Window child;
557       bool direction = !(reverse^previous);
558       int c;
559       for(c = (!direction) ? controls.count-1 : 0; c<controls.count && c>-1; c += (!direction) ? -1 : 1)
560       {
561          child = controls[c];
562          if(child.nonClient || !child.created || !child.visible) continue;
563          if(filter && !eClass_IsDerived(child._class, filter)) continue;
564          next = child;
565          break;
566       }
567       if(current)
568       {
569          for(c = direction ? controls.count-1 : 0; c<controls.count && c>-1; c += direction ? -1 : 1)
570          {
571             child = controls[c];
572             if(child.nonClient || !child.created || !child.visible) continue;
573             if(!eClass_IsDerived(child._class, filter)) continue;
574             if(child == current)
575                break;
576             next = child;
577          }
578          result = next;
579       }
580       else
581          result = next;
582       return result;
583    }
584 }