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