Yet again I am rewriting the property grid. I want it to have very fluent API. I mean something like this:
auto object = model->object("Material", material);
object
.property("Alpha cutout", &Material::isAlphaCutout, &Material::enableAlphaCutout)
.property("Alpha to coverage", &Material::isAlphaToCoverage, &Material::enableAlphaToCoverage)
.property("Backface culling", &Material::isBackfaceCulling, &Material::enableBackfaceCulling)
.property("Shadow receiver", &Material::isShadowReceiver, &Material::enableShadowReceiving)
.property("Z test", &Material::isZTest, &Material::enableZTest)
.property("Shader", [](Material* material) -> const char* { return material->getShader()->getPath().c_str(); });
object.array("Textures", material->getTextureCount(), &Material::getTexture, [](Lumix::Texture* texture) -> const char* { return texture->getPath().c_str(); })
.property("Width", &Lumix::Texture::getWidth)
.property("Height", &Lumix::Texture::getHeight)
.property("Bytes per pixel", &Lumix::Texture::getBytesPerPixel);
As you can see, a property must have a getter and it can have a setter. Most of time they are just pointers to member functions. However sometimes I want to use simple functions or lambdas. These two options differ in signature:
template <typename T>
class Object
{
template <typename R>
Object<T>& property(const char* name, R (T::*getter)());
template <typename R>
Object<T>& property(const char* name, R (*getter)(T*));
};
This seems to be pretty straightforward, however it does not work with lambdas passed directly:
object.property("Shader", [](Material* material) -> const char* { return material->getShader()->getPath().c_str(); });
// could not deduce template argument for 'R (__cdecl *)(T *)'
A possible solution would be:
template <typename T>
class Object
{
template <typename Getter>
Object<T>& propertyFunction(const char* name, Getter getter);
template <typename Getter>
Object<T>& propertyMethod(const char* name, Getter getter);
};
object
.propertyMethod("Z test", &Material::isZTest, &Material::enableZTest)
.propertyFunction("Shader", [](Material* material) -> const char* { return material->getShader()->getPath().c_str(); });
As you can see this is not as fluent as I want it to be. This is where a ugly template trick come in. The only way I found out how to make it better is based on the fact that the function has one argument while the method has none.
template <typename T>
struct FunctionTraits : public FunctionTraits<decltype(&T::operator())>
{};
template <typename ClassType, typename ReturnType, typename... Args>
struct FunctionTraits<ReturnType(ClassType::*)(Args...) const>
{
enum { arity = sizeof...(Args) };
};
template<typename Functor, size_t NArgs, typename Ret>
struct CountArg : std::enable_if<FunctionTraits<Functor>::arity == NArgs, Ret>
{};
template <typename Getter>
typename CountArg<Getter, 1, Object>::type property(QString name, Getter getter) { ... }
template <typename Getter>
typename CountArg<Getter, 0, Object>::type property(QString name, Getter getter) { ... }