#331 TabPanel is now functional 4.x-dev

Sun, 03 Oct 2021 15:49:47 +0300

author
cemkalyoncu
date
Sun, 03 Oct 2021 15:49:47 +0300
branch
4.x-dev
changeset 1733
5035795e8794
parent 1732
53fcfbba9755
child 1734
66ce2e2dbf1f

#331 TabPanel is now functional

.hgignore file | annotate | diff | comparison | revisions
Source/Gorgon/UI/ComponentStack.cpp file | annotate | diff | comparison | revisions
Source/Gorgon/UI/ComponentStack.h file | annotate | diff | comparison | revisions
Source/Gorgon/UI/RadioControl.h file | annotate | diff | comparison | revisions
Source/Gorgon/UI/Template.h file | annotate | diff | comparison | revisions
Source/Gorgon/Widgets/Generator.cpp file | annotate | diff | comparison | revisions
Source/Gorgon/Widgets/TabPanel.h file | annotate | diff | comparison | revisions
Testing/Source/Manual/UI_Component.cpp file | annotate | diff | comparison | revisions
Testing/Source/Manual/UI_WidgetTest.cpp file | annotate | diff | comparison | revisions
--- a/.hgignore	Fri Oct 01 14:45:24 2021 +0300
+++ b/.hgignore	Sun Oct 03 15:49:47 2021 +0300
@@ -40,6 +40,7 @@
 CMakeSettings.json
 .vs
 .vscode
+GGE.code-workspace
 syntax: regexp
 .*\.kdev4(_.*)?
 
@@ -47,4 +48,4 @@
 .ccls-cache
 compile_commands.json
 .directory
-.cache
\ No newline at end of file
+.cache
--- a/Source/Gorgon/UI/ComponentStack.cpp	Fri Oct 01 14:45:24 2021 +0300
+++ b/Source/Gorgon/UI/ComponentStack.cpp	Sun Oct 03 15:49:47 2021 +0300
@@ -8,6 +8,7 @@
 #include "math.h"
 
 //TODO:
+// Widgets that are not at the top of the stack still gets to be in the stack
 
 namespace Gorgon { namespace UI {
     
@@ -303,7 +304,7 @@
                     base.Add(substacks[&temp]);
                 }
                 else if(widgets.Exists(&temp)) {
-                    adapter.Add(widgets[&temp]);
+                    adapter.Add(widgets[&temp]); //TODO: do not add if not at the top of the stack
                 }
             }
         }
@@ -2355,6 +2356,70 @@
         }
 
         widgetgenerators[tag] = fn;
+        for(auto &t : temp) {
+            if(t.GetType() == ComponentType::Placeholder && t.GetTag() == tag) {
+                const auto &ptemp = dynamic_cast<const PlaceholderTemplate&>(t);
+
+                //if the placeholder has a subtemplate
+                if(ptemp.HasTemplate()) {
+                    if(fn) {
+                        auto w = fn(ptemp.GetTemplate());
+
+                        if(w) {
+                            widgets.Add(&t, w);
+                            auto ind = t.GetIndex();
+
+                            for(int i=0; i<stacksizes[ind]; i++) {
+                                if(&get(ind, i).GetTemplate() == &t) {
+                                    adapter.Add(*w);
+                                    break;
+                                }
+                            }
+
+                        }
+                    }
+                }
+            }
+        }
+
+        Update();
+    }
+
+    void ComponentStack::SetWidget(ComponentTemplate::Tag tag, Widget *w) {
+        for(auto it=substacks.begin(); it.IsValid(); ) {
+            if(it.Current().first->GetTag() == tag)
+                it.Delete();
+            else
+                it.Next();
+        }
+
+        for(auto &t : temp) {
+            if(t.GetType() == ComponentType::Placeholder && t.GetTag() == tag) {
+                const auto &ptemp = dynamic_cast<const PlaceholderTemplate&>(t);
+
+                //if the placeholder has a subtemplate
+                if(ptemp.HasTemplate()) {
+                    if(w) {
+                        if(widgets.Exists(&t)) {
+                            widgets[&t].Remove();
+                        }
+                        widgets.Add(&t, w);
+                        auto ind = t.GetIndex();
+
+                        for(int i=0; i<stacksizes[ind]; i++) {
+                            if(&get(ind, i).GetTemplate() == &t) {
+                                adapter.Add(*w);
+                                break;
+                            }
+                        }
+                    }
+                    else {
+                        widgets.Remove(&t);
+                    }
+                }
+            }
+        }
+
         Update();
     }
 
--- a/Source/Gorgon/UI/ComponentStack.h	Fri Oct 01 14:45:24 2021 +0300
+++ b/Source/Gorgon/UI/ComponentStack.h	Sun Oct 03 15:49:47 2021 +0300
@@ -490,6 +490,10 @@
         
         /// Adds or replaces a generator for a specific tag
         void AddGenerator(ComponentTemplate::Tag tag, std::function<Widget *(const Template &)> fn);
+
+        /// Sets the widget for a given tag. If same tag exists multiple times, the last one will get
+        /// the widget
+        void SetWidget(ComponentTemplate::Tag tag, Widget *wgt);
         
         /// Removes the generator for a specific tag
         void RemoveGenerator(ComponentTemplate::Tag tag) {
--- a/Source/Gorgon/UI/RadioControl.h	Fri Oct 01 14:45:24 2021 +0300
+++ b/Source/Gorgon/UI/RadioControl.h	Sun Oct 03 15:49:47 2021 +0300
@@ -239,7 +239,7 @@
             return columns;
         }
         
-        Event<RadioControl, T_> ChangedEvent;
+        Event<RadioControl, const T_ &> ChangedEvent;
         
     protected:
         void changing(TwoStateControl &control, bool state, bool &allow) {
--- a/Source/Gorgon/UI/Template.h	Fri Oct 01 14:45:24 2021 +0300
+++ b/Source/Gorgon/UI/Template.h	Sun Oct 03 15:49:47 2021 +0300
@@ -968,6 +968,8 @@
             HelpTag,
             ButtonTag,
             ButtonsTag,
+            PanelTag,
+            ContainerTag
         };
         
         /// Some components are repeated along some axis, this property controls how they will be
--- a/Source/Gorgon/Widgets/Generator.cpp	Fri Oct 01 14:45:24 2021 +0300
+++ b/Source/Gorgon/Widgets/Generator.cpp	Sun Oct 03 15:49:47 2021 +0300
@@ -2742,7 +2742,7 @@
 
         //Button container
         auto &buttonsarea = temp.AddContainer(1, UI::ComponentCondition::Always)
-            //.AddIndex(3) //button panel
+            .AddIndex(3) //button panel
             .AddIndex(4) //additional graphics
         ;
         buttonsarea.SetSizing(UI::ComponentTemplate::Fixed, UI::ComponentTemplate::GrowOnly);
@@ -2751,18 +2751,26 @@
 
         auto &buttonspanel = temp.AddPlaceholder(3, UI::ComponentCondition::Always);
         buttonspanel.SetTag(UI::ComponentTemplate::ButtonsTag);
-        buttonspanel.SetSize({100, UI::Dimension::Percent}, unitsize);
-        buttonspanel.SetSizing(UI::ComponentTemplate::Automatic, UI::ComponentTemplate::Fixed);
+        buttonspanel.SetSize({0, UI::Dimension::Percent}, 0);
+        buttonspanel.SetTemplate((*this)[Panel_Blank]);
+        buttonspanel.SetSizing(UI::ComponentTemplate::Fixed, UI::ComponentTemplate::Fixed);
 
         auto &graph = temp.AddGraphics(4, UI::ComponentCondition::Always);
         graph.Content.SetAnimation(A(BorderFilled, Regular, None));
-        graph.SetSize({100, UI::Dimension::Percent}, {Border.Width, UI::Dimension::Pixel});
+        graph.SetSize({100, UI::Dimension::Percent}, Border.Width);
         graph.SetFillArea(true);
         graph.SetSizing(UI::ComponentTemplate::Fixed);
         graph.SetAnchor(UI::Anchor::BottomRight, UI::Anchor::BottomLeft, UI::Anchor::BottomLeft);
 
+        auto &container = temp.AddPlaceholder(2, UI::ComponentCondition::Always);
+        container.SetTag(UI::ComponentTemplate::PanelTag);
+        container.SetTemplate((*this)[Panel_Top]); // TODO: replace
+        container.SetSize(100, 100, UI::Dimension::Percent);
+        container.SetSizing(UI::ComponentTemplate::Fixed);
+        container.SetAnchor(UI::Anchor::BottomLeft, UI::Anchor::TopLeft, UI::Anchor::TopLeft);
+
         auto &btn = temp.AddPlaceholder(5, UI::ComponentCondition::Always);
-        btn.SetTemplate((*this)[Checkbox_Button]); // TODO replace
+        btn.SetTemplate((*this)[Checkbox_Button]); // TODO: replace
         btn.SetTag(UI::ComponentTemplate::ButtonTag);
         btn.SetPositioning(UI::ComponentTemplate::Absolute);
 
--- a/Source/Gorgon/Widgets/TabPanel.h	Fri Oct 01 14:45:24 2021 +0300
+++ b/Source/Gorgon/Widgets/TabPanel.h	Sun Oct 03 15:49:47 2021 +0300
@@ -5,6 +5,9 @@
 #include "../Containers/Collection.h"
 
 #include "Panel.h"
+#include "Checkbox.h"
+#include "../UI/RadioControl.h"
+#include "../UI/Organizers/Flow.h"
 
 namespace Gorgon { namespace Widgets {
     template <class Key_>
@@ -17,10 +20,23 @@
     class Tab : public Panel {
         friend class basic_TabPanel<Key_>;
     public:
-        //TODO icon
-        //blankpanel
+        //TODO: icon
         
+        Key_ GetKey() const {
+            return key;
+        }
+
+        void SetKey(const Key_ &value);
+
+        std::string GetTitle() const {
+            return title;
+        }
+
+        void SetTitle(const std::string &value);
         
+        PROPERTY_GETSET(Tab, , Key_, Key);
+        
+        PROPERTY_GETSET(Tab, , std::string, Title);
 
     private:
         Tab(basic_TabPanel<Key_> &parent, const UI::Template &temp, 
@@ -44,6 +60,7 @@
      */
     template <class Key_>
     class basic_TabPanel : public ComponentStackComposer {
+        friend class Tab<Key_>;
     public:
 
         /// Enumeration that controls how tab buttons are sized
@@ -80,18 +97,59 @@
         /// Construct a new panel
         explicit basic_TabPanel(const UI::Template &temp) : 
             ComponentStackComposer(temp, {
-                {UI::ComponentTemplate::ButtonTag, {}}
-            }) 
+                {UI::ComponentTemplate::ButtonTag, {}},
+                {UI::ComponentTemplate::PanelTag, {}},
+            })
         {
+            stack.AddGenerator(UI::ComponentTemplate::ButtonsTag, std::bind(&basic_TabPanel::getbtns, this, std::placeholders::_1));
+            radio.ChangedEvent.Register(*this, &basic_TabPanel::Activate);
+
+            Refresh();
         }
 
         /// Construct a new panel
         explicit basic_TabPanel(Registry::TemplateType type = Registry::TabPanel_Regular) : basic_TabPanel(Registry::Active()[type]) {
         }
 
+        ~basic_TabPanel() {
+            delete buttonspnl;
+            buttons.Destroy();
+            tabs.Destroy();
+        }
+
         /// Create a new tab with the given key and title
         Tab<Key_> &New(const Key_ &key, const std::string &title) {
+            auto layertemp = stack.GetTemplate(UI::ComponentTemplate::PanelTag);
+            if(!layertemp)
+                layertemp = &Registry::Active()[Registry::Panel_Blank];
             
+            auto buttontemp = stack.GetTemplate(UI::ComponentTemplate::ButtonTag);
+            if(!buttontemp)
+                buttontemp = &Registry::Active()[Registry::Checkbox_Button];
+
+            auto &tab = *new Tab<Key_>(
+                *this,
+                *layertemp,
+                key, title
+            );
+            tabs.Add(tab);
+            mapping.Add(key, tab);
+
+            auto &btn = *new Checkbox(*buttontemp, title);
+            btn.SetHorizonalAutosize(UI::Autosize::Unit);
+            buttons.Add(btn);
+            radio.Add(key, btn);
+
+            if(buttonspnl)
+                buttonspnl->Add(btn);
+
+            if(tabs.GetSize() == 1) {
+                Activate(key);
+            }
+
+            Refresh();
+
+            return tab;
         }
 
         /// Create a new tab with the given key, title will be determined from the key
@@ -153,10 +211,24 @@
         }
 
         /// Activates the tab with the key. If key does not exist nothing is done.
-        void Activate(const Key_ &key);
+        void Activate(const Key_ &key) {
+            if(mapping.Exists(key)) {
+                if(radio.Get() != key)
+                    radio.Set(key);
+
+                mapping[key].Resize(stack.GetTagSize(UI::ComponentTemplate::PanelTag));
+                stack.SetWidget(UI::ComponentTemplate::PanelTag, &mapping[key]);
+
+                hasactive = true;
+            }
+        }
 
         /// Deactivate the currently active tab.
-        void Deactivate();
+        void Deactivate() {
+            radio.Set(Key_{});
+            stack.SetWidget(UI::ComponentTemplate::PanelTag, nullptr);
+            hasactive = false;
+        }
 
         /// Activates the next tab
         void ActivateNext();
@@ -165,13 +237,25 @@
         void ActivatePrevious();
 
         /// Returns if there is an active tab.
-        bool HasActiveTab() const;
+        bool HasActiveTab() const {
+            return hasactive && radio.Exists(radio.Get()) && mapping.Exists(radio.Get());
+        }
 
         /// Returns the currently active tab. Throws is there is no active tab.
-        Tab<Key_> &GetActiveTab();
+        Tab<Key_> &GetActiveTab() {
+            if(!HasActiveTab())
+                throw std::runtime_error("No active tab");
+
+            return mapping[radio.Get()];
+        }
 
         /// Returns the currently active tab. Throws is there is no active tab.
-        const Tab<Key_> &GetActiveTab() const;
+        const Tab<Key_> &GetActiveTab() const {
+            if(!HasActiveTab())
+                throw std::runtime_error("No active tab");
+
+            return mapping[radio.Get()];
+        }
 
         /// Set how the tab buttons will be resized. Default is AutoUnit
         void SetButtonSizing(ButtonSizing value);
@@ -222,13 +306,32 @@
             return rollover;
         }
 
+        /// Refreshes the tab buttons and their locations. This function is called automatically.
+        void Refresh() {
+            int x = 0;
+            for(int i=0; i<tabs.GetCount(); i++) {
+                buttons[i].SetText(tabs[i].GetTitle());
+                buttons[i].Location.X = x;
+                x = buttons[i].GetBounds().Right;
+            }
+
+            if(buttonspnl) {
+                if(buttons.GetSize()) {
+                    buttonspnl->Resize((Geometry::Size)buttons.Last()->GetBounds().BottomRight());
+                }
+                else {
+                    buttonspnl->Resize({0, GetUnitWidth()});
+                }
+
+                stack.SetTagSize(UI::ComponentTemplate::ButtonsTag, buttonspnl->GetSize());
+            }
+        }
 
         /// Used to enumerate tabs
         auto begin() {
             return tabs.begin();
         }
 
-
         /// Used to enumerate tabs
         auto begin() const {
             return tabs.begin();
@@ -256,8 +359,37 @@
         ButtonOverflow  overflow        = Scale;
         Geometry::Size  buttonsize; //constructor will initialize to 3x1U
         bool            rollover        = false;
+        bool            hasactive       = false;
+
+        Panel *buttonspnl = nullptr;
+        Containers::Collection<Checkbox> buttons;
+
+        UI::RadioControl<Key_, Checkbox> radio;
+
+        UI::Widget* getbtns(const UI::Template& temp) {
+            buttonspnl = new Panel(temp);
+            //buttonspnl->CreateOrganizer<UI::Organizers::Flow>();
+            buttonspnl->EnableScroll(false, false);
+
+            return buttonspnl;
+        }
     };
 
+    template <class Key_>
+    void Tab<Key_>::SetKey(const Key_ &value) {
+        parent->mapping.Remove(key);
+        parent->mapping.Add(value, this);
+        parent->radio.ChangeValue(key, value);
+        key = value;
+    }
+
+    template<class Key_>
+    void Tab<Key_>::SetTitle(const std::string &value) {
+        title = value;
+        parent->Refresh();
+    }
+
     using TabPanel = basic_TabPanel<std::string>;
 
+
 } }
--- a/Testing/Source/Manual/UI_Component.cpp	Fri Oct 01 14:45:24 2021 +0300
+++ b/Testing/Source/Manual/UI_Component.cpp	Sun Oct 03 15:49:47 2021 +0300
@@ -1067,6 +1067,56 @@
     return {"Baseline anchoring between textholders", "Size 10x10 and 10x20 objects on a 60x60 white background, first should be aligned to left, 20px from the top; second should be touching first object, should be from 10px from the top border. Objects are red and green.", stack};
 }
 
+TestData test_anchtozero(Layer &layer) {
+    auto &temp = *new Template;
+    temp.SetSize(60, 60);
+
+    auto &cont1 = temp.AddContainer(0, Gorgon::UI::ComponentCondition::Always);
+    cont1.AddIndex(1);
+    cont1.AddIndex(2);
+    cont1.Background.SetAnimation(whiteimg());
+
+    auto &cont2 = temp.AddContainer(1, Gorgon::UI::ComponentCondition::Always);
+    cont2.Background.SetAnimation(greenimg());
+    cont2.SetSize(0, 30, Gorgon::UI::Dimension::Pixel);
+
+    auto &cont3 = temp.AddContainer(2, Gorgon::UI::ComponentCondition::Always);
+    cont3.Background.SetAnimation(redimg());
+    cont3.SetSize(20, 20, Gorgon::UI::Dimension::Pixel);
+    cont3.SetAnchor(UI::Anchor::BottomRight, UI::Anchor::BottomLeft, UI::Anchor::BottomLeft);
+
+    auto &stack = *new ComponentStack(temp);
+
+    layer.Add(stack);
+
+    return {"Anchors to zero width object", "Size 20x20 object on a 60x60 white background, the object should be 10px from top.", stack};
+}
+
+TestData test_anchtoreverseside(Layer &layer) {
+    auto &temp = *new Template;
+    temp.SetSize(60, 60);
+
+    auto &cont1 = temp.AddContainer(0, Gorgon::UI::ComponentCondition::Always);
+    cont1.AddIndex(1);
+    cont1.AddIndex(2);
+    cont1.Background.SetAnimation(whiteimg());
+
+    auto &cont2 = temp.AddPlaceholder(1, Gorgon::UI::ComponentCondition::Always);
+    cont2.SetSizing(Gorgon::UI::ComponentTemplate::Fixed, Gorgon::UI::ComponentTemplate::Fixed);
+    cont2.SetSize({0, Gorgon::UI::Dimension::Percent}, 30);
+
+    auto &cont3 = temp.AddContainer(2, Gorgon::UI::ComponentCondition::Always);
+    cont3.Background.SetAnimation(redimg());
+    cont3.SetSize({100, Gorgon::UI::Dimension::Percent}, 20);
+    cont3.SetAnchor(UI::Anchor::None, UI::Anchor::BottomRight, UI::Anchor::BottomRight);
+
+    auto &stack = *new ComponentStack(temp);
+
+    layer.Add(stack);
+
+    return {"Anchors to zero width placeholder", "Size 60x20 object on a 60x60 white background, the object should be aligned to bottom.", stack};
+}
+
 TestData test_abssliding(Layer &layer) {
     auto &temp = *new Template;
     temp.SetSize(80, 40);
@@ -3319,9 +3369,11 @@
     
     &test_relanch,
     &test_relanch2,
-    &test_relanchvert,*/
+    &test_relanchvert,
     &test_relanchvertrelsize,
-    
+    &test_anchtozero,*/
+    &test_anchtoreverseside,
+    /*
     &test_anchbaseline,
     &test_anchsetbaseline,
     &test_anchbaseline2,
@@ -3423,7 +3475,7 @@
     &test_autosizedstack_complex,
     &test_autosizedstack_center,
     //END
-    /*
+
     //BEGIN Extra
     &test_anchacc,
     &test_ignored,
@@ -3454,7 +3506,7 @@
     
     Graphics::Layer datalayer;
     app.wind.Add(datalayer);
-    datalayer.Move(w + xs *2, h-50);
+    datalayer.Move(w + xs *2, h-30);
     datalayer.Resize(app.wind.GetWidth() - w - xs*2 - 10, 50);
     
     grid.Draw(xs-20, ys-2, 20, 2, Graphics::Color::White);
--- a/Testing/Source/Manual/UI_WidgetTest.cpp	Fri Oct 01 14:45:24 2021 +0300
+++ b/Testing/Source/Manual/UI_WidgetTest.cpp	Sun Oct 03 15:49:47 2021 +0300
@@ -5,6 +5,7 @@
 #include <Gorgon/UI/Organizers/Flow.h>
 #include <Gorgon/Widgets/Window.h>
 #include <Gorgon/Widgets/TabPanel.h>
+#include <Gorgon/Widgets/Button.h>
 
 std::string helptext =
     "Key list:\n"
@@ -22,6 +23,16 @@
     
     org 
         << wgt1 ;
+
+    wgt1.New("Tab 1");
+    wgt1.New("Tab 2");
+
+    Widgets::Button btn1("Hey tab 1");
+    Widgets::Button btn2("Hey tab 2");
+
+    wgt1["Tab 1"].Add(btn1);
+    wgt1["Tab 2"].Add(btn2);
+
         
     
     app.wind.Run();

mercurial