Skip to content

APropertyDef#

Property implementation to use with custom getter/setter.

Header:#include <AUI/Common/APropertyDef.h>
CMake:aui_link(my_target PUBLIC aui::core)

Detailed Description#

Experimental Feature

This API is experimental. Experimental APIs are likely to contain bugs, might be changed or removed in the future.

You can use this way if you are required to define custom behaviour on getter/setter. As a downside, you have to write extra boilerplate code: define property, data field, signal, getter and setter checking equality. Also, APropertyDef requires the class to derive AObject. Most of AView's properties are defined this way.

See property system for usage examples.

Performance considerations#

APropertyDef does not involve extra runtime overhead between assignment and getter/setter.

Declaration#

To declare a property with custom getter/setter, use APropertyDef template. APropertyDef-based property is defined by const member function as follows:

class User : public AObject {
public:
    auto name() const {
        return APropertyDef {
            this,
            &User::getName,   // this works too: &User::mName
            &User::setName,
            mNameChanged,
        };
    }

private:
    AString mName;
    emits<AString> mNameChanged;

    void setName(AString name) {
        // APropertyDef requires us to emit
        // changed signal if value is actually
        // changed
        if (mName == name) {
            return;
        }
        mName = std::move(name);
        emit mNameChanged(mName);
    }

    const AString& getName() const { return mName; }
};
APropertyDef behaves like a class/struct function member:
User u;
u.name() = "Hello"; // calls setName implicitly
EXPECT_EQ(u.name(), "Hello");

Note

Properties defined with APropertyDef instead of AProperty impersonate themselves by trailing braces (). We can't get rid of them, as APropertyDef is defined thanks to member function. In comparison to user->name, think of user->name() as the same kind of property except defining custom behaviour via function, hence the braces ().

For the rest, APropertyDef is identical to AProperty including seamless interaction:

User u;
u.name() = "Hello";
u.name() += " world!";
EXPECT_EQ(u.name(), "Hello world!");
EXPECT_EQ(u.name()->length(), AString("Hello world!").length());

Note

In order to honor getters/setters, APropertyDef calls getter/setter instead of using += on your property directly. Equivalent code will be:

u.setName(u.getName() + " world!")

The implicit conversions work the same way as with AProperty:

auto doSomethingWithName = [](const AString& name) { EXPECT_EQ(name, "Hello"); };
User u;
u.name() = "Hello";
doSomethingWithName(u.name());
If it doesn't, simply put an asterisk:
doSomethingWithName(*u.name());
//                 ^^^ HERE

Observing changes#

All property types offer .changed field which is a signal reporting value changes. Let's make little observer object for demonstration:

class LogObserver : public AObject {
public:
    void log(const AString& msg) {
        ALogger::info("LogObserver") << "Received value: " << msg;
    }
};
The usage is close to AProperty:
auto observer = _new<LogObserver>();
auto u = _new<User>();
u->name() = "Chloe";
// ...
AObject::connect(u->name().changed, AUI_SLOT(observer)::log);
u->name() = "Marinette";
Code produces the following output:
[07:58:59][][LogObserver][INFO]: Received value: Marinette

Making connection to property directly instead of .changed:

auto observer = _new<LogObserver>();
auto u = _new<User>();
u->name() = "Chloe";
// ...
AObject::connect(u->name(), AUI_SLOT(observer)::log);
Code above produces the following output:
[07:58:59][][LogObserver][INFO]: Received value: Chloe

Subsequent changes to field would send updates as well:

u->name() = "Marinette";
Assignment operation above makes an additional line to output:
[07:58:59][][LogObserver][INFO]: Received value: Marinette

Whole program output when connecting to property directly:

[07:58:59][][LogObserver][INFO]: Received value: Chloe
[07:58:59][][LogObserver][INFO]: Received value: Marinette

Public fields and Signals#


base#

const M* base

AObject which this property belongs to.


changed#

const emits<SignalArg>& changed

Reference to underlying signal emitting on value changes.


get#

Getter get

Getter. Can be pointer-to-member(function or field) or lambda.

Examples#

examples/app/game_of_life/src/main.cpp

Game of Life - Game of Life implementation that uses advanced large dynamic data rendering techniques such as ITexture, AImage to be GPU friendly. The computation is performed in AThreadPool.

        mFrame = AThreadPool::global() * [&] {
            for (int y = 0; y < mSize.y; ++y) {
                for (int x = 0; x < mSize.x; ++x) {
                    glm::ivec2 i { x, y };
                    get(mNextPopulation, i) = [&] {
                        auto around = cellsAround(i);
                        switch (around) {
                            default:
                                return CellState::DEAD;
                            case 2:
examples/app/minesweeper/src/Style.cpp

Minesweeper Game - Minesweeper game implementation driven by ass.

    }

    void setupConnections(AView* view, const _<AAssHelper>& helper) override {
        IAssSubSelector::setupConnections(view, helper);
        view->customCssPropertyChanged.clearAllOutgoingConnectionsWith(helper.get());
        AObject::connect(view->customCssPropertyChanged, AUI_SLOT(helper)::onInvalidateStateAss);
    }
};
/// [CellSelector]

set#

Setter set

Setter. Can be pointer-to-member(function or field) or lambda.

The setter implementation typically emits changed signal. If it is, it must emit changes only if value is actually changed.

void setValue(int value) {
  if (mValue == value) {
    return;
  }
  mValue = value;
  emit mValueChanged(valueChanged);
}

Examples#

examples/app/fractal/src/FractalView.cpp

Fractal Example - Fractal viewer application demonstrating usage of custom shaders.

}
)", { "pos", "uv" }, gl::GLSLOptions { .custom = true });
    mShader.compile();
    mShader.use();
    mShader.set(UNIFORM_TR, mTransform);
    mShader.set(UNIFORM_SQ, 1.f);

    mTexture = _new<gl::Texture2D>();
    mTexture->tex2D(*AImage::fromUrl(":img/color_scheme_wikipedia.png"));
}
examples/ui/opengl_simple/src/main.cpp

OpenGL Example - Demonstrates how to integrate custom OpenGL rendering with AUI Framework.

}   // namespace

AUI_ENTRY {
    // ask aui to provide opengl context for us. (no fallback to software rendering)
    ARenderingContextOptions::set({
        .initializationOrder = {
            ARenderingContextOptions::OpenGL{},
        },
        .flags = ARenderContextFlags::NO_SMOOTH | ARenderContextFlags::NO_VSYNC,
    });

Public Methods#

assignment#


auto APropertyDef::assignment()

Makes ASlotDef that assigns value to this property.

notify#


void APropertyDef::notify()

Notify observers that a change was occurred (no preconditions).

Examples:

examples/7guis/circle_drawer/src/main.cpp

7GUIs Circle Drawer - Undo, redo, dialog control.

    void add(_unique<IAction> action) {
        action->redo();
        nextAction = std::next(mStack.insert(mStack.erase(*nextAction, mStack.end()), std::move(action)));
        nextAction.notify();
    }

    Iterator begin() const { return mStack.begin(); }

    Iterator end() const { return mStack.end(); }

writeScope#


aui::PropertyModifier<APropertyDef> APropertyDef::writeScope()
Returns
@copybrief aui::PropertyModifier See aui::PropertyModifier.

Examples:

examples/7guis/circle_drawer/src/main.cpp

7GUIs Circle Drawer - Undo, redo, dialog control.

        class ActionAddCircle : public IAction {
        public:
            ActionAddCircle(_<State> state, Circle circle) : mState(std::move(state)), mCircle(std::move(circle)) {}
            ~ActionAddCircle() override = default;
            void undo() override { mState->circles.writeScope()->pop_back(); }
            void redo() override { mState->circles.writeScope()->push_back(mCircle); }

        private:
            _<State> mState;
            Circle mCircle;