ecere/gui/controls/Stacker: Freeing space for scrollers when not needed
[sdk] / ecere / src / gui / controls / Stacker.ec
index 747c72b..fa89484 100644 (file)
@@ -10,102 +10,16 @@ public import "ecere"
 #endif
 #endif
 
-public class RepButton : Button
-{
-public:
-   bool pressing;
-   isRemote = true;
-   inactive = true;
-   
-   property Seconds delay { set { timer2.delay = value; } }
-   property Seconds delay0 { set { timer.delay = value; } }
-   
-   bool OnKeyHit(Key key, unichar ch)
-   {
-      return true;
-   }
-
-   bool OnKeyDown(Key key, unichar ch)
-   {
-      if(key == hotKey)
-      {
-         NotifyPushed(master, this, 0,0, key.modifiers);
-         return false;
-      }
-      return true;
-   }
-
-   bool OnKeyUp(Key key, unichar ch)
-   {
-      if(key == hotKey)
-      {
-         NotifyReleased(master, this, 0,0, key.modifiers);
-         return false;
-      }
-      return true;
-   }
-
-   bool NotifyPushed(RepButton button, int x, int y, Modifiers mods)
-   {
-      button.pressing = true;
-      button.NotifyClicked(this, button, x, y, mods);
-      button.timer.Start();
-      return true;
-   }
-
-   bool NotifyMouseLeave(RepButton button, Modifiers mods)
-   {
-      button.timer.Stop();
-      button.timer2.Stop();
-      return true;
-   }
-
-   bool NotifyReleased(RepButton button, int x, int y, Modifiers mods)
-   {
-      button.pressing = false;
-      button.NotifyMouseLeave(this, button, mods);
-      return false;
-   }
-
-   bool NotifyMouseOver(RepButton button, int x, int y, Modifiers mods)
-   {
-      if(button.pressing)
-         button.timer2.Start();
-      return true;
-   }
-
-   Timer timer
-   {
-      this, delay = 0.1;
-
-      bool DelayExpired()
-      {
-         timer.Stop();
-         timer2.Start();
-         timer2.DelayExpired(this);
-         return true;
-      }
-   };
-   Timer timer2
-   {
-      this, delay = 0.1;
-      bool DelayExpired()
-      {
-         NotifyClicked(master, this, 0, 0, 0);
-         return true;
-      }
-   };
-}
+// class RepButton WAS ALREADY DEFINED IN date.ec! The version here broke CalendarControl behavior.
 
 static define stackerScrolling = 16;
 
-public enum FlipStackerSpringMode { none, previous, next };
-
-public class FlipStacker : Window
+class StackerBits
 {
-   size = { };
-public:
-   FlipStackerSpringMode spring;
+   bool reverse:1, scrollable:1, flipSpring:1, autoSize:1, endButtons:1;
+
+   // internals
+   bool holdChildMonitoring:1;
 }
 
 public class Stacker : Window
@@ -114,35 +28,95 @@ public:
 
    property ScrollDirection direction { set { direction = value; } get { return direction; } };
    property int gap { set { gap = value; } get { return gap; } };
-   property bool reverse { set { reverse = value; } get { return reverse; } };
+   property bool reverse { set { bits.reverse = value; } get { return bits.reverse; } };
 
    property bool scrollable
    {
       set
       {
-         if(value != scrollable)
+         if(value != bits.scrollable)
          {
-            scrollable = value;
+            bits.scrollable = value;
             // how to recall these?
             //GetDecorationsSize(...);
             //SetWindowArea(...);
             OnResize(clientSize.w, clientSize.h);
          }
       }
-      get { return scrollable; }
+      get { return bits.scrollable; }
    }
 
    property Array<Window> controls { get { return controls; } };
 
+   property Window flipper { set { flipper = value; } get { return flipper; } };
+   property bool flipSpring { set { bits.flipSpring = value; } get { return bits.flipSpring; } };
+   property bool autoSize
+   {
+      set
+      {
+         bits.autoSize = value;
+         if(value)
+         {
+            // Auto Size implementation conflicts with this base Window property, resulting in overhead and potential glitches:
+            // dontAutoScrollArea = false;
+            modifyVirtualArea = false;
+         }
+      }
+      get { return bits.autoSize; }
+   };
+   property int margin { set { margin = value; } get { return margin; } };
+   property bool endButtons
+   {
+      set
+      {
+         if(bits.endButtons && scrollable && !value)
+         {
+            left.visible = false;
+            right.visible = false;
+         }
+         bits.endButtons = value;
+         if(value && scrollable)
+         {
+            left.visible = true;
+            right.visible = true;
+         }
+      }
+      get { return bits.endButtons; }
+   };
+
 private:
+   StackerBits bits;
    ScrollDirection direction;
    int gap;
-   bool scrollable;
+   int margin;
    Array<Window> controls { };
-   bool reverse;
+   Window flipper;
+
+   void OnVScroll(ScrollBarAction action, int position, Key key)
+   {
+      if(bits.endButtons)
+      {
+         bool ld = false, rd = false;
+         left.disabled = false;
+         right.disabled = false;
+         if(direction == horizontal)
+         {
+            if(position == 0) ld = true;
+            if(position + clientSize.w >= scrollArea.w) rd = true;
+         }
+         else
+         {
+            if(position == 0) ld = true;
+            if(position + clientSize.h >= scrollArea.h) rd = true;
+         }
+         if(left.disabled != ld)  { left.disabled = ld;  left.OnLeftButtonUp(-1,0,0); }
+         if(right.disabled != rd) { right.disabled = rd; right.OnLeftButtonUp(-1,0,0); }
+      }
+   }
+
    RepButton left
    {
-      this, visible = false, bevelOver = true, nonClient = true, keyRepeat = true, opacity = 0;
+      nonClient = true, parent = this, visible = false, bevelOver = true, keyRepeat = true, opacity = 0; delay0 = 0.1;
 
       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
       {
@@ -163,7 +137,7 @@ private:
    };
    RepButton right
    {
-      this, visible = false, bevelOver = true, nonClient = true, keyRepeat = true, opacity = 0;
+      nonClient = true, parent = this, visible = false, bevelOver = true, keyRepeat = true, opacity = 0; delay0 = 0.1;
 
       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
       {
@@ -183,21 +157,23 @@ private:
       }
    };
 
+   bool inAutoSize;
+
    void GetDecorationsSize(MinMaxValue * w, MinMaxValue * h)
    {
       Window::GetDecorationsSize(w, h);
-      if(scrollable)
+      if(bits.scrollable && bits.endButtons && left.visible)
       {
-         if(direction == vertical) *h += left.size.w + right.size.w + 8; else *w += left.size.h + right.size.h + 8;
+         if(direction == vertical) *h += left.size.h + right.size.h + 8; else *w += left.size.w + right.size.w + 8;
       }
    }
 
    void SetWindowArea(int * x, int * y, MinMaxValue * w, MinMaxValue * h, MinMaxValue * cw, MinMaxValue * ch)
    {
       Window::SetWindowArea(x, y, w, h, cw, ch);
-      if(scrollable)
+      if(bits.scrollable && bits.endButtons && left.visible)
       {
-         if(direction == vertical) *y += left.size.w + 4; else *x += left.size.h + 4;
+         if(direction == vertical) *y += left.size.h + 4; else *x += left.size.w + 4;
       }
    }
 
@@ -206,16 +182,23 @@ private:
       controls.Free();
    }
 
+   bool OnCreate()
+   {
+      bits.holdChildMonitoring = true;
+      return true;
+   }
+
    bool OnPostCreate()
    {
+      bits.holdChildMonitoring = false;
       OnResize(clientSize.w, clientSize.h);
 
       if(direction == vertical)
       {
-         left.bitmap = { "<:ecere>elements/arrowTop.png" };
+         left.bitmap = { "<:ecere>elements/arrowUp.png" };
          left.anchor = { top = 2, left = 2, right = 2 };
 
-         right.bitmap = { "<:ecere>elements/arrowBottom.png" };
+         right.bitmap = { "<:ecere>elements/arrowDown.png" };
          right.anchor = { bottom = 2, left = 2, right = 2 };
       }
       else
@@ -231,216 +214,198 @@ private:
 
    gap = 5;
    direction = vertical;
+   endButtons = true;
 
-   void OnResize(int width, int height)
+   void OnChildAddedOrRemoved(Window child, bool removed)
    {
-      if(created)
+      if(!child.nonClient)
       {
-         int y = 0;
-         Array<Window> oldControls = controls;
-         Array<Window> orderedControls;
-         Array<Window> controlsDirA { };
-         Array<Window> controlsDirB { };
-         Window child;
-
-         controls = { };     
-
-         // TOFIX: this needs to maintain an order and allow for dynamically adding
-         //        children. inserting in the order should also be possible.
-         for(c : oldControls)
+         if(removed)
          {
-            for(child = firstChild; child; child = child.next)
+            if((child.destroyed && !destroyed) || child.parent != this)
             {
-               if(child.nonClient/* || !child.visible*/ /*|| !child.created*/) continue;
-               if(c == child)
+               Iterator<Window> it { controls };
+               if(it.Find(child))
                {
-                  controls.Add(child);
-                  incref child;
-                  break;
+                  it.Remove();
+                  delete child;
                }
             }
          }
-         for(child = firstChild; child; child = child.next)
-         {
-            if(child.nonClient/* || !child.visible*/ /*|| !child.created*/) continue;
-            if(!controls.Find(child))
-            {
-               controls.Add(child);
-               incref child;
-            }
-         }
-
-         oldControls.Free();
-         delete oldControls;
-
-         if(scrollable)
+         else
          {
-            if(reverse)
+            if((child.created || (!created && child.autoCreate)) && !child.destroyed && child.parent == this)
             {
-               int c;
-               orderedControls = { };
-               for(c = controls.count-1; c >= 0; c--)
+               if(!controls.Find(child))
                {
-                  child = controls[c];
-                  orderedControls.Add(child);
+                  controls.Add(child);
                   incref child;
                }
             }
-            else
-               orderedControls = controls;
+         }
+         if(!bits.holdChildMonitoring)
+            DoResize(size.w, size.h);
+      }
+   }
+   void OnChildVisibilityToggled(Window child, bool visible)
+   {
+      DoResize(size.w, size.h); // todo: improve with DoPartialResize(size.w, size.h, client);
+      // size = size;   // TRIGGER SCROLLING UPDATE (Currently required since we aren't using Window scrollbars)
+   }
+   void OnChildResized(Window child, int x, int y, int w, int h)
+   {
+      DoResize(size.w, size.h); // todo: improve with DoPartialResize(size.w, size.h, client);
+      // size = size;   // TRIGGER SCROLLING UPDATE (Currently required since we aren't using Window scrollbars)
+   }
 
-            for(child : orderedControls)
+   /*void UpdateControls()
+   {
+      Window child;
+      Array<Window> newControls { };
+      for(c : controls; !c.nonClient)
+      {
+         child = null;
+         if(!c.destroyed)
+         {
+            for(child = firstChild; child; child = child.next)
             {
-               if(!child.visible) continue;
-               if(direction == vertical)
-               {
-                  if(reverse)
-                     child.anchor.bottom = y;
-                  else
-                     child.anchor.top = y;
-                  y += child.size.h + gap;
-               }
-               else
+               if(c == child)
                {
-                  if(reverse)
-                     child.anchor.right = y;
-                  else
-                     child.anchor.left = y;
-                  y += child.size.w + gap;
+                  newControls.Add(child);
+                  break;
                }
             }
-            if(reverse)
-            {
-               orderedControls.Free();
-               delete orderedControls;
-            }
          }
+         if(!child)
+         {
+            child = c;
+            delete child;
+         }
+      }
+      for(child = firstChild; child; child = child.next)
+      {
+         if(child.nonClient || child.destroyed || !child.created) continue;
+         if(!newControls.Find(child))
+         {
+            newControls.Add(child);
+            incref child;
+         }
+      }
+      delete controls;
+      controls = newControls;
+      newControls = null;
+   }*/
+
+   void OnResize(int width, int height)
+   {
+      if(!bits.holdChildMonitoring && !inAutoSize)
+         DoResize(width, height);
+   }
+
+   void DoResize(int width, int height)
+   {
+      if(left.visible)
+      {
+         // Take into consideration the space we would gain back by getting rid of the scrolling buttons
+         if(direction == horizontal)
+            width += 2 * (left.size.w + 4);
          else
+            height += 2 * (left.size.h + 4);
+      }
+      // TOIMPROVE: this needs to maintain an order and allow for dynamically adding
+      //            children. inserting in the order should also be possible.
+      // TOIMPROVE: in Window.ec... it should be possible to change the order of children
+      //            at runtime. it should also be possible to choose where dynamically
+      //            created children are inserted.
+
+      if(created)
+      {
+         int y = margin, c;
+         bool r = bits.reverse;
+         int inc = bits.reverse ? -1 : 1;
+         Window flip = null;
+
+         for(c = r ? controls.count-1 : 0; c<controls.count && c>-1; c += inc)
          {
-            int c;
-            int limit = 0;
-            Window previousChild = 0;
-            FlipStackerSpringMode spring = none;
-            if(reverse)
+            Anchor anchor;
+            Window child = controls[c];
+            if(flip && child == flip) break;
+            if(child.nonClient || !child.visible) continue;
+            anchor = child.anchor;
+            if(direction == vertical)
             {
-               for(c = controls.count-1; c >= 0; c--)
+               if(r)
                {
-                  child = controls[c];
-                  if(child._class == class(FlipStacker) || eClass_IsDerived(child._class, class(FlipStacker)))
-                  {
-                     FlipStacker flip = (FlipStacker)child;
-                     spring = flip.spring;
-                     break;
-                  }
-                  previousChild = child;
-                  controlsDirA.Add(child);
-                  incref child;
+                  if(!anchor.bottom.type || anchor.bottom.distance != y)
+                     child.anchor.bottom = y;
                }
-               if(c >= 0)
+               else
                {
-                  limit = c;
-                  for(c = 0; c < limit; c++)
-                  {
-                     child = controls[c];
-                     if(child._class != class(FlipStacker) && !eClass_IsDerived(child._class, class(FlipStacker)))
-                     {
-                        controlsDirB.Add(child);
-                        incref child;
-                     }
-                  }
+                  if(!anchor.top.type || anchor.top.distance != y)
+                     child.anchor.top = y;
                }
+               y += child.size.h + gap;
             }
             else
             {
-               for(c = 0; c < controls.count; c++)
-               {
-                  child = controls[c];
-                  if(child._class == class(FlipStacker) || eClass_IsDerived(child._class, class(FlipStacker)))
-                  {
-                     FlipStacker flip = (FlipStacker)child;
-                     spring = flip.spring;
-                     break;
-                  }
-                  previousChild = child;
-                  controlsDirA.Add(child);
-                  incref child;
-               }
-               if(c < controls.count)
-               {
-                  limit = c;
-                  for(c = controls.count-1; c > limit; c--)
-                  {
-                     child = controls[c];
-                     if(child._class != class(FlipStacker) && !eClass_IsDerived(child._class, class(FlipStacker)))
-                     {
-                        controlsDirB.Add(child);
-                        incref child;
-                     }
-                  }
-               }
-            }
-
-            y = 0;
-            for(child : controlsDirA)
-            {
-               if(!child.visible) continue;
-               if(direction == vertical)
+               if(r)
                {
-                  if(reverse)
-                     child.anchor.bottom = y;
-                  else
-                     child.anchor.top = y;
-                  y += child.size.h + gap;
+                  if(!anchor.right.type || anchor.right.distance != y)
+                     child.anchor.right = y;
                }
                else
                {
-                  if(reverse)
-                     child.anchor.right = y;
-                  else
+                  if(!anchor.left.type || anchor.left.distance != y)
                      child.anchor.left = y;
-                  y += child.size.w + gap;
                }
+               y += child.size.w + gap;
+            }
+            // If this child is the flipper, we flip
+            if(flipper && !flip && child == flipper)
+            {
+               flip = child;
+               if(r) { r = false; inc = 1; c = -1; }
+               else  { r = true;  inc =-1; c = controls.count; }
+               y = margin;
             }
-            y = 0;
-            for(child : controlsDirB)
+         }
+
+         if(flip)
+         {
+            if(bits.flipSpring)
             {
-               if(!child.visible) continue;
                if(direction == vertical)
                {
-                  if(reverse)
-                     child.anchor.top = y;
-                  else
-                     child.anchor.bottom = y;
-                  y += child.size.h + gap;
+                  if(r) flip.anchor.bottom = y;
+                  else  flip.anchor.top = y;
                }
                else
                {
-                  if(reverse)
-                     child.anchor.left = y;
-                  else
-                     child.anchor.right = y;
-                  y += child.size.w + gap;
+                  if(r) flip.anchor.right = y;
+                  else  flip.anchor.left = y;
                }
             }
-            if(spring == previous && previousChild)
-            {
-               if(reverse)
-                  previousChild.anchor.left = y;
-               else
-                  previousChild.anchor.right = y;
-            }
-
-            controlsDirA.Free();
-            delete controlsDirA;
-            controlsDirB.Free();
-            delete controlsDirB;
+         }
+         else if(bits.autoSize)
+         {
+            inAutoSize = true;
+            if(direction == vertical)
+               //this.clientSize.h = y - gap + margin;
+               this.size.h = y - gap + margin + (this.size.h - this.clientSize.h);
+            else
+               //this.clientSize.w = y - gap + margin;
+               this.size.w = y - gap + margin + (this.size.w - this.clientSize.w);
+            inAutoSize = false;
          }
 
-         if(scrollable && y > ((direction == horizontal) ? width : height))
+         if(bits.scrollable && y > ((direction == horizontal) ? width : height))
          {
             scrollArea = (direction == horizontal) ? { y, 0 } : { 0, y };
-            left.visible = true;
-            right.visible = true;
+            if(bits.endButtons)
+            {
+               left.visible = true;
+               right.visible = true;
+            }
          }
          else
          {
@@ -449,12 +414,12 @@ private:
             scrollArea = { 0, 0 };
          }
 
-         // FOR WHEN SCROLLING OCCURED
-         for(child : controls)
-            child.anchor = child.anchor;
-
-         if(scrollable)
+         if(bits.scrollable)
          {
+            // FOR WHEN SCROLLING OCCURED
+            for(child : controls; !child.nonClient && child.visible)
+               child.anchor = child.anchor;
+
             if(direction == horizontal)
             {
                left.disabled = (scroll.x == 0);
@@ -473,16 +438,34 @@ private:
 
    public void DestroyChildren()
    {
-      Window child, next;
-
-      for(child = firstChild; child; child = next)
+      // This safe loop with 'left' will jam if the Stacker is destroyed
+      if(!destroyed && created)
       {
-         next = child ? child.next : null;
-         if(!child.nonClient)
+         bool left = true;
+         while(left)
          {
-            child.Destroy(0);
-            child.parent = null;
-            delete child;
+            left = false;
+            for(w : controls)
+            {
+               if(!w.destroyed && w.created)
+               {
+                  w.Destroy(0);
+                  left = true;
+                  break;
+               }
+            }
+         }
+      }
+      else
+      {
+         // If the stacker is already destroyed, just clear everything
+         Iterator<Window> it { controls };
+         while(it.pointer = null, it.Next())
+         {
+            Window w = it.data;
+            it.Remove();
+            w.Destroy(0);
+            delete w;
          }
       }
    }
@@ -530,4 +513,37 @@ private:
          }
       }
    }
+
+   public Window GetNextStackedItem(Window current, bool previous, Class filter)
+   {
+      Window result = null;
+      Window next = null;
+      Window child;
+      bool direction = !(reverse^previous);
+      int c;
+      for(c = (!direction) ? controls.count-1 : 0; c<controls.count && c>-1; c += (!direction) ? -1 : 1)
+      {
+         child = controls[c];
+         if(child.nonClient || !child.created || !child.visible) continue;
+         if(filter && !eClass_IsDerived(child._class, filter)) continue;
+         next = child;
+         break;
+      }
+      if(current)
+      {
+         for(c = direction ? controls.count-1 : 0; c<controls.count && c>-1; c += direction ? -1 : 1)
+         {
+            child = controls[c];
+            if(child.nonClient || !child.created || !child.visible) continue;
+            if(!eClass_IsDerived(child._class, filter)) continue;
+            if(child == current)
+               break;
+            next = child;
+         }
+         result = next;
+      }
+      else
+         result = next;
+      return result;
+   }
 }