#139 Listbox single selection system 4.x-dev

Mon, 12 Oct 2020 21:24:41 +0300

author
cemkalyoncu
date
Mon, 12 Oct 2020 21:24:41 +0300
branch
4.x-dev
changeset 1463
aafcf9c1dac9
parent 1462
a750ce7fd0d7
child 1464
fc28c4139fcc

#139 Listbox single selection system

Source/Gorgon/UI/LayerAdapter.h file | annotate | diff | comparison | revisions
Source/Gorgon/UI/Template.h file | annotate | diff | comparison | revisions
Source/Gorgon/Widgets/ListItem.cpp file | annotate | diff | comparison | revisions
Source/Gorgon/Widgets/ListItem.h file | annotate | diff | comparison | revisions
Source/Gorgon/Widgets/Listbox.h file | annotate | diff | comparison | revisions
Source/Gorgon/Widgets/Slider.h file | annotate | diff | comparison | revisions
Testing/Source/Manual/UI_Generate.cpp file | annotate | diff | comparison | revisions
--- a/Source/Gorgon/UI/LayerAdapter.h	Thu Oct 08 09:32:40 2020 +0300
+++ b/Source/Gorgon/UI/LayerAdapter.h	Mon Oct 12 21:24:41 2020 +0300
@@ -11,8 +11,14 @@
      */
     class LayerAdapter : public WidgetContainer {
     public:
+        
+        /// This constructor will leave the LayerAdapter in an invalid state.
+        /// You must use SetLayer before calling any other functions.
+        LayerAdapter() {
+        }
+        
         /// Constructor requires the base layer
-        LayerAdapter(Layer &base) : base(base) { 
+        LayerAdapter(Layer &base) : base(&base) { 
             SetFocusStrategy(Deny);
         }
         
@@ -21,25 +27,33 @@
         }
         
         virtual Geometry::Size GetInteriorSize() const override {
-            return base.GetSize();
+            return base->GetSize();
         }
         
         virtual bool ResizeInterior(Geometry::Size size) override {
-            base.Resize(size);
+            base->Resize(size);
             
             return true;
         }
         
         virtual bool IsVisible() const override {
-            return base.IsVisible();
+            return base->IsVisible();
+        }
+        
+        void SetLayer(Gorgon::Layer &value) {
+            base = &value;
+        }
+        
+        Gorgon::Layer &GetLayer() const {
+            return *base;
         }
         
     protected:
         virtual Gorgon::Layer &getlayer() override {
-            return base;
+            return *base;
         }
         
-        Layer &base;
+        Layer *base = nullptr;
     };
     
 } }
--- a/Source/Gorgon/UI/Template.h	Thu Oct 08 09:32:40 2020 +0300
+++ b/Source/Gorgon/UI/Template.h	Mon Oct 12 21:24:41 2020 +0300
@@ -869,6 +869,9 @@
             ViewPortTag,
             SelectionTag,
             CaretTag,
+            ItemTag,
+            HeaderTag,
+            SpacerTag
         };
         
         /// Some components are repeated along some axis, this property controls how they will be
--- a/Source/Gorgon/Widgets/ListItem.cpp	Thu Oct 08 09:32:40 2020 +0300
+++ b/Source/Gorgon/Widgets/ListItem.cpp	Mon Oct 12 21:24:41 2020 +0300
@@ -82,20 +82,6 @@
     }
     
 
-    void ListItem::SetFocusState(bool value) {
-        if(value == focus)
-            return;
-        
-        focus = value;
-        
-        if(value) {
-            stack.AddCondition(UI::ComponentCondition::Focused);
-        }
-        else {
-            stack.RemoveCondition(UI::ComponentCondition::Focused);
-        }
-    }
-
 
     void ListItem::SetSelected(bool value) {
         if(value == selected)
@@ -200,7 +186,7 @@
 
     
     bool ListItem::allowfocus() const {
-        return false;
+        return true;
     }
     
 
--- a/Source/Gorgon/Widgets/ListItem.h	Thu Oct 08 09:32:40 2020 +0300
+++ b/Source/Gorgon/Widgets/ListItem.h	Mon Oct 12 21:24:41 2020 +0300
@@ -18,11 +18,10 @@
     * This widget is meant to be used in listboxes to display the contents.
     * ListItem only supports strings; therefore, conversion is necessary at
     * the Listbox side. ListboxItem does not perform any operation by itself.
-    * All state changes should be facilitated by the listbox. They cannot be
-    * focused, focus state is faked. State changes apart from hover/down/
-    * disabled are not automatic. This item will not handle keys and is not
-    * meant to have focus. Finally, apart from the inherited properties, this
-    * widget does not feature any properties.
+    * All state changes should be facilitated by the listbox. State changes 
+    * apart from hover/down/disabled/focused are not automatic. Finally, apart
+    * from the inherited properties, this widget does not feature any 
+    * properties.
     *
     * ClickEvent will be raise when the widget is clicked. This event will be
     * raised first if the widget will be toggled too. Toggle event will be
@@ -105,15 +104,6 @@
         void OwnIcon(Graphics::Bitmap &&value);
 
 
-        /// Sets the focus state of the widget. This is different from actual
-        /// focus where the widget will receive keyevents.
-        void SetFocusState(bool value);
-
-        /// Returns the current focus state of the widget.
-        bool GetFocusState() const {
-            return focus;
-        }
-
 
         /// Sets selection status of the widget. Depending on the template,
         /// selected status might invert colors or display checkbox,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Source/Gorgon/Widgets/Listbox.h	Mon Oct 12 21:24:41 2020 +0300
@@ -0,0 +1,553 @@
+#pragma once
+
+
+#include "Common.h"
+#include "../UI/ComponentStackWidget.h"
+#include "../Property.h"
+#include "Registry.h"
+#include "ListItem.h"
+#include <vector>
+
+namespace Gorgon { namespace Widgets {
+    
+    //TODO how to deal with pointers and references
+    
+    /**
+     * This is the abstract base of listboxes. It is mainly used to allow list
+     * mixins to access list items.
+     */
+    template<class T_, class W_>
+    class ListBase {
+    public:
+        virtual ~ListBase() {
+        }
+        
+        //TODO add, remove, insert, move, push_back (for back inserter)
+        
+        /// Returns the item at the given point. This operator will not perform
+        /// bounds checking.
+        virtual T_ &operator[](long index) = 0;
+
+        /// Returns the item at the given point. This operator will not perform
+        /// bounds checking.
+        virtual const T_ &operator[](long index) const = 0;
+        
+        /// Returns the number of elements in the list.
+        virtual long GetCount() const = 0;
+        
+        /// For internal use.
+        /// Returns the widget used to represent the item at the given index. This
+        /// function will return nullptr if the index does not currently have a
+        /// visual representation. This is not an edge case, any item that is not
+        /// in view will most likely not have a representation.
+        virtual W_ *getrepresentation(long index) = 0;
+        
+        /// For internal use.
+        /// Returns the first widget used to represent any item at within the 
+        /// listbox. This function will return nullptr if there are no items in the
+        /// list.
+        virtual W_ *getrepresentation() = 0;
+    };
+    
+    /// @cond internal
+    namespace internal {
+        // blank traits has no data associated with the items.
+        // It is good for very long lists. Should be used with
+        // useisvisible = false.
+        template<class T_, class W_>
+        class LBTR_blank {
+        public:
+            void Apply(W_ &, const T_ &, Geometry::Point, Geometry::Size) { }
+            UI::ComponentTemplate::Tag Tag(const T_ &, Geometry::Point, Geometry::Size) {
+                return UI::ComponentTemplate::UnknownTag;
+            }
+        };
+        
+        template <class T_, class W_>
+        class LBTRF_blank {
+            using TR_ = LBTR_blank<T_, W_>;
+        protected:
+            LBTRF_blank() { }
+            ~LBTRF_blank() { }
+            
+            TR_ access(long) {
+                return TR_();
+            }
+            
+            void prepare(W_ &) { }
+            void insert(long, long) { }
+            void move(long, long) { }
+            void remove(long, long) { }
+        };
+        
+        template<class T_, class W_>
+        void SetTextUsingFrom(const T_ &val, W_ &w) {
+            w.SetText(String::From(val));
+        }
+        
+        template<class T_, class W_>
+        void GetTextUsingTo(W_ &w, T_ &val) {
+            val = String::To<T_>(w.GetText());
+        }
+        
+        //This class allows single selection. The selected item will
+        //follow the focus by default. If desired, this can be changed
+        template<class T_, class W_>
+        class LBSELTR_Single {
+        public:
+            /// Returns true if this listbox has a selected item.
+            bool HasSelectedItem() const {
+                return selectedindex != -1;
+            }
+            
+            /// Returns the selected item. If nothing is selected this function
+            /// will throw. You may check if there is a selection using 
+            /// HasSelectedItem function.
+            T_ GetSelectedItem() const {
+                if(selectedindex == -1)
+                    throw std::runtime_error("Nothing is selected.");
+                
+                return dynamic_cast<ListBase<T_, W_>&>(*this)[selectedindex];
+            }
+            
+            /// Returns the index of the selected item. -1 will be returned if 
+            /// nothing is selected.
+            long GetSelectedIndex() const {
+                return selectedindex;
+            }
+            
+            /// Sets the selection to the given index. If index in not within the
+            /// bounds this function will throw std::out_of_range exception. -1
+            /// can be used to remove selected item.
+            void SetSelectedIndex(long index) {
+                if(index < -1 || index >= dynamic_cast<ListBase<T_, W_>&>(*this).GetSize())
+                    throw std::out_of_range("Selected index does not exits");
+                
+                if(index == selectedindex)
+                    return;
+                
+                if(focusonly) {
+                    if(index == -1) {
+                        W_ *elm = dynamic_cast<ListBase<T_, W_>&>(*this).getrepresentation();
+                        if(elm != nullptr && elm->HasParent()) {
+                            elm->GetParent().RemoveFocus();
+                        }
+                    }
+                    else {
+                        W_ *elm = dynamic_cast<ListBase<T_, W_>&>(*this).getrepresentation(index);
+                        if(elm != nullptr) {
+                            elm->Focus();
+                        }
+                        else {
+                            elm = dynamic_cast<ListBase<T_, W_>&>(*this).getrepresentation();
+                            
+                            if(elm != nullptr && elm->HasParent()) {
+                                elm->GetParent().RemoveFocus();
+                            }
+                        }
+                    }
+                    
+                    focusindex = index;
+                }
+                
+                if(selected)
+                    selected->SetSelected(false);
+                
+                if(index != -1) {
+                    W_ *elm = dynamic_cast<ListBase<T_, W_>&>(*this).getrepresentation(index);
+                    
+                    if(elm)
+                        elm->SetSelected(true);
+                }
+                
+                selectedindex = index;
+            }
+            
+            void RemoveSelection() {
+                SetSelectedIndex(-1);
+            }
+            
+            /// Selects the first item that has the given value. If item does
+            /// not exists, this function will remove the selection
+            void SetSelection(T_ item) {
+                auto &me = dynamic_cast<ListBase<T_, W_>&>(*this);
+                
+                for(long i=0; i<me.GetSize(); i++) {
+                    if(me[i] == item) {
+                        SetSelectedIndex(i);
+                        return;
+                    }
+                }
+                
+                SetSelectedIndex(-1);
+            }
+            
+            /// Returns true if the selected item is the focused item. Default
+            /// is true. When set to false, an item can be focused without being
+            /// selected. A listbox with radio buttons should have this value
+            /// set to false.
+            bool IsSelectionFollowingFocus() const {
+                return focusonly;
+            }
+            
+            /// Sets whether the selected item is the focused item. Default
+            /// is true. When set to false, an item can be focused without being
+            /// selected. A listbox with radio buttons should have this value
+            /// set to false.
+            void SetSelectionFollowsFocus(bool value) {
+                if(!focusonly && value) {
+                    if(focusindex != -1) {
+                        if(selectedindex != focusindex) {
+                            selectedindex = focusindex;
+                            SelectionChanged(selectedindex);
+                        }
+                    }
+                    else {
+                        focusindex = selectedindex;
+                    }
+                }
+                
+                focusonly = value;
+            }
+            
+            Event<LBSELTR_Single, long> SelectionChanged = Event<LBSELTR_Single, long>{this};
+            
+        protected:
+            LBSELTR_Single() {
+            }
+            
+            void clicked(long index, W_ &w) {
+                if(focusonly) {
+                    if(selectedindex == index)
+                        return;
+                    
+                    if(selected)
+                        selected->SetSelected(false);
+                    
+                    w.SetSelected(true);
+                    w.Focus();
+                    
+                    selected = &w;
+                    selectedindex = index;
+                    focusindex    = index;
+                }
+                else {
+                    if(focusindex == index)
+                        return;
+                    
+                    w.Focus();
+                    focusindex = index;
+                }
+            }
+            
+            void toggled(long index, W_ &w) {
+                if(selectedindex == index)
+                    return;
+                
+                if(selected)
+                    selected->SetSelected(false);
+                
+                w.SetSelected(true);
+                selectedindex = index;
+                selected = &w;
+                
+                if(focusonly) {
+                    w.Focus();
+                    focusindex = index;
+                }
+            }
+            
+            void apply(long index, W_ &w, const T_ &) {
+                if(index == focusindex) {
+                    w.Focus();
+                }
+                else if(w.IsFocused()) {
+                    w.RemoveFocus();
+                }
+                
+                if(index == selectedindex) {
+                    w.SetSelected(true);
+                    w = &w;
+                }
+                else {
+                    w.SetSelected(false);
+                    
+                    if(&w == selected)
+                        w = nullptr;
+                }
+            }
+            
+            void prepare(W_ &) {
+                //nothing to be done
+            }
+            
+            void insert(long index, long count) { 
+                if(index <= focusindex)
+                    focusindex += count;
+                
+                if(index <= selectedindex)
+                    selectedindex += count;
+            }
+            
+            void move(long index, long target) { 
+                //move triggers apply to both indexes
+                
+                if(index == focusindex) {
+                    focusindex = target;
+                }
+                else if(index > focusindex && target <= focusindex) {
+                    focusindex++;
+                }
+                else if(index < focusindex && target > focusindex) {
+                    focusindex--;
+                }
+                
+                if(index == selectedindex) {
+                    selectedindex = target;
+                }
+                else if(index > selectedindex && target <= selectedindex) {
+                    selectedindex++;
+                }
+                else if(index < selectedindex && target > selectedindex) {
+                    selectedindex--;
+                }
+            }
+            
+            void remove(long index, long count) { 
+                //removed items will be repurposed using apply,
+                if(index <= focusindex) {
+                    if(index+count > focusindex) {
+                        focusindex = -1;
+                    }
+                    else {
+                        focusindex -= count;
+                    }
+                }
+                
+                if(index <= selectedindex) {
+                    if(index+count > selectedindex) {
+                        selectedindex = -1;
+                    }
+                    else {
+                        selectedindex -= count;
+                    }
+                }
+            }
+            
+            void destroy(W_ &w) {
+                if(&w == selected) {
+                    selected = nullptr;
+                    selectedindex = -1;
+                }
+                if(w.IsFocused()) {
+                    focusindex = -1;
+                    w.RemoveFocus();
+                }
+            }
+            
+            bool focusonly = true;
+            
+            long focusindex = -1, selectedindex = -1;
+            
+            W_ *selected = nullptr;
+        };
+        
+        template<class S_>
+        typename std::enable_if<TMP::FunctionTraits<decltype(&S_::size)>::IsMember, long>::type 
+        getsize(const S_ &storage) {
+            return storage.size();
+        }
+        
+        template<class S_>
+        typename std::enable_if<TMP::FunctionTraits<decltype(&S_::GetCount)>::IsMember, long>::type
+        getsize(const S_ &storage) {
+            return storage.GetCount();
+        }
+    }
+    /// @endcond
+    
+    /**
+     * This is the base class for all listbox like widgets. It is not intended
+     * to be used directly but you may use it to create your own listboxes. The
+     * solid listboxes should be derived from this object and should make 
+     * desired functionality public.
+     * 
+     * Listbox will maintain the necessary number widgets to fill its area. It
+     * does not create widgets for elements falling outside the range. If traits
+     * can return different tags, the widgets to cover its area will be created
+     * separately for these tags. 
+     * 
+     * Listbox has multi-column functionality. However, this is not to be used
+     * for tables. Table systems should insert rows as listbox items, which then
+     * should have cells.
+     * 
+     * *Template parameters*
+     * 
+     * T_ is the stored data type. 
+     * 
+     * S_ is the storage for T_.
+     * 
+     * W_ is the widget that will be used to represent items.
+     * 
+     * TR_ is widget traits class that allows additional properties for each
+     * element. They should contain minimal number of data members. There should
+     * be a function Apply taking `W_ &`, `const T_ &`, Point index, Size total
+     * as parameters applying traits to W_. Additionally, TR_ should have Tag
+     * function that has `const T_ &`, Point index, Size total as parameters and
+     * should return UI::ComponentTemplate::Tag to determine the template for
+     * the widget. The tags should match with the listbox template. If the given
+     * tag is not found, ItemTag will be used.
+     * 
+     * TRF_ is the trait functions that will return/set the traits of items. 
+     * TRF_ should contain access function that should take the index of an item
+     * to return TR_ associated with it.
+     * It should contain `insert(before, count)` to add count elements before
+     * the given index. These elements must be default initialized to a normal
+     * state. 
+     * `move(index, before)` should move the indexed item before the given 
+     * index. 
+     * `remove(index, count)` should remove count number of items 
+     * starting from the given index. 
+     * `prepare(W_ &)` function should prepare the given widget for the first
+     * usage. This is only called once on a newly created widget.
+     * `destroy(W_ &)` function is called when a widget is about to be 
+     * destroyed.
+     * Index parameters should be long int. These functions should be protected
+     * as ListboxBase will be derived from TRF_. Finally, TRF_ should have a 
+     * protected constructor/destructor.
+     * 
+     * useisvisible controls if elements can be hidden. If this is true, TR_
+     * should have IsVisible function returning bool. This parameter will
+     * slowdown listbox considerably when many items are stored as each item
+     * will be checked whether they are visible until to the point of display.
+     * This will not affect the performance on short lists.
+     * 
+     * SELTR_ is the class that will control selection traits of the Listbox.
+     * Defult traits allow single select. It is possible to construct list of 
+     * particular widgets that has no concept of selection if selection traits
+     * is set to internal::LBSELTR_None.
+     * This class should support click and toggle functions both taking long 
+     * index, const T_ & value, W_ &widget. Click function will be called when
+     * the user uses arrow keys to select an item.
+     * apply function should apply selection related traits to the widget taking
+     * long index, W_ &widget, const T_ & value. Any item that is benched will
+     * be applied to as well with an index of -1.
+     * This class should contain `insert(before, count)` to add count elements
+     * before the given index. These elements must be default initialized to a
+     * non-selected normal state. 
+     * `move(index, before)` should move the indexed item before the given 
+     * index. 
+     * `remove(index, count)` should remove count number of items 
+     * starting from the given index. 
+     * `prepare(W_ &)` function should prepare the given widget for the first
+     * usage. This is only called once on a newly created widget.
+     * `destroy(W_ &)` function is called when a widget is about to be 
+     * destroyed.
+     * Index parameters should be long int. These functions should be protected
+     * as ListboxBase will be derived from SELTR_. Finally, SELTR_ should have a 
+     * protected constructor/destructor.
+     * 
+     * TW_ function should take a T_ and set this to its W_ representation. 
+     * 
+     * WT_ should read the data from W_ and set it to T_.
+     */
+    template<
+        class T_, class S_, class W_, class TR_, class TRF_, 
+        bool useisvisible = false, class SELTR_ = internal::LBSELTR_Single<T_, W_>,
+        void (*TW_)(const T_ &, W_ &) = internal::SetTextUsingFrom<T_, W_>,
+        void (*WT_)(W_ &, T_ &)       = internal::GetTextUsingTo  <T_, W_>
+    >
+    class ListboxBase : public UI::ComponentStackWidget, public ListBase<T_, W_>, public TRF_, public SELTR_ {
+    public:
+        
+        /// Returns the item at the given point. This operator will not perform
+        /// bounds checking.
+        virtual T_ &operator[](long index) override {
+            return storage[index];
+        }
+
+        /// Returns the item at the given point. This operator will not perform
+        /// bounds checking.
+        virtual const T_ &operator[](long index) const override {
+            return storage[index];
+        }
+        
+        /// Returns the number of elements in the list.
+        virtual long GetCount() const override {
+            return internal::getsize(storage);
+        }
+        
+        /// For internal use.
+        /// Returns the widget used to represent the item at the given index. This
+        /// function will return nullptr if the index does not currently have a
+        /// visual representation. This is not an edge case, any item that is not
+        /// in view will most likely not have a representation.
+        virtual W_ *getrepresentation(long index) override {
+            return nullptr;
+        }
+        
+        /// For internal use.
+        /// Returns the first widget used to represent any item at within the 
+        /// listbox. This function will return nullptr if there are no items in the
+        /// list.
+        virtual W_ *getrepresentation() override {
+            return nullptr;
+        }
+        
+        auto begin() {
+            return storage.begin();
+        }
+        
+        auto begin() const {
+            return storage.begin();
+        }
+        
+        auto end() {
+            return storage.end();
+        }
+        
+        auto end() const {
+            return storage.end();
+        }
+        
+    protected:
+        ListboxBase(const UI::Template &temp) : 
+            ComponentStackWidget(temp)
+        {
+            stack.HandleMouse();
+        }
+        
+        ~ListboxBase() { }
+        
+        S_ storage;
+    };
+    
+    /**
+     * This is a simple listbox. It does not store any additional information
+     * about each item, therefore, can store large amounts of items. Items are
+     * stored in a std::vector. Only one element can be selected at a time.
+     */
+    template<class T_>
+    class SimpleListbox : 
+        public ListboxBase<T_, 
+            std::vector<T_>, ListItem, 
+            internal::LBTR_blank <T_, ListItem>,
+            internal::LBTRF_blank<T_, ListItem>
+        >
+    {
+        using Base = ListboxBase<T_, 
+            std::vector<T_>, ListItem, 
+            internal::LBTR_blank <T_, ListItem>,
+            internal::LBTRF_blank<T_, ListItem>
+        >;
+    public:
+        SimpleListbox() : Base(Registry::Active()[Registry::Checkbox_Regular]) {
+        }
+        
+        virtual bool Activate() {
+            return false;
+        }
+    };
+    
+} }
+
--- a/Source/Gorgon/Widgets/Slider.h	Thu Oct 08 09:32:40 2020 +0300
+++ b/Source/Gorgon/Widgets/Slider.h	Mon Oct 12 21:24:41 2020 +0300
@@ -48,7 +48,7 @@
         internal::SliderValueMapping valuemapping = internal::SliderValueMapping::OneValue
         
     >
-    class SliderBase: 
+    class SliderBase : 
         public UI::ComponentStackWidget,
         public P_<
             UI::internal::prophelper<SliderBase<T_, DIV_, VAL_, P_, interactive, valuemapping>, T_>, 
--- a/Testing/Source/Manual/UI_Generate.cpp	Thu Oct 08 09:32:40 2020 +0300
+++ b/Testing/Source/Manual/UI_Generate.cpp	Mon Oct 12 21:24:41 2020 +0300
@@ -14,6 +14,7 @@
 #include <Gorgon/Widgets/Progressbar.h>
 #include <Gorgon/Widgets/Scrollbar.h>
 #include <Gorgon/Widgets/Composer.h>
+#include <Gorgon/Widgets/Listbox.h>
 #include <Gorgon/Widgets/ListItem.h>
 #include <Gorgon/UI/RadioControl.h>
 #include <Gorgon/UI/Organizers/List.h>
@@ -52,9 +53,12 @@
 
     ///Blank Panel & elements with Registry & Regulars
     /*Widgets::SimpleGenerator generator;
-    generator.Init(13);
+    generator.Density = 5;
+    generator.Init(9);
     generator.UpdateBorders();
-    generator.Border.Radius = 0;
+    generator.Border.Width = 2;
+    generator.Border.Radius = 8;
+    generator.Border.Divisions = 0;
     generator.UpdateDimensions();
     generator.Activate();*/
 
@@ -151,6 +155,7 @@
     scroll1 = 100;
     scroll1.Range = 20;
 
+    
     Widgets::HScrollbar scroll2(200);
     scroll2.Range = 20;
     //scroll2.SetValue(200, false);
@@ -176,6 +181,9 @@
     addme(blank, scroll1);
     addme(blank, scroll2);
     addme(blank, sizef);
+    
+    Widgets::SimpleListbox<int> list;
+    list.SetSelectionFollowsFocus(false);
 
     
 

mercurial