Mixins.cpp

// Mixins.cpp
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2007

@import "ExampleUtils.h"
#include "Ceda/cxUtils/Tracer.h"

///////////////////////////////////////////////////////////////////////////////////////////////////
/*
A mixin is a template class parameterised on the base class.   Typically multiple mixins chain 
through the base class, often leading to long single inheritance hierarchies.

A given mixin is often intended to be highly reusable, by being mixed into many concrete classes.  
Therefore it can be regarded as a means of achieving code reuse.

In normal C++, long mixin chains are rather arkward syntactically.

The example below illustrates the mixin technique in normal C++, without making any attempt to be 
practical.

Generated output:

    Mixins example 1
    {
        i = 9
        i = 90
        i = 81
        i = 810
    }
*/
namespace Mixin1
{
    template <typename BaseClass>
    struct M1 : public BaseClass
    {
        void foo(int i) { BaseClass::foo(i+1); }
    };

    template <typename BaseClass>
    struct M2 : public BaseClass
    {
        void foo(int i) { BaseClass::foo(i); BaseClass::foo(i*10); }
    };

    struct X 
    {
        void foo(int i) { Tracer() << "i = " << i << '\n'; }
    };

    struct Y : public M1< M2 < M1< M2<X> > > > {};

    void Run()
    {
        ceda::TraceGroup g("Mixins example 1");
        Y y;

        /*
        foo(7)

             M1
            --->  foo(8)
        
             M2
            --->  foo(8)            foo(80) 

             M1
            --->  foo(9)            foo(81) 

             M2
            --->  foo(9) foo(90)    foo(81) foo(810) 

             X
            --->  i=9    i=90       i=81    i=810
        */
        y.foo(7);
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////
/*
This is the same in functionality to Mixin1, except that the xcpp syntax is used

Generated output:

    Mixins example 2
    {
        i = 9
        i = 90
        i = 81
        i = 810
    }
*/
namespace Mixin2
{
    $mixin M1
    {
        void foo(int i) { BaseClass::foo(i+1); }
    };

    $mixin M2
    {
        void foo(int i) { BaseClass::foo(i); BaseClass::foo(i*10); }
    };

    $mixin X 
    {
        void foo(int i) { Tracer() << "i = " << i << '\n'; }
    };

    $struct Y : mixin [ X M2 M1 M2 M1 ] {};

    void Run()
    {
        ceda::TraceGroup g("Mixins example 2");
        Y y;
        y.foo(7);
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////
/*
This again is the same in functionality to Mixin1.  It shows how anonymous mixins can be directly
inserted into the mixin chain

Generated output:

    Mixins example 3
    {
        i = 9
        i = 90
        i = 81
        i = 810
    }
*/
namespace Mixin3
{
    $mixin M1
    {
        void foo(int i) { BaseClass::foo(i+1); }
    };

    $mixin M2
    {
        void foo(int i) { BaseClass::foo(i); BaseClass::foo(i*10); }
    };

    $struct Y : mixin 
    [ 
        // Anonymous mixin (compare to the body of struct X in Mixin1)
        {
            void foo(int i) { Tracer() << "i = " << i << '\n'; }
        } 

        M2
        
        // Anonymous mixin that is equivalent to M1
        {
            void foo(int i) { BaseClass::foo(i+1); }
        }
        
        M2
        M1
    ] {};

    void Run()
    {
        ceda::TraceGroup g("Mixins example 3");
        Y y;
        y.foo(7);
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////
/*
This provides an illustration of the power of mixins.  The code is incomplete and is only meant
to indicate the basic ideas.

It is assumed that there are 2D scenegraph nodes that each define their width and height and a 
Draw() method to issue OpenGL commands.

Mixins can be used to rotate, mirror, scale, layout horizontally and vertically etc. In principle
these mixins can all be fully inlined, resulting in concrete classes that are equivalent to hand
written versions that apply layout control without using large assemblies of small run time objects.

An important idea is that a data source model can be fed into the mixin chain,  allowing every 
mixin in the chain to directly access the data model variables (invoking their read and write 
barriers as required)
*/

namespace Mixin4
{
    @if (CEDA_IS_WINDOWS_PLATFORM)
    {
        // Node for a 2D scenegraph
        $interface INode
        {
            void GetHeight() const;
            void GetWidth() const;
            void Draw() const;
        };
    
        // Implementation not shown.  Assumes there is a vector<char> called Text in the base.
        $mixin EditBoxMixin
        {
        };
    
        // Rotates the base by 90 degrees
        $mixin Rotate90Mixin
        {
            // Rotation by 90 degrees cause height and width to interchange
            void GetHeight() const { return BaseClass::GetWidth(); }
            void GetWidth() const { return BaseClass::GetHeight(); }
        
            void Draw() const
            {
                // Push and pop matrix
                typename BaseClass::SaveMatrix sm;
            
                // Apply a rotation about z axis by 90 degrees
                glTranslatef(GetAmbientWidth(),0,0);
                glRotatef(90,0,0,1);
            
                BaseClass::Draw();
            }
        };

        // Mirrors the base in the x axis
        $mixin MirrorXMixin
        {
            void Draw() const
            {
                typename BaseClass::SaveMatrix sm;
                glTranslatef(GetAmbientWidth(),0,0);
                glScalef(-1,1,1);
                BaseClass::Draw();
            }
        };

        // Applies a border around the base
        $mixin BorderMixin
        {
            void Draw() const
            {
                typename BaseClass::SaveMatrix sm;
                int mx = BaseClass::GetMarginx();
                int my = BaseClass::GetMarginy();
                int tx = GetAmbientWidth();
                int ty = GetAmbientHeight();
                DrawBorder(Rect2i(0,0,tx,ty),mx,my);
                glTranslatef(mx,my);
                BaseClass::Draw();
            }
            ceda::int32 GetWidth() const { return 2*BaseClass::GetMarginx() + BaseClass::GetWidth(); }
            ceda::int32 GetHeight() const { return 2*BaseClass::GetMarginy() + BaseClass::GetHeight(); }
        };
    
        // Positions Head and Base horizontally
        $mixin HBoxMixin
        {
            typedef BaseClass Tail;
            using typename BaseClass::Head;

            void Draw() const
            {
                int hx = Head::GetWidth();
                int hy = Head::GetHeight();
                int tx = Tail::GetWidth();
                int ty = Tail::GetHeight();

                {
                    ApplyViewTransform vt(Rect2i(0,0,hx,hy));
                    if (vt)
                    {
                        Head::Draw();
                    }
                }

                {
                    ApplyViewTransform vt(Rect2i(hx,0,hx+tx,ty));
                    if (vt)
                    {
                        Tail::Draw();
                    }
                }
            }
            ceda::int32 GetWidth() const { return Head::GetWidth() + Tail::GetWidth(); }
            ceda::int32 GetHeight() const { return CedaMax(Head::GetHeight() , Tail::GetHeight()); }
        };

        // Convenient macro used to create HBoxes within a mixin chain
        @def mHBoxMixin2(c1,c2) =
        {
            c1
            { typedef $$ Head; }
            c2
            HBoxMixin
        }
    
        @if (false)
        {
            $struct Demo isa INode :
                model
                {
                    ceda::int32 marginx;
                    ceda::int32 marginy;
                    string text;
                }
                mixin
                [
                    mVBoxMixin3
                    (
                        MenuMixin,
                        mHBoxMixin2(SliderMixin Rotate90Mixin, EditBoxMixin),
                        StatusBarMixin
                    )
                ]
            {
            };
        }
    }
    @else
    {
        @println(todo : fix mixins.cpp example 4)
    }
    void Run()
    {
        ceda::TraceGroup g("Mixins example 4");
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////
/*
Within a mixin '$this' can be used in place of 'this' as a pointer to the final class.
This provides access to public members defined in the final class that appear after the
mixin!

Generated output:

    Mixins example 5
    {
        g1()
        g2()
    }
*/
namespace Mixin5
{
    $mixin M1
    {
        void f1() 
        { 
            $this->g1();
        }
        void f2() const
        { 
            $this->g2();
        }
    };

    $struct Y : mixin 
    [ 
        M1
    ] 
    {
        void g1()
        {
            Tracer() << "g1()\n";
        }
        void g2() const
        {
            Tracer() << "g2()\n";
        }
    };

    void Run()
    {
        ceda::TraceGroup g("Mixins example 5");
        
        Y y;
        y.f1();
        y.f2();
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////
/*
Mixins can be forward declared
*/
namespace MixinForwardDeclarations
{
    // Forward declaration
    $mixin M1;
    
    // Forward declaration
    $mixin M2<typename T, int X>;
    
    $mixin M1
    {
        void foo() {}
    };
}

///////////////////////////////////////////////////////////////////////////////////////////////////
namespace Mixin
{
    void Run()
    {
        Mixin1::Run();
        Mixin2::Run();
        Mixin3::Run();
        Mixin4::Run();
        Mixin5::Run();
    }
}