Sun, 03 Oct 2021 15:49:47 +0300
#331 TabPanel is now functional
--- 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();