#196 Dropdown list is now usable with many problems 4.x-dev

Sat, 14 Nov 2020 12:18:17 +0200

author
cemkalyoncu
date
Sat, 14 Nov 2020 12:18:17 +0200
branch
4.x-dev
changeset 1486
cb932eb6b647
parent 1485
8a2a17aa0f93
child 1487
66f8110078d5

#196 Dropdown list is now usable with many problems

Source/Gorgon/UI/ComponentStack.h file | annotate | diff | comparison | revisions
Source/Gorgon/UI/LayerAdapter.cpp file | annotate | diff | comparison | revisions
Source/Gorgon/UI/WidgetContainer.h file | annotate | diff | comparison | revisions
Source/Gorgon/UI/Window.h file | annotate | diff | comparison | revisions
Source/Gorgon/Widgets/Dropdown.h file | annotate | diff | comparison | revisions
Source/Gorgon/Widgets/ListItem.cpp file | annotate | diff | comparison | revisions
Source/Gorgon/Widgets/Listbox.h file | annotate | diff | comparison | revisions
Source/Gorgon/Widgets/Panel.cpp file | annotate | diff | comparison | revisions
Testing/Source/Manual/UI_Generate.cpp file | annotate | diff | comparison | revisions
--- a/Source/Gorgon/UI/ComponentStack.h	Sat Nov 14 12:07:55 2020 +0200
+++ b/Source/Gorgon/UI/ComponentStack.h	Sat Nov 14 12:18:17 2020 +0200
@@ -56,6 +56,11 @@
             ReplaceCondition(condition, ComponentCondition::Always, transition); 
         }
 
+        /// Returns if the given condition is in effect.
+        bool HasCondition(ComponentCondition condition) { 
+            return conditions.count(condition);
+        }
+        
         /// Finalizes on-going transitions immediately
         void FinalizeTransitions();
 
--- a/Source/Gorgon/UI/LayerAdapter.cpp	Sat Nov 14 12:07:55 2020 +0200
+++ b/Source/Gorgon/UI/LayerAdapter.cpp	Sat Nov 14 12:18:17 2020 +0200
@@ -10,7 +10,7 @@
         if(toplevel)
             return toplevel->RequestExtender(self);
         else
-            return {false, this, self.GetLocation()};
+            return {false, this, self.GetLocation(), GetInteriorSize()};
     }
 
 } }
--- a/Source/Gorgon/UI/WidgetContainer.h	Sat Nov 14 12:07:55 2020 +0200
+++ b/Source/Gorgon/UI/WidgetContainer.h	Sat Nov 14 12:18:17 2020 +0200
@@ -22,6 +22,9 @@
         
         /// Coordinates of the given point in the extender container
         Geometry::Point CoordinatesInExtender = {0, 0};
+        
+        /// Total size of the container. -1 means infinite
+        Geometry::Size TotalSize = {0, 0};
     };
     
     /**
--- a/Source/Gorgon/UI/Window.h	Sat Nov 14 12:07:55 2020 +0200
+++ b/Source/Gorgon/UI/Window.h	Sat Nov 14 12:18:17 2020 +0200
@@ -64,7 +64,10 @@
             delete extenderlayer;
             extenderlayer = other.extenderlayer;
             Add(extenderlayer);
+            adapter.SetLayer(*extenderlayer);
             other.extenderlayer = other.layerinit();
+            other.Add(other.extenderlayer);
+            other.adapter.SetLayer(*other.extenderlayer);
             
             return *this;
         }
@@ -102,7 +105,7 @@
         }
         
         virtual ExtenderRequestResponse RequestExtender(const Gorgon::Layer &self) override {
-            return {true, &adapter, self.TranslateToTopLevel()};
+            return {true, &adapter, self.TranslateToTopLevel(), GetSize()};
         }
         
         using WidgetContainer::Add;
--- a/Source/Gorgon/Widgets/Dropdown.h	Sat Nov 14 12:07:55 2020 +0200
+++ b/Source/Gorgon/Widgets/Dropdown.h	Sat Nov 14 12:18:17 2020 +0200
@@ -39,7 +39,12 @@
                  Registry::Active()[Registry::Listbox_Regular]
             ),
             List(list)
-        { }
+        { 
+            stack.SetClickEvent([this](auto, auto, auto) {
+                Toggle();
+            });
+            list.SetOverscroll(0.5);
+        }
         
         template <class ...A_>
         explicit DropdownBase(const UI::Template &temp, A_&& ... elms) : DropdownBase(temp)
@@ -66,8 +71,25 @@
                 refresh = true;
         }
         
+        /// Opens the list
         void Open() {
-            //TODO
+            if(!HasParent())
+                return;
+            
+            auto res = GetParent().RequestExtender(stack);
+            
+            if(!res.Extender)
+                return;
+            
+            opened = true;
+            
+            list.Move(res.CoordinatesInExtender.X, res.CoordinatesInExtender.Y + GetHeight());
+            list.SetWidth(GetWidth());
+            list.FitHeight(res.TotalSize.Height-list.GetLocation().Y); 
+            res.Extender->Add(list);
+            
+            stack.AddCondition(UI::ComponentCondition::Opened);
+            
             
             if(refresh) {
                 refresh = false;
@@ -75,8 +97,11 @@
             }
         }
         
+        /// Closes the list
         void Close() {
-            
+            opened = false;
+            stack.RemoveCondition(UI::ComponentCondition::Opened);
+            list.Remove();
         }
         
         /// Toggles open/close state of the dropdown
@@ -131,10 +156,12 @@
                 if(this->list.HasSelectedItem()) {
                     TW_(this->list.GetSelectedItem(), *this);
                     this->Close();
+                    SelectionChanged(this->list.GetSelectedIndex());
                 }
                 else {
                     this->SetText("");
                     this->RemoveIcon();
+                    SelectionChanged(-1);
                 }
             });
         }
--- a/Source/Gorgon/Widgets/ListItem.cpp	Sat Nov 14 12:07:55 2020 +0200
+++ b/Source/Gorgon/Widgets/ListItem.cpp	Sat Nov 14 12:18:17 2020 +0200
@@ -7,7 +7,7 @@
         UI::ComponentStackWidget(temp) 
     {
         stack.HandleMouse();
-        stack.SetClickEvent([&](auto tag, auto location, auto) {
+        stack.SetClickEvent([this](auto tag, auto location, auto) {
             if(tag == UI::ComponentTemplate::NoTag) {
                 auto ind = stack.ComponentAt(location);
                 if(ind != -1) 
--- a/Source/Gorgon/Widgets/Listbox.h	Sat Nov 14 12:07:55 2020 +0200
+++ b/Source/Gorgon/Widgets/Listbox.h	Sat Nov 14 12:18:17 2020 +0200
@@ -4,6 +4,7 @@
 #include "Common.h"
 #include "../UI/ComponentStackWidget.h"
 #include "../Property.h"
+#include "../Input/KeyRepeater.h"
 #include "Registry.h"
 #include "ListItem.h"
 #include <vector>
@@ -1625,18 +1626,27 @@
                 i++;
                 c++;
             }
+                
+            if(totalh == 0)
+                maxdisplay = 0;
+            else
+                maxdisplay = b.Height() / (float(totalh) / c);
+            
+            if(maxdisplay < elms+overscroll && !stack.HasCondition(UI::ComponentCondition::VScroll)) {
+                stack.AddCondition(UI::ComponentCondition::VScroll);
+                stack.Update(true);
+            }
             
             auto scroller = dynamic_cast<VScroller<float>*>(stack.GetWidget(UI::ComponentTemplate::VScrollTag));
             if(scroller) {
                 scroller->SetMaximum(elms + overscroll);
                 
-                if(totalh == 0)
-                    maxdisplay = 0;
-                else
-                    maxdisplay = b.Height() / (float(totalh) / c);
-                
                 scroller->Range = maxdisplay;
                 scroller->LargeChange = maxdisplay * 0.8f;
+                
+                if(scroller->Range >= scroller->Maximum) {
+                    stack.RemoveCondition(UI::ComponentCondition::VScroll);
+                }
             }
         }
         
@@ -1688,7 +1698,7 @@
         /// Sets the amount of extra scrolling distance after the bottom-most
         /// widget is completely visible in pixels. Default is 0. Does not
         /// apply if everything is visible.
-        void SetOverscroll(int value) {
+        void SetOverscroll(float value) {
             if(overscroll == value)
                 return;
             
@@ -1701,7 +1711,7 @@
         
         /// Returns the amount of extra scrolling distance after the bottom-most
         /// widget is completely visible in pixels.
-        int GetOverscroll() const {
+        float GetOverscroll() const {
             return overscroll;
         }
         
@@ -1747,6 +1757,66 @@
             return scrollspeed != 0;
         }
         
+        /// This may not be a perfect number
+        float GetMaximumDisplayedElements() const {
+            return maxdisplay;
+        }
+        
+        /// This function will try to set the height of the listbox to contain
+        /// all its elements. If the size necessary to do so is larger than
+        /// maxpixels, then maxpixels will be used and this function will return
+        /// false. This function may give up before fully exceeding maxpixels if
+        /// elements have different heights. But it will never surpass maxpixels.
+        bool FitHeight(int maxpixels) {
+            int curh = GetInteriorSize().Height;
+            int overhead = GetHeight() - curh;
+            int expected = int( std::ceil( (this->GetCount()+overscroll) * (curh/maxdisplay) ) ) + overhead;
+            bool smalldone = false;
+            
+            //will possibly fit
+            if(expected < maxpixels) {
+                
+                do {
+                    SetHeight(expected);
+                    stack.Update(true);
+                    Refresh();
+                    
+                    auto diff = maxdisplay - (this->GetCount()+overscroll);
+                    
+                    //it fits everything, might not be tightest but that's ok. We can also eat up a bit of overscroll
+                    if(diff < 1 && diff >= -overscroll/2)
+                        return true;
+                    
+                    curh = GetInteriorSize().Height;
+                    overhead = GetHeight() - curh;
+                    int curexpected = int( std::ceil( (this->GetCount()+overscroll) * (curh/maxdisplay) ) ) + overhead;
+                    
+                    if(abs(expected - curexpected) == 0) { //no progress, exit
+                        break;
+                    }
+                    else if(abs(expected - curexpected) < 0.1 && smalldone) { //we will allow one small progress, no more
+                        break;
+                    }
+                    else if(abs(expected - curexpected) < 0.1) {
+                        smalldone = true;
+                    }
+                    expected = curexpected;
+                } while(expected < maxpixels);
+                
+            }
+            
+            SetHeight(maxpixels);
+            return false;
+        }
+        
+        Geometry::Size GetInteriorSize() const {
+            auto b = stack.TagBounds(UI::ComponentTemplate::ViewPortTag);
+            if(b.Width() == 0 || b.Height() == 0)
+                b = stack.TagBounds(UI::ComponentTemplate::ContentsTag);
+            
+            return b.GetSize();
+        }
+        
         using UI::Widget::EnsureVisible;
         
         /// Ensures the given index is completely visible on the screen.
@@ -1817,7 +1887,7 @@
             });
             
             stack.SetOtherMouseEvent([this](UI::ComponentTemplate::Tag tag, Input::Mouse::EventType type, Geometry::Point, float amount) {
-                if(type == Input::Mouse::EventType::Scroll_Vert) {
+                if(type == Input::Mouse::EventType::Scroll_Vert && stack.HasCondition(UI::ComponentCondition::VScroll)) {
                     ScrollBy(-amount*scrolldist);
                     
                     return true;
@@ -1980,13 +2050,17 @@
             return indexes.count(&widget) ? indexes[&widget] : -1;
         }
         
+        virtual void boundschanged() override {
+            Refresh();
+        }
+        
         std::map<UI::ComponentTemplate::Tag, Containers::Collection<W_>> widgets;
         std::map<const W_*, long> indexes;
         std::map<long, W_*> widgetlist;
         UI::LayerAdapter contents;
         float scrolloffset = 0.f;
         float target = 0.f;
-        int overscroll = 1;
+        float overscroll = 1;
         int scrollspeed = 20;
         int scrolldist  = 5;
         bool isscrolling = false;
--- a/Source/Gorgon/Widgets/Panel.cpp	Sat Nov 14 12:07:55 2020 +0200
+++ b/Source/Gorgon/Widgets/Panel.cpp	Sat Nov 14 12:18:17 2020 +0200
@@ -504,7 +504,8 @@
             }
         }
 
-        return {false, this, self.GetLocation()};
+        auto size = GetInteriorSize();
+        return {false, this, self.GetLocation(), {hscroll ? -1 : size.Width, vscroll ? -1 : size.Height}};
     }
 
 
--- a/Testing/Source/Manual/UI_Generate.cpp	Sat Nov 14 12:07:55 2020 +0200
+++ b/Testing/Source/Manual/UI_Generate.cpp	Sat Nov 14 12:18:17 2020 +0200
@@ -64,6 +64,7 @@
     generator.Activate();*/
 
     Widgets::Panel blank/*(Gorgon::Widgets::Registry::Panel_Blank)*/;
+    blank.Move(5, 10);
     blank.SetHeight(300);
     Gorgon::Widgets::Button btn("Save Âj",Gorgon::Widgets::Registry::Button_Regular);
     Gorgon::Widgets::Button icnbtn("+", Gorgon::Widgets::Registry::Button_Icon);
@@ -175,6 +176,7 @@
     list.Add("9");
     list.Add("Hello");
     list.Add("World");
+    list.Add("Welcome", "to", "the", "wonderful", "Gorgon", "Library", "!");
     list.AddToSelection(3);
     list.AddToSelection(1, 4);
     list.InvertSelection();
@@ -199,8 +201,11 @@
     
     Widgets::DropdownList<std::string> dlist;
     dlist.List.Add("Hello");
-    dlist.List.Add("World");
+    dlist.List.Add("Welcome", "to", "the", "wonderful", "Gorgon", "Library", "!");
     dlist.List.SetSelectedIndex(1);
+    dlist.SelectionChanged.Register([](long index) {
+        std::cout << "Dropdown index: " << index << std::endl;
+    });
     
     app.wind.Add(blank);
     addme(blank, btn);

mercurial