scriptable widget system

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

scriptable widget system

István Csanády
Hi All!

I am developing a custom widget system. The widgets are written in C++, since the rendering requires very high performance. The widgets are loaded from xml files. A widget can contain several different UI elements, for example a slider, a textbox, a label, buttons, etc... Until now I used a simple but quite powerful solution to synchronize the UI values and the python object attributes. The concept is the following: C++ UI elements can have properties (see the code below). Properties can be bounded to a python object's attribute. If a property changes, it sets the python attribute, and after the setter ran, it gets the actual python value, and sets the property's value to it. (For better understanding: when a property changes because of some user input it rather suggests a value then set it. This is useful when for example you want a slider to be able to set only to odd values, or want to limit the availible values of a numeric text box. So this way you have full control of the "control UI elements" values.) From the other side: when you set an attribute of the python object the object checks if it is bounded to any C++ property, and if it is, then it sets the property to the value. Here are some code snippets:

This is the C++ property:

template <typename T> class Property : public PythonPropertyBinder {

        

    public:

        

        void bindToAttribute(PythonComponent* component,std::string name) {

            //Add a PyCFunction to the bindings of the python object. 

        }

        

        void unbind() {

            //Remove the bindings.

        }

        

        T& operator= (const Property<T>& p) {

            return (*this = p.value);

        }

        

        T & operator = (const T &i) {

            if(!updating) {

                updating = true;

                if(component) {

                    component->setAttribute(i, pythonAttributeName.c_str());

                    component->getAttribute(value,pythonAttributeName.c_str());

                    PyErr_Print();

                }

                else {

                    value = i;

                }

                updating = false;

                notifyObservers();

                return value;

            }else {

                return value;

            }

        }

        

        operator T const & () const {

            return value;

        }

        

        

        void addObserver(PythonPropertyBinderObserver* observer) {

            observers.insert(observer);

        }

        

        void removeObserver(PythonPropertyBinderObserver* observer) {

            observers.erase(observer);

        }

        

        Property(const T& v):Property() {

            value = v;


        }

        

        Property():updating(false),component(nullptr) {

            //set up stuff...


        }

        

        ~Property() {

            unbind();

        }

        

    private:


        T value;

        std::string pythonAttributeName;

       

        //...not interesting

    };





And for example a slider looks like the following:


class Slider : public View, public PythonPropertyBinderObserver {

        

    public:

        //...


        Property<double> value;

        Property<double> low;

        Property<double> high;

        //...

};





The python class:



class Component(object):


    def bind_to_attribute(self,attribute_name, cpp_property_setter):

        if not hasattr(self,attribute_name):

            return

        if self.binder_functions.has_key(attribute_name):

            self.binder_functions[attribute_name].add(cpp_property_setter)

        else:

            s = set()

            s.add(cpp_property_setter)

            self.binder_functions[attribute_name] = s 

            

    def unbind_from_attribute(self,attribute_name, cpp_property_setter):

        if self.binder_functions.has_key(attribute_name):

            self.binder_functions[attribute_name].remove(cpp_property_setter)


    def __setattr__(self,attributename,value):

        super(Component,self).__setattr__(attributename,value)

        self.sync_attribute(attributename)


    def sync_attribute(self,attributename):

        if(hasattr(self,'binder_functions') and self.binder_functions.has_key(attributename)):

            s = self.binder_functions[attributename]

            for cpp_property_setter in s:

                cpp_property_setter()


    '''

    And several other methods...

    '''


So this way I can simply bind a UI element's property to a python attribute, and it is guaranteed that they will be the same (until a python property is not accessed by its setter...) 

For example:

     

    

class Slider(Component):

        

    def get_value(self):

        return self.__value

        

    def set_value(self,value):

        if((value<=self.high and value>=self.low) or (value>=self.high and value<=self.low)):

            if self.integers_only:

                self.__value = round(value)

            else:

                self.__value = value

            self.run()

            

    def get_high(self):

        return self.__high    

    

    def set_high(self,high):

        self.__high = high

        

    def get_low(self):

        return self.__low

    

    def set_low(self,low):

        self.low = low

        

    

    value = property(get_value,set_value)

    low = property(get_low,set_low)

    high = property(get_high,set_high)


And then all I have to do from the C++ UI:


    Slider* slider = new Slider(Point(20, 30.), 195.);

    

    slider->value.bindToAttribute(controller->getModel(),"value");

    slider->low.bindToAttribute(controller->getModel(),"low");

    slider->high.bindToAttribute(controller->getModel(),"high");




So this way:

- the python objects work like models

- the values are synced

- the script writer is not able access to any other properties of the UI elements

- however one have total control of the values

- all the widgets can work without binded to any UI elements

- if I want to serialize the objects (and I want to), the PyCFunctions and other - runtime added - objects can be easily excluded

- properties can be easily bounded and unbouded

- I did not have to create any new types, such as MySyncedFloat, MySyncedString etc.

- the script writer doesn't even have to know about that a value will be bounded to a UI element

- the UI elements are not strongly connected to any python code

- more UI elements can be bounded to a single python attribute for example I can bind an attribute to a slider's max value and to a numeric text box at the same time

- this approach can be easily used with a graphical interface builder: one just have to drag and drop some UI elements to a widget, and tell the interface builder that a property of an element will be bounded to a python attribute. Then it can be exported to an XML and loaded runtime and bind the properties runtime. Cool.



 This approach worked very well. But... Few days ago our UI/UX designer created some new UI elements which I call "batched controllers". A batched controller is an array of same controllers for example sliders. The number of the sliders can be changed runtime for example when I click on a button a new slider is added. And this type of view can not be used with the previous approach, since a value is determined by two values: the ordinal of the slider and the value itself. 


So my question is: does anybody have any idea how to create something that works like I previously specified for these "batched controllers" (==properties with indices)


István


_______________________________________________
Cplusplus-sig mailing list
[hidden email]
http://mail.python.org/mailman/listinfo/cplusplus-sig