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; }
};
AUI_DOCS_CODE_BEGIN APropertyDef behaves like a class/struct function member:
User u;
u.name() = "Hello"; // calls setName implicitly
EXPECT_EQ(u.name(), "Hello");
AUI_DOCS_CODE_BEGIN

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());
AUI_DOCS_CODE_BEGIN

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());
AUI_DOCS_CODE_BEGIN If it doesn't, simply put an asterisk:
doSomethingWithName(*u.name());
//                 ^^^ HERE
AUI_DOCS_CODE_BEGIN

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;
    }
};
AUI_DOCS_CODE_BEGIN 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);
AUI_DOCS_CODE_BEGIN
u->name() = "Marinette";
AUI_DOCS_CODE_BEGIN 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);
AUI_DOCS_CODE_BEGIN 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";
AUI_DOCS_CODE_BEGIN 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/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]
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/ui/views/src/ExampleWindow.cpp

Views Example — All-in-one views building example.

                      };
                      ALayoutInflater::inflate(it, Stacked { blackRect });
                      connect(
                          it->transformed, blackRect,
                          [blackRect = blackRect.get(),
                           keptTransform = _new<A2DTransform>()](const A2DTransform& transform) {
                              keptTransform->applyDelta(transform);
                              blackRect->setCustomStyle({
                                TransformOffset {
                                  AMetric(keptTransform->offset.x, AMetric::T_PX),

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,
    });
examples/ui/embedded_sdl/CMakeLists.txt

SDL3 — This code demonstrates how to integrate the AUI Framework with SDL3 to create a window with OpenGL rendering.

cmake_minimum_required(VERSION 3.15)

set(AUIB_SDL3_VALIDATE OFF)
auib_import(SDL3 https://github.com/libsdl-org/SDL VERSION release-3.2.26)

aui_executable(aui.example.embedded_sdl)
aui_link(aui.example.embedded_sdl PRIVATE aui::core aui::views)

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/crud/src/main.cpp

7GUIs CRUD — Create/Read/Update/Delete example.

    APropertyPrecomputed<bool> mDeleteEnabled = [this] { return mSelectedUser != nullptr; };
    APropertyPrecomputed<bool> mUpdateEnabled = [this] { return mCreateEnabled && mDeleteEnabled; };

    void createClicked() {
        mUsers.writeScope() << aui::ptr::manage_shared(new User {
          .name = std::exchange(mEditedUser.name, {}), .surname = std::exchange(mEditedUser.surname, {}) });
    }

    void updateClicked() {
        (*mSelectedUser)->name = std::exchange(mEditedUser.name, {});
examples/app/notes/src/main.cpp

Notes App — Note taking app that demonstrates usage of AListModel, AProperty, user data saving and loading.

    /// [newNote]
    void newNote() {
        auto note = aui::ptr::manage_shared(new Note { .title = "Untitled" });
        mNotes.writeScope()->push_back(note);
        mCurrentNote = std::move(note);
    }
    /// [newNote]

    /// [deleteCurrentNote]
examples/ui/contacts/src/main.cpp

AUI Contacts — Usage of AUI_DECLARATIVE_FOR to make a contacts-like application.

                              "This action is irreversible!",
                              AMessageBox::Icon::NONE, AMessageBox::Button::YES_NO) != AMessageBox::ResultButton::YES) {
            return;
        }
        mContacts.writeScope()->removeFirst(mSelectedContact);
        mSelectedContact = nullptr;
    }

    _<AView> indexedList() {
        return AUI_DECLARATIVE_FOR(group, *mContacts | ranges::views::chunk_by([](const _<Contact>& lhs, const _<Contact>& rhs) {
examples/ui/infinite_lazy_list/src/main.cpp

Infinite Lazy List — Usage of AUI_DECLARATIVE_FOR to make an infinite lazy list.

                return aui::ptr::manage_shared(new Item { .value = "Item {}"_format(loadFrom + i) });
            });

            AUI_UI_THREAD { // back to main thread.
                state->items.writeScope()->insertAll(loadedItems);
                state->needMore = false;
            };
        };
    });