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; }
};
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:
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());
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;
}
};
AProperty:
auto observer = _new<LogObserver>();
auto u = _new<User>();
u->name() = "Chloe";
// ...
AObject::connect(u->name().changed, AUI_SLOT(observer)::log);
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);
Subsequent changes to field would send updates as well:
Assignment operation above makes an additional line to output: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#
Makes ASlotDef that assigns value to this property.
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.
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;