ecere, ide: Fixed extra buttons/crashes in compilers
[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
162    void GetDecorationsSize(MinMaxValue * w, MinMaxValue * h)
163    {
164       Window::GetDecorationsSize(w, h);
165       if(bits.scrollable && bits.endButtons)
166       {
167          if(direction == vertical) *h += left.size.h + right.size.h + 8; else *w += left.size.w + right.size.w + 8;
168       }
169    }
170
171    void SetWindowArea(int * x, int * y, MinMaxValue * w, MinMaxValue * h, MinMaxValue * cw, MinMaxValue * ch)
172    {
173       Window::SetWindowArea(x, y, w, h, cw, ch);
174       if(bits.scrollable && bits.endButtons)
175       {
176          if(direction == vertical) *y += left.size.h + 4; else *x += left.size.w + 4;
177       }
178    }
179
180    ~Stacker()
181    {
182       controls.Free();
183    }
184
185    bool OnCreate()
186    {
187       bits.holdChildMonitoring = true;
188       return true;
189    }
190
191    bool OnPostCreate()
192    {
193       bits.holdChildMonitoring = false;
194       OnResize(clientSize.w, clientSize.h);
195
196       if(direction == vertical)
197       {
198          left.bitmap = { "<:ecere>elements/arrowUp.png" };
199          left.anchor = { top = 2, left = 2, right = 2 };
200
201          right.bitmap = { "<:ecere>elements/arrowDown.png" };
202          right.anchor = { bottom = 2, left = 2, right = 2 };
203       }
204       else
205       {
206          left.bitmap = { "<:ecere>elements/arrowLeft.png" };
207          left.anchor = { left = 2, top = 2, bottom = 2 };
208
209          right.bitmap = { "<:ecere>elements/arrowRight.png" };
210          right.anchor = { right = 2, top = 2, bottom = 2 };
211       }
212       return true;
213    }
214
215    gap = 5;
216    direction = vertical;
217    endButtons = true;
218
219    void OnChildAddedOrRemoved(Window child, bool removed)
220    {
221       if(!child.nonClient)
222       {
223          if(removed)
224          {
225             if((child.destroyed && !destroyed) || child.parent != this)
226             {
227                Iterator<Window> it { controls };
228                if(it.Find(child))
229                {
230                   it.Remove();
231                   delete child;
232                }
233             }
234          }
235          else
236          {
237             if((child.created || (!created && child.autoCreate)) && !child.destroyed && child.parent == this)
238             {
239                if(!controls.Find(child))
240                {
241                   controls.Add(child);
242                   incref child;
243                }
244             }
245          }
246          if(!bits.holdChildMonitoring)
247             DoResize(size.w, size.h);
248       }
249    }
250    void OnChildVisibilityToggled(Window child, bool visible)
251    {
252       DoResize(size.w, size.h); // todo: improve with DoPartialResize(size.w, size.h, client);
253       // size = size;   // TRIGGER SCROLLING UPDATE (Currently required since we aren't using Window scrollbars)
254    }
255    void OnChildResized(Window child, int x, int y, int w, int h)
256    {
257       DoResize(size.w, size.h); // todo: improve with DoPartialResize(size.w, size.h, client);
258       // size = size;   // TRIGGER SCROLLING UPDATE (Currently required since we aren't using Window scrollbars)
259    }
260
261    /*void UpdateControls()
262    {
263       Window child;
264       Array<Window> newControls { };
265       for(c : controls; !c.nonClient)
266       {
267          child = null;
268          if(!c.destroyed)
269          {
270             for(child = firstChild; child; child = child.next)
271             {
272                if(c == child)
273                {
274                   newControls.Add(child);
275                   break;
276                }
277             }
278          }
279          if(!child)
280          {
281             child = c;
282             delete child;
283          }
284       }
285       for(child = firstChild; child; child = child.next)
286       {
287          if(child.nonClient || child.destroyed || !child.created) continue;
288          if(!newControls.Find(child))
289          {
290             newControls.Add(child);
291             incref child;
292          }
293       }
294       delete controls;
295       controls = newControls;
296       newControls = null;
297    }*/
298
299    void OnResize(int width, int height)
300    {
301       if(!bits.holdChildMonitoring && !inAutoSize)
302          DoResize(width, height);
303    }
304
305    void DoResize(int width, int height)
306    {
307       // TOIMPROVE: this needs to maintain an order and allow for dynamically adding
308       //            children. inserting in the order should also be possible.
309       // TOIMPROVE: in Window.ec... it should be possible to change the order of children
310       //            at runtime. it should also be possible to choose where dynamically
311       //            created children are inserted.
312
313       if(created)
314       {
315          int y = margin, c;
316          bool r = bits.reverse;
317          int inc = bits.reverse ? -1 : 1;
318          Window flip = null;
319
320          for(c = r ? controls.count-1 : 0; c<controls.count && c>-1; c += inc)
321          {
322             Anchor anchor;
323             Window child = controls[c];
324             if(flip && child == flip) break;
325             if(child.nonClient || !child.visible) continue;
326             anchor = child.anchor;
327             if(direction == vertical)
328             {
329                if(r)
330                {
331                   if(!anchor.bottom.type || anchor.bottom.distance != y)
332                      child.anchor.bottom = y;
333                }
334                else
335                {
336                   if(!anchor.top.type || anchor.top.distance != y)
337                      child.anchor.top = y;
338                }
339                y += child.size.h + gap;
340             }
341             else
342             {
343                if(r)
344                {
345                   if(!anchor.right.type || anchor.right.distance != y)
346                      child.anchor.right = y;
347                }
348                else
349                {
350                   if(!anchor.left.type || anchor.left.distance != y)
351                      child.anchor.left = y;
352                }
353                y += child.size.w + gap;
354             }
355             // If this child is the flipper, we flip
356             if(flipper && !flip && child == flipper)
357             {
358                flip = child;
359                if(r) { r = false; inc = 1; c = -1; }
360                else  { r = true;  inc =-1; c = controls.count; }
361                y = margin;
362             }
363          }
364
365          if(flip)
366          {
367             if(bits.flipSpring)
368             {
369                if(direction == vertical)
370                {
371                   if(r) flip.anchor.bottom = y;
372                   else  flip.anchor.top = y;
373                }
374                else
375                {
376                   if(r) flip.anchor.right = y;
377                   else  flip.anchor.left = y;
378                }
379             }
380          }
381          else if(bits.autoSize)
382          {
383             inAutoSize = true;
384             if(direction == vertical)
385                //this.clientSize.h = y - gap + margin;
386                this.size.h = y - gap + margin + (this.size.h - this.clientSize.h);
387             else
388                //this.clientSize.w = y - gap + margin;
389                this.size.w = y - gap + margin + (this.size.w - this.clientSize.w);
390             inAutoSize = false;
391          }
392
393          if(bits.scrollable && y > ((direction == horizontal) ? width : height))
394          {
395             scrollArea = (direction == horizontal) ? { y, 0 } : { 0, y };
396             if(bits.endButtons)
397             {
398                left.visible = true;
399                right.visible = true;
400             }
401          }
402          else
403          {
404             left.visible = false;
405             right.visible = false;
406             scrollArea = { 0, 0 };
407          }
408
409          if(bits.scrollable)
410          {
411             // FOR WHEN SCROLLING OCCURED
412             for(child : controls; !child.nonClient && child.visible)
413                child.anchor = child.anchor;
414
415             if(direction == horizontal)
416             {
417                left.disabled = (scroll.x == 0);
418                right.disabled = (scroll.x + clientSize.w >= scrollArea.w);
419             }
420             else
421             {
422                left.disabled = (scroll.y == 0);
423                right.disabled = (scroll.y + clientSize.h >= scrollArea.h);
424             }
425             if(left.disabled && left.buttonState == down) left.OnLeftButtonUp(-1,0,0);
426             if(right.disabled && right.buttonState == down) right.OnLeftButtonUp(-1,0,0);
427          }
428       }
429    }
430
431    public void DestroyChildren()
432    {
433       // This safe loop with 'left' will jam if the Stacker is destroyed
434       if(!destroyed && created)
435       {
436          bool left = true;
437          while(left)
438          {
439             left = false;
440             for(w : controls)
441             {
442                if(!w.destroyed && w.created)
443                {
444                   w.Destroy(0);
445                   left = true;
446                   break;
447                }
448             }
449          }
450       }
451       else
452       {
453          // If the stacker is already destroyed, just clear everything
454          Iterator<Window> it { controls };
455          while(it.pointer = null, it.Next())
456          {
457             Window w = it.data;
458             it.Remove();
459             w.Destroy(0);
460             delete w;
461          }
462       }
463    }
464
465    public void MakeControlVisible(Window control)
466    {
467       if(direction == horizontal)
468       {
469          int x;
470          if(control.position.x - stackerScrolling < scroll.x)
471          {
472             x = control.position.x;
473             if(clientSize.w > control.size.w)
474                x -=(clientSize.w - control.size.w - stackerScrolling) / 2;
475             scroll.x = Max(x, 0);
476             size = size;
477          }
478          else if(control.position.x + control.size.w + stackerScrolling > scroll.x + clientSize.w)
479          {
480             x = control.position.x;
481             if(clientSize.w > control.size.w)
482                x -=(clientSize.w - control.size.w + stackerScrolling) / 2;
483             scroll.x = Max(x, 0);
484             size = size;
485          }
486       }
487       else
488       {
489          int y;
490          if(control.position.y - stackerScrolling < scroll.y)
491          {
492             y = control.position.y;
493             if(clientSize.h > control.size.h)
494                y -=(clientSize.h - control.size.h - stackerScrolling) / 2;
495             scroll.y = Max(y, 0);
496             size = size;
497          }
498          else if(control.position.y + control.size.h + stackerScrolling > scroll.y + clientSize.h)
499          {
500             y = control.position.y;
501             if(clientSize.h > control.size.h)
502                y -=(clientSize.h - control.size.h + stackerScrolling) / 2;
503             scroll.y = Max(y, 0);
504             size = size;
505          }
506       }
507    }
508
509    public Window GetNextStackedItem(Window current, bool previous, Class filter)
510    {
511       Window result = null;
512       Window next = null;
513       Window child;
514       bool direction = !(reverse^previous);
515       int c;
516       for(c = (!direction) ? controls.count-1 : 0; c<controls.count && c>-1; c += (!direction) ? -1 : 1)
517       {
518          child = controls[c];
519          if(child.nonClient || !child.created || !child.visible) continue;
520          if(filter && !eClass_IsDerived(child._class, filter)) continue;
521          next = child;
522          break;
523       }
524       if(current)
525       {
526          for(c = direction ? controls.count-1 : 0; c<controls.count && c>-1; c += direction ? -1 : 1)
527          {
528             child = controls[c];
529             if(child.nonClient || !child.created || !child.visible) continue;
530             if(!eClass_IsDerived(child._class, filter)) continue;
531             if(child == current)
532                break;
533             next = child;
534          }
535          result = next;
536       }
537       else
538          result = next;
539       return result;
540    }
541 }