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"));
}
Public Methods#
biProjected#
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())));
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#
Notify observers that a change was occurred (no preconditions).
Examples#
examples/7guis/circle_drawer/src/main.cpp
7GUIs Circle Drawer - Undo, redo, dialog control.
readProjected#
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.
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);
}