Skip to content

AUI_REACT#

Explicitly denotes a reactive expression.

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

Definition#

#define AUI_REACT(...)        \
    ::aui::react::makeExpression(       \
        [=]() -> decltype(auto) { return (__VA_ARGS__); } \
    )

Detailed Description#

AUI_REACT is a core component of AUI Framework's reactive reactive programming model. It's used to create reactive expressions that automatically update UI elements when their dependent values change.

The expression is a C++ expression that depends on AProperty values:

AUI_REACT(expression)

Basic example#

This creates a label that automatically updates when property mCounter changes:

class CounterWindow : public AWindow {
public:
    CounterWindow() : AWindow("AUI - 7GUIs - Counter", 200_dp, 100_dp) {
        setContents(Centered {
          Horizontal {
            Label { AUI_REACT("Count: {}"_format(mCounter)) },
            Button { Label { "Count" }, [this] { mCounter += 1; } },
          } AUI_OVERRIDE_STYLE { LayoutSpacing { 4_dp } },
        });
    }

private:
    AProperty<int> mCounter;
};

Formatted label example#

class TimerWindow : public AWindow {
public:
    TimerWindow() : AWindow("AUI - 7GUIs - Timer", 300_dp, 50_dp) {
        setContents(Centered {
          Vertical::Expanding {
            Horizontal {
              Label { "Elapsed Time:" },
              Centered::Expanding {
                _new<AProgressBar>() AUI_LET {
                        it & mElapsedTimeRatio;
                        it->setCustomStyle({ Expanding { 1, 0 } });
                    },
              },
            } AUI_OVERRIDE_STYLE { LayoutSpacing { 4_dp } },
            Label { AUI_REACT("{:.1f}s"_format(duration_cast<milliseconds>(*mElapsedTime).count() / 1000.f)) },
            Horizontal {
              Label { "Duration:" },
              Slider {
                .value = AUI_REACT(float(mDuration->count()) / float(MAX_DURATION.count())),
                .onValueChanged =
                    [this](aui::float_within_0_1 newValue) {
                        mDuration =
                            high_resolution_clock::duration(long(float(MAX_DURATION.count()) * float(newValue)));
                    },
              } AUI_OVERRIDE_STYLE { Expanding { 1, 0 } },
            } AUI_OVERRIDE_STYLE { LayoutSpacing { 4_dp } },
            _new<AButton>("Reset Timer") AUI_OVERRIDE_STYLE {
                  Expanding { 1, 0 },
                } AUI_LET { connect(it->clicked, me::reset); },
          } AUI_OVERRIDE_STYLE { LayoutSpacing { 4_dp } },
        });

        connect(mTimer->fired, me::update);
        mTimer->start();
    }

private:
    _<ATimer> mTimer = _new<ATimer>(100ms);
    high_resolution_clock::time_point mStartTime = high_resolution_clock::now();
    AProperty<high_resolution_clock::time_point> mCurrentTime;
    AProperty<high_resolution_clock::duration> mDuration = 30s;

    APropertyPrecomputed<high_resolution_clock::duration> mElapsedTime = [&] {
        return std::min(mCurrentTime - mStartTime, *mDuration);
    };

    APropertyPrecomputed<aui::float_within_0_1> mElapsedTimeRatio = [&] {
        return float(mElapsedTime->count()) / float(mDuration->count());
    };

    void update() { mCurrentTime = high_resolution_clock::now(); }

    void reset() { mStartTime = high_resolution_clock::now(); }
};

Implementation details#

When used in declarative UI building, AUI_REACT creates an instance of APropertyPrecomputed<T> behind the scenes, which:

  1. Evaluates the expression initially.
  2. Sets up observers for all dependent properties.
  3. Re-evaluates when dependencies change.

The macros itself consists of a lambda syntax with forced [=] capture and explicit decltype(auto) return type. The lambda is wrapped with aui::react::Expression to be strongly typed.

The decltype(auto) return type is used to avoid property copy when referenced.

Examples#

examples/app/notes/src/main.cpp

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

                      },
                      /// [scrollarea]
                      AScrollArea::Builder()
                          .withContents(
                          AUI_DECLARATIVE_FOR(note, *mNotes, AVerticalLayout) {
                              observeChangesForDirty(note);
                              return notePreview(note) AUI_LET {
                                  connect(it->clicked, [this, note] { mCurrentNote = note; });
                                  it& mCurrentNote > [note](AView& view, const _<Note>& currentNote) {
                                      ALOG_DEBUG(LOG_TAG) << "currentNote == note " << currentNote << " == " << note;

examples/ui/contacts/src/main.cpp

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

        mSelectedContact = nullptr;
    }

    _<AView> indexedList() {
        return AUI_DECLARATIVE_FOR(group, *mContacts | ranges::views::chunk_by([](const _<Contact>& lhs, const _<Contact>& rhs) {
                                return groupLetter(lhs->displayName) == groupLetter(rhs->displayName);
                            }), AVerticalLayout) {
            auto firstContact = *ranges::begin(group);
            auto firstLetter = groupLetter(firstContact->displayName);
            ALogger::info("Test") << "Computing view for group " << AString(1, firstLetter);

examples/ui/views/src/ExampleWindow.cpp

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

                                    }
                                }),
                            _new<ASpacerExpanding>(),
                          } AUI_OVERRIDE_STYLE { LayoutSpacing { 4_dp } },
                          AUI_DECLARATIVE_FOR(i, *state->colors, AWordWrappingLayout) {
                              return Horizontal {
                                  _new<ALabel>(i.toString()) AUI_OVERRIDE_STYLE {
                                      TextColor { i.readableBlackOrWhite() },
                                  }
                              } AUI_OVERRIDE_STYLE {

examples/ui/opengl_simple/src/main.cpp

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

          _new<MyRenderer>(state) AUI_OVERRIDE_STYLE { Expanding() },
          Horizontal::Expanding {
            Vertical {
              Vertical {
                Label { AUI_REACT(fmt::format("FPS: {:.1f}", *state->fps)) },
              } AUI_OVERRIDE_STYLE {
                    Padding(16_dp),
                    BackgroundSolid { AColor::WHITE.transparentize(0.5f) },
                    MinSize { 150_dp, {} },
                  },

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

7GUIs Counter - Simple counter.

public:
    CounterWindow() : AWindow("AUI - 7GUIs - Counter", 200_dp, 100_dp) {
        setContents(Centered {
          Horizontal {
            Label { AUI_REACT("Count: {}"_format(mCounter)) },
            Button { Label { "Count" }, [this] { mCounter += 1; } },
          } AUI_OVERRIDE_STYLE { LayoutSpacing { 4_dp } },
        });
    }

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

7GUIs Flight Booker - Flight Booker.

            std::unique_lock lock(state.userChangesText);
            state.parsed = parseDate(s);
        });
        AObject::connect(
            AUI_REACT(ass::PropertyList { BackgroundSolid(!state->parsed->hasValue() ? AColor::RED : AColor::WHITE) }),
            AUI_SLOT(it)::setCustomStyle);
    };
}

struct State {

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

7GUIs Timer - Timer example.

                        it->setCustomStyle({ Expanding { 1, 0 } });
                    },
              },
            } AUI_OVERRIDE_STYLE { LayoutSpacing { 4_dp } },
            Label { AUI_REACT("{:.1f}s"_format(duration_cast<milliseconds>(*mElapsedTime).count() / 1000.f)) },
            Horizontal {
              Label { "Duration:" },
              Slider {
                .value = AUI_REACT(float(mDuration->count()) / float(MAX_DURATION.count())),
                .onValueChanged =

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

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

                          "", 200_dp, 50_dp, dynamic_cast<AWindow*>(AWindow::current()), WindowStyle::MODAL);
                      radiusPopup->setContents(Vertical {
                        Label { "Adjust diameter of circle at {}."_format(circle->position) },
                        Slider {
                          .value = AUI_REACT(circle->radius / MAX_RADIUS),
                          .onValueChanged =
                              [this, circle](aui::float_within_0_1 s) {
                                  circle->radius = s * MAX_RADIUS;
                                  mState->circles.notify();
                              },

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 {
          AUI_REACT(contact->displayName->empty() ? "?" : AString(1, contact->displayName->first()).uppercase())
        } AUI_OVERRIDE_STYLE { Opacity(0.5f), FontSize { 32_dp } },
    } AUI_OVERRIDE_STYLE {
        FixedSize { 64_dp },
        BorderRadius { 32_dp },
        BackgroundGradient { AColor::GRAY.lighter(0.5f), AColor::GRAY, 163_deg },