ecere/gui/Window ScrollBars: Fix for bug introduced by earlier optimization of window...
[sdk] / ecere / src / gui / controls / ScrollBar.ec
1 namespace gui::controls;
2
3 import "Window"
4
5 #define SB_WIDTH  16
6 #define SB_HEIGHT 16
7
8 #define SNAPDOWN(x, d) \
9       if(Abs(x) % (d)) \
10       { \
11          if(x < 0) x -= ((d) - Abs(x) % (d)); else x -= x % (d); \
12       }
13
14 #define SNAPUP(x, d) \
15       if(Abs(x) % (d)) \
16       { \
17          if(x > 0) x += ((d) - Abs(x) % (d)); else x += x % (d); \
18       }
19
20 class ScrollBarBits { bool vertical:1, snap:1, window:1; };
21
22 public enum ScrollDirection { horizontal, vertical };
23 public enum ScrollBarAction { none, up, down, pageDown, pageUp, setPosition, home, end, setRange, wheelUp, wheelDown };
24 public class ScrollBar : CommonControl
25 {
26    class_property(icon) = "<:ecere>controls/scrollBarHorizontal.png";
27
28 public:
29    bool Action(ScrollBarAction action, int newPosition, Key key)
30    {
31       if(this)
32       {
33          int sbWidth = guiApp.textMode ? 8 : SB_WIDTH;
34          int oldPosition = position;
35          Window master = this.master;
36
37          // Setup Range and Thumb Size
38          range = total - seen + 1;
39       /*   if(sbStyle.snap)
40             SNAPDOWN(range, lineStep);*/
41          range = Max(range, 1);
42
43          if(thumb)
44             ComputeThumb();
45
46          // Change position
47          switch(action)
48          {
49             case up:         position -= lineStep; break;
50             case down:       position += lineStep; break;
51             case pageUp:     position -= pageStep; break;
52             case pageDown:   position += pageStep; break;
53             case setPosition:   position = newPosition; break;
54             case setRange:   position = newPosition; break;
55             case home:       position = 0; break;
56             case end:        position = range-1; break;
57             case wheelUp:    position -= lineStep * 3; break;
58             case wheelDown:  position += lineStep * 3; break;
59          }
60
61          if(sbStyle.snap)
62             SNAPUP(position, lineStep);
63             // SNAPDOWN(position, lineStep);
64
65          if(position < 0) position = 0;
66          if(position >= range) 
67             position = range - 1;
68
69          if(thumb)
70             PositionThumb();
71
72          // Notify the master
73          if(master)
74             NotifyScrolling(master, this, action, position, key);
75          return position != oldPosition;
76       }
77       return false;
78    }
79
80    virtual void Window::NotifyScrolling(ScrollBar scrollBar, ScrollBarAction action, int position, Key key);
81
82    property ScrollDirection direction { property_category $"Appearance" set { sbStyle.vertical = value == vertical; } get { return sbStyle.vertical ? vertical : horizontal; } };
83    property bool windowOwned { set { sbStyle.window = value; } /* get { return sbStyle.window; } */ };
84    property bool snap { property_category $"Behavior" set { sbStyle.snap = value; } get { return sbStyle.snap; } };
85    property int range
86    {
87       property_category $"Behavior" 
88       set
89       {
90          total = value + seen - 1;
91          Action(setRange, position, 0);
92       }
93       get { return range; }
94    };
95    // Improve this, needs to be done in 2 so updates commented out for seen
96    property int seen
97    {
98       property_category $"Behavior" 
99       set
100       {
101          if(this)
102          {
103             value = Max(1,value);
104             if(sbStyle.snap)
105                SNAPDOWN(value, lineStep);
106             seen = value;
107             Action(setRange, position, 0);
108          }
109       }
110       get { return seen; }
111    };
112    property int total
113    {
114       property_category $"Behavior" 
115       set
116       {
117          if(this)
118          {
119             if(!value) value = seen;
120             total = value;
121             Action(setRange, position, 0);
122          }
123       }
124       get { return total; }
125    };
126
127    property int lineStep { property_category $"Behavior" set { if(this) lineStep = value; } get { return lineStep; } };
128    property int pageStep { property_category $"Behavior" set { if(this) pageStep = value; } get { return pageStep; } };
129    property int thumbPosition { property_category $"Behavior" set { Action(setPosition, value, 0); } get { return position; } };
130
131 private:
132    ScrollBar()
133    {
134       lineStep = 1;
135       pageStep = 5;
136       seen = 1;
137       total = 100;
138       range = total - seen + 1;
139       return true;
140    }
141
142    background = Color { 224,224,224 };
143
144    ScrollBarBits sbStyle;
145    int lineStep, pageStep;
146    int range, seen, total;
147
148    bool ButtonMouseLeave(Window control, Modifiers mods)
149    {
150       timer.Stop();
151       timer2.Stop();
152       return true;
153    }
154
155    bool ButtonReleased(Window control, int x, int y, Modifiers mods)
156    {
157       action = none;
158       ButtonMouseLeave(control, mods);
159       return true;
160    }
161
162    bool ButtonMouseOver(Window control, int x, int y, Modifiers mods)
163    {
164       if(action == up || action == down)
165          timer2.Start();
166       return true;
167    }
168
169    public Button downBtn
170    {
171       this, inactive = true;
172       NotifyMouseLeave = ButtonMouseLeave, NotifyReleased = ButtonReleased, NotifyMouseOver = ButtonMouseOver;
173
174       bool NotifyPushed(Button button, int x, int y, Modifiers mods)
175       {
176          action = down;
177          Action(action, 0, (Key)mods);
178          timer.Start();
179          return true;
180       }
181    };
182    public Button upBtn
183    {
184       this, inactive = true;
185       NotifyMouseLeave = ButtonMouseLeave, NotifyReleased = ButtonReleased, NotifyMouseOver = ButtonMouseOver;
186
187       bool NotifyPushed(Button button, int x, int y, Modifiers mods)
188       {
189          action = up; 
190          Action(action, 0, (Key)mods);
191          timer.Start();
192          return true;
193       }
194    };
195    public Button thumb
196    {
197       this, stayUp = true, NotifyReleased = ButtonReleased;
198
199       bool NotifyPushed(Button button, int x, int y, Modifiers mods)
200       {
201          thumbClick.x = x;
202          thumbClick.y = y;
203          action = setPosition;
204          return true;
205       }
206
207       bool NotifyMouseMove(Button control, int x, int y, Modifiers mods)
208       {
209          if(action == setPosition && !mods.isSideEffect)
210          {
211             int position;
212             int height;
213
214             if(sbStyle.vertical)
215             {
216                height = (clientSize.h - upBtn.size.h - downBtn.size.h - thumbSize);
217                if(guiApp.textMode)
218                {
219                   SNAPUP(height, textCellH);
220                }
221                position = y - thumbClick.y + thumb.position.y;
222                if(height)
223                   position = (position - SB_HEIGHT) * (range - 1) / height;
224                if(guiApp.textMode)
225                {
226                   SNAPUP(position, textCellH);
227                }
228             }
229             else
230             {
231                height = (clientSize.w - upBtn.size.w - downBtn.size.w - thumbSize);
232                if(guiApp.textMode)
233                {
234                   SNAPUP(height, textCellW);
235                }
236                position = x - thumbClick.x + thumb.position.x;
237                if(height)
238                   position = (position - upBtn.size.w) * (range - 1) / height;
239                if(guiApp.textMode)
240                {
241                   SNAPUP(position, textCellW);
242                }
243             }
244
245             if(height)
246             {
247                if(sbStyle.snap)
248                   SNAPDOWN(position, lineStep);
249                if(position == 0)
250                   Action(setPosition, position, (Key)mods);
251                else
252                   Action(setPosition, position, (Key)mods);
253             }
254          }
255          return true;
256       }
257    };
258
259    bool fixedThumb;
260
261    int position, mousePosition;
262    public int thumbSize;
263    int thumbPos;
264    Timer timer
265    {
266       userData = this, delay = 0.5;
267
268       bool DelayExpired()
269       {
270          timer.Stop();
271          timer2.Start();
272          timer2.DelayExpired(this);
273          return true;
274       }
275    };
276    Timer timer2
277    {
278       userData = this, delay = 0.1;
279       bool DelayExpired()
280       {
281          if(action != up && action != down)
282          {
283             if(inside)
284             {
285                if(mousePosition < thumbPos)
286                   action = pageUp;
287                else if(mousePosition >= thumbPos + thumbSize)
288                   action = pageDown;
289                else
290                   action = none;
291                if(action)
292                   Action(action, 0, 0);
293                Update(null);
294             }
295          }
296          else
297             Action(action, 0, 0);
298          return true;
299       }
300    };
301
302    ScrollBarAction action;
303    bool draggingThumb;
304    Point pos, thumbClick;
305    bool inside;
306    
307    void OnRedraw(Surface surface)
308    {
309       int sbWidth = SB_WIDTH;
310
311       if(guiApp.textMode)
312       {
313          sbWidth = 8;
314          surface.DrawingChar(177);
315          surface.SetForeground(Color { 0, 0, 170 });
316          surface.Area(0,0, clientSize.w-1, clientSize.h-1);
317       }
318
319       if((action == pageUp || action == pageDown) && inside)
320       {
321          surface.SetBackground(Color { 85, 85, 85 });
322          if(sbStyle.vertical)
323          {
324             if(action == pageUp)
325                surface.Area(0, SB_HEIGHT, clientSize.w-1, thumbPos+thumbSize/2-1);
326             else if(action == pageDown)
327                surface.Area(0, thumbPos +thumbSize/2, clientSize.w-1, clientSize.h - SB_HEIGHT-1);
328          }
329          else
330          {
331             if(action == pageUp)
332                surface.Area(sbWidth, 0, thumbPos + thumbSize/2-1, clientSize.h-1);
333             if(action == pageDown)
334                surface.Area(thumbPos+thumbSize/2, 0, clientSize.w - sbWidth - 1, clientSize.h-1);
335          }
336       }
337    }
338
339    bool OnPostCreate()
340    {
341       if(sbStyle.vertical)
342       {
343          downBtn.size.h = SB_HEIGHT;
344          downBtn.anchor = Anchor { left = 0, right = 0, bottom = 0 };
345          downBtn.bitmap = { fileName = "<:ecere>elements/arrowDown.png" };
346          downBtn.symbol = 31;
347
348          upBtn.size.h = SB_HEIGHT;
349          upBtn.anchor = Anchor { left = 0, right = 0, bottom = 0 };
350          upBtn.bitmap = { fileName = "<:ecere>elements/arrowUp.png" };
351          upBtn.symbol = 30;
352       }
353       else
354       {
355          downBtn.size.w = SB_HEIGHT;
356          downBtn.anchor = Anchor { top = 0, bottom = 0, right = 0 };
357          downBtn.bitmap = { fileName = "<:ecere>elements/arrowRight.png" };
358          downBtn.symbol = 16;
359
360          upBtn.size.w = SB_HEIGHT;
361          upBtn.anchor = Anchor { left = 0, top = 0, bottom = 0 };
362          upBtn.bitmap = { fileName = "<:ecere>elements/arrowLeft.png" };
363          upBtn.symbol = 17;
364       }
365       Action(setPosition, position, 0);
366       return true;
367    }
368
369    void PositionThumb()
370    {
371       if(range > 1)
372       {
373          // Move the thumb
374          if(sbStyle.vertical)
375          {
376             int height = (clientSize.h - upBtn.size.h - downBtn.size.h - thumbSize);
377             if(guiApp.textMode)
378             {
379                SNAPUP(height, textCellH);
380             }
381             thumbPos = upBtn.size.h + position * height / (range - 1);
382             if(guiApp.textMode)
383             {
384                SNAPUP(thumbPos, textCellH);
385             }
386
387             thumb.Move(0,thumbPos, size.w,thumbSize);
388          }
389          else
390          {
391             int width = (clientSize.w - upBtn.size.w - downBtn.size.w - thumbSize);
392             if(guiApp.textMode)
393             {
394                SNAPUP(width, textCellW);
395             }
396             thumbPos = upBtn.size.w + position * width / (range - 1);
397             if(guiApp.textMode)
398             {
399                SNAPUP(thumbPos, textCellW);
400             }
401
402             thumb.Move(thumbPos, 0, thumbSize, size.h);
403          }
404       }
405    }
406
407    void OnApplyGraphics()
408    {
409       int sbWidth = SB_WIDTH;
410       if(guiApp.textMode)
411       {
412          sbWidth = 8;
413          upBtn.background = Color { 0, 170, 170 };
414          downBtn.background = Color { 0, 170, 170 };
415          thumb.background = Color { 0, 170, 170 };
416          background = Color { 0, 170, 170 };
417       }
418       else
419       {
420          // What to do here?
421          upBtn.background = formColor;
422          downBtn.background = formColor;
423          thumb.background = formColor;
424          background = Color { 224, 224, 224 };
425       }
426       if(sbStyle.vertical)
427       {
428          downBtn.size.h = SB_HEIGHT;
429          downBtn.anchor = Anchor { left = 0, bottom = 0, right = 0 };
430
431          upBtn.size.h = SB_HEIGHT;
432          upBtn.anchor = Anchor { left = 0, right = 0, top = 0 };
433
434          thumb.Move(0,0, Max(clientSize.w, sbWidth), thumbSize);
435       }
436       else
437       {
438          downBtn.size.w = SB_HEIGHT;
439          downBtn.anchor = Anchor { top = 0, bottom = 0, right = 0 };
440
441          upBtn.size.w = SB_HEIGHT;
442          upBtn.anchor = Anchor { left = 0, top = 0, bottom = 0 };
443
444          thumb.Move(0,0, thumbSize, Max(clientSize.h, SB_HEIGHT));
445       }
446
447       PositionThumb();
448    }
449
450    bool OnResizing(int *w, int *h)
451    {
452       int sbWidth = guiApp.textMode ? 8 : SB_WIDTH;
453       if(sbStyle.vertical)
454       {
455          if(!*h) *h = 100;
456          *w = Max(*w, sbWidth);
457          *h = Max(*h, SB_HEIGHT * 2 + SB_HEIGHT);
458       }
459       else
460       {
461          if(!*w) *w = 100;
462          *h = Max(*h, SB_HEIGHT);
463          *w = Max(*w, sbWidth * 2 + sbWidth);
464       }
465       return true;
466    }
467
468    void ComputeThumb()
469    {
470       if(!fixedThumb)
471       {
472          int sbWidth = guiApp.textMode ? 8 : SB_WIDTH;
473          if(total)
474          {
475             int size;
476             if(sbStyle.vertical)
477                size = clientSize.h - upBtn.size.h - downBtn.size.h;
478             else
479                size = clientSize.w - upBtn.size.w - downBtn.size.w;
480             thumbSize = seen * size / total;
481          }
482          else
483             thumbSize = 0;      
484
485          if(sbStyle.vertical)
486          {
487             thumbSize = Min(thumbSize, clientSize.h - upBtn.size.h -  downBtn.size.h - 1);
488             thumbSize = Max(thumbSize, SB_HEIGHT);
489          }
490          else
491          {
492             thumbSize = Min(thumbSize, clientSize.w - upBtn.size.w - downBtn.size.w - 1);
493             thumbSize = Max(thumbSize, sbWidth);
494          }
495       }
496       else if(sbStyle.vertical)
497          thumbSize = thumb.size.h;
498       else
499          thumbSize = thumb.size.w;
500    }
501
502    void OnResize(int w, int h)
503    {
504       if(thumb)
505       {
506          if(!sbStyle.window)
507             Action(setPosition, position, 0);
508          else
509          {
510             // This makes sure our thumb size is recomputed
511             ComputeThumb();
512             PositionThumb();
513          }
514       }
515    }
516
517    bool OnLeftButtonDown(int x, int y, Modifiers mods)
518    {
519       int pos = (sbStyle.vertical) ? y : x;
520
521       Capture();
522
523       if(pos < thumbPos)
524          action = pageUp;
525       else
526          action = pageDown;
527
528       Action(action, 0, (Key)mods);
529       timer.Start();
530
531       Update(null);
532       return true;
533    }
534
535    bool OnLeftButtonUp(int x, int y, Modifiers mods)
536    {
537       action = none;
538       ButtonReleased(null, x, y, mods);
539       ReleaseCapture();
540       Update(null);
541       return true;
542    }
543
544    bool OnMouseMove(int x, int y, Modifiers mods)
545    {
546       mousePosition = sbStyle.vertical ? y : x;
547       return true;
548    }
549
550    bool OnMouseOver(int x, int y, Modifiers mods)
551    {
552       inside = true;
553       return true;
554    }
555
556    bool OnMouseLeave(Modifiers mods)
557    {
558       inside = false;
559       if(action == pageDown || action == pageUp)
560          Update(null);
561       return true;
562    }
563
564    bool OnKeyDown(Key key, unichar ch)
565    {
566       switch(key)
567       {
568          case home: Action(home, 0, key); break;
569          case end:  Action(end, 0, key); break;
570       }
571       return true;
572    }
573
574    bool OnKeyHit(Key key, unichar ch)
575    {
576       switch(key)
577       {
578          case left:
579          case up:     Action(up, 0, key); break;
580          case right: 
581          case down:   
582             Action(down, 0, key); 
583             break;
584          case pageUp:   Action(pageUp, 0, key); break;
585          case pageDown:  Action(pageDown, 0, key); break;
586       }
587       return true;
588    }
589
590    watch(disabled)
591    {
592       thumb.visible = !disabled;
593    };
594 };
595
596 void SBSetSeen(ScrollBar sb, int value)
597 {
598    value = Max(1,value);
599    if(sb.sbStyle.snap)
600       SNAPDOWN(value, *&sb.lineStep);
601    *&sb.seen = value;
602    sb.range = Max(sb.total - sb.seen + 1, 1);
603 }
604
605 void SBSetTotal(ScrollBar sb, int value)
606 {
607    if(!value) value = *&sb.seen;
608    *&sb.total = value;
609    sb.range = Max(sb.total - sb.seen + 1, 1);
610 }