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"));
}

Public Methods#

biProjected#


auto APropertyDef::biProjected(ProjectionRead&& projectionRead, ProjectionWrite&& projectionWrite)

Makes a bidirectional projection of this property.

Examples#

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

7GUIs Flight Booker - Flight Booker.

auto dateTextField(DateTextFieldState& state) {
    return _new<ATextField>() AUI_LET {
        AObject::biConnect(
            state.parsed.biProjected(aui::lambda_overloaded {
              [](const AOptional<system_clock::time_point>& v) -> AString {
                  if (!v) {
                      return "";
                  }
                  return formatDate(*v);
examples/7guis/timer/src/main.cpp

7GUIs Timer - Timer example.

            }),
            Horizontal {
              Label { "Duration:" },
              _new<ASlider>() AUI_LET {
                      it&& mDuration.biProjected(aui::lambda_overloaded {
                        [](high_resolution_clock::duration d) -> aui::float_within_0_1 {
                            return float(d.count()) / float(MAX_DURATION.count());
                        },
                        [](aui::float_within_0_1 d) -> high_resolution_clock::duration {
                            return high_resolution_clock::duration(long(float(d) * float(MAX_DURATION.count())));

auto APropertyDef::biProjected(Projection&& projectionBidirectional)

Makes a bidirectional projection of this property (by a single aui::lambda_overloaded).

Examples#

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

7GUIs Flight Booker - Flight Booker.

auto dateTextField(DateTextFieldState& state) {
    return _new<ATextField>() AUI_LET {
        AObject::biConnect(
            state.parsed.biProjected(aui::lambda_overloaded {
              [](const AOptional<system_clock::time_point>& v) -> AString {
                  if (!v) {
                      return "";
                  }
                  return formatDate(*v);
examples/7guis/timer/src/main.cpp

7GUIs Timer - Timer example.

            }),
            Horizontal {
              Label { "Duration:" },
              _new<ASlider>() AUI_LET {
                      it&& mDuration.biProjected(aui::lambda_overloaded {
                        [](high_resolution_clock::duration d) -> aui::float_within_0_1 {
                            return float(d.count()) / float(MAX_DURATION.count());
                        },
                        [](aui::float_within_0_1 d) -> high_resolution_clock::duration {
                            return high_resolution_clock::duration(long(float(d) * float(MAX_DURATION.count())));

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

readProjected#


auto APropertyDef::readProjected(Projection&& projection)

Makes a readonly projection of this property.

Examples#

examples/ui/contacts/src/view/ContactDetailsView.cpp

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

namespace {
_<AView> profilePhoto(const _<Contact>& contact) {
    return Centered {
        Label {} & contact->displayName.readProjected([](const AString& s) {
            return s.empty() ? "?" : AString(1, s.first()).uppercase();
        }) AUI_WITH_STYLE { Opacity(0.5f), FontSize { 32_dp } },
    } AUI_WITH_STYLE {
        FixedSize { 64_dp },
        BorderRadius { 32_dp },
examples/7guis/flight_booker/src/main.cpp

7GUIs Flight Booker - Flight Booker.

        } });
        setContents(Centered {
          Vertical {
            _new<ADropdownList>(AListModel<AString>::make({ "one-way flight", "return flight" })) AUI_LET {
                    connect(it->selectionId().readProjected([](int selectionId) { return selectionId == 1; }),
                            mIsReturnFlight);
                },
            dateTextField(mDepartureDate),
            dateTextField(mReturnDate) AUI_LET { connect(mIsReturnFlight, AUI_SLOT(it)::setEnabled); },
            _new<AButton>("Book") AUI_LET {
examples/7guis/circle_drawer/src/main.cpp

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

          Centered {
            Horizontal {
              Button { "Undo" } AUI_LET {
                  connect(it->clicked, me::undo);
                  it & mState.history.nextAction.readProjected([&](UndoStack::Iterator i) { return i != mState.history.begin(); }) > &AView::setEnabled;
              },
              Button { "Redo" } AUI_LET {
                connect(it->clicked, me::redo);
                it & mState.history.nextAction.readProjected([&](UndoStack::Iterator i) { return i != mState.history.end(); }) > &AView::setEnabled;
              },
examples/7guis/counter/src/main.cpp

7GUIs Counter - Simple counter.

    CounterWindow(): AWindow("AUI - 7GUIs - Counter", 200_dp, 100_dp) {
        setContents(Centered {
          Horizontal {
            _new<ATextField>() AUI_LET {
                AObject::connect(mCounter.readProjected(AString::number<int>), it->text());
                it->setEditable(false);
            },
            Button { "Count" }.connect(&AView::clicked, [&] { mCounter += 1; }),
          },
        });
examples/7guis/timer/src/main.cpp

7GUIs Timer - Timer example.

                        it->setCustomStyle({ Expanding { 1, 0 } });
                    },
              },
            },
            Label {} & mElapsedTime.readProjected([](high_resolution_clock::duration d) {
                return "{:.1f}s"_format(duration_cast<milliseconds>(d).count() / 1000.f);
            }),
            Horizontal {
              Label { "Duration:" },
              _new<ASlider>() AUI_LET {

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.

        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);
            }