AUI Framework  develop
Cross-platform base for C++ UI apps
Loading...
Searching...
No Matches
examples/7guis/circle_drawer/src/main.cpp
Note
This Source File belongs to 7GUIs Circle Drawer Example. Please follow the link for example explanation.
/*
* AUI Framework - Declarative UI toolkit for modern C++20
* Copyright (C) 2020-2025 Alex2772 and Contributors
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <AUI/Platform/Entry.h>
#include <AUI/Platform/AWindow.h>
#include <AUI/Util/UIBuildingHelpers.h>
#include "AUI/View/AProgressBar.h"
#include "AUI/View/ASlider.h"
#include "AUI/View/AButton.h"
using namespace declarative;
struct Circle {
    glm::vec2 position;
    float radius = 10_dp;
};
class IAction {   // IAction: candidate to be committed to the framework
public:
    virtual ~IAction() = default;
    virtual void undo() = 0;
    virtual void redo() = 0;
};
class UndoStack {
public:
    using Container = std::list<_unique<IAction>>;
    using Iterator = Container::const_iterator;
private:
    Container mStack;
public:
    void undo() {
        if (nextAction == mStack.begin()) {
            return;
        }
        nextAction = std::prev(*nextAction);
        (**nextAction)->undo();
    }
    void redo() {
        if (nextAction == mStack.end()) {
            return;
        }
        (**nextAction)->redo();
        nextAction = std::next(*nextAction);
    }
    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();
    }
    AProperty<Iterator> nextAction = mStack.end();
};
struct State {
    AProperty<std::list<Circle>> circles;
    UndoStack history;
};
static constexpr auto MAX_RADIUS = 128.f;
class CircleDrawArea : public AView {
public:
    CircleDrawArea(_<State> state) : mState(std::move(state)) {
        setCustomStyle({
          Expanding(),
          BackgroundSolid(AColor::WHITE),
          Border(1_px, AColor::GRAY),
          AOverflow::HIDDEN_FROM_THIS,
        });
        connect(mState->circles.changed, me::redraw);
        connect(mHoveredCircle.changed, me::redraw);
    }
    void render(ARenderContext ctx) override {
        AView::render(ctx);
        for (const auto& circle : *mState->circles) {
            if (&circle == mHoveredCircle) {
                ctx.render.roundedRectangle(
                    ASolidBrush { AColor::GRAY }, circle.position - circle.radius, glm::vec2(circle.radius * 2.f),
                    circle.radius);
            }
            ctx.render.roundedRectangleBorder(
                ASolidBrush { AColor::BLACK }, circle.position - circle.radius, glm::vec2(circle.radius * 2.f),
                circle.radius, 1);
        }
    }
    void onPointerMove(glm::vec2 pos, const APointerMoveEvent& event) override {
        AView::onPointerMove(pos, event);
        mHoveredCircle = [&] {
            Circle* result = nullptr;
            float nearestDistanceToCursor = std::numeric_limits<float>::max();
            for (auto& circle : mState->circles.raw) {
                float distanceToCursor = glm::distance2(circle.position, pos);
                if (distanceToCursor > nearestDistanceToCursor) {
                    continue;
                }
                if (distanceToCursor > circle.radius * circle.radius) {
                    continue;
                }
                result = &circle;
                nearestDistanceToCursor = distanceToCursor;
            }
            return result;
        }();
    }
protected:
    AMenuModel composeContextMenu() override {
        auto circle = *mHoveredCircle;
        if (circle == nullptr) {
            return {};
        }
        return {
            {},
            {
              .name = "Adjust radius...",
              .onAction =
                  [this, circle] {
                      auto radiusPopup = _new<AWindow>(
                          "", 200_dp, 50_dp, dynamic_cast<AWindow*>(AWindow::current()), WindowStyle::MODAL);
                      radiusPopup->setContents(Vertical {
                        Label { "Adjust diameter of circle at {}."_format(circle->position) },
                        _new<ASlider>() let {
                                it->setValue(circle->radius / MAX_RADIUS);
                                connect(
                                    it->valueChanging, [this, circle](aui::float_within_0_1 s) {
                                        circle->radius = s * MAX_RADIUS;
                                        mState->circles.notify();
                                    });
                            },
                      });
                      connect(radiusPopup->closed, [this, circle, oldRadius = circle->radius] {
                          if (oldRadius == circle->radius) {
                              return;
                          }
                          class ActionChangeRadius : public IAction {
                          public:
                              ActionChangeRadius(Circle* circle, float prevRadius, float newRadius)
                                : mCircle(circle), mPrevRadius(prevRadius), mNewRadius(newRadius) {}
                              ~ActionChangeRadius() override = default;
                              void undo() override {
                                  mCircle->radius = mPrevRadius;
                              }
                              void redo() override {
                                  mCircle->radius = mNewRadius;
                              }
                          private:
                              Circle* mCircle;
                              float mPrevRadius;
                              float mNewRadius;
                          };
                          mState->history.add(std::make_unique<ActionChangeRadius>(circle, oldRadius, circle->radius));
                      });
                      radiusPopup->show();
                  },
            },
        };
    }
public:
    void onPointerReleased(const APointerReleasedEvent& event) override {
        AView::onPointerReleased(event);
        if (event.asButton != AInput::LBUTTON) {
            return;
        }
        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;
        };
        mState->history.add(std::make_unique<ActionAddCircle>(mState, Circle { .position = event.position }));
    }
private:
    _<State> mState;
    AProperty<Circle*> mHoveredCircle = nullptr;
};
class CircleDrawerWindow : public AWindow {
public:
    CircleDrawerWindow() : AWindow("AUI - 7GUIs - Circle Drawer", 300_dp, 250_dp) {
        setContents(Vertical {
          Centered {
            Horizontal {
              Button { "Undo" } let {
                  connect(it->clicked, me::undo);
                  it & mState.history.nextAction.readProjected([&](UndoStack::Iterator i) { return i != mState.history.begin(); }) > &AView::setEnabled;
              },
              Button { "Redo" } let {
                connect(it->clicked, me::redo);
                it & mState.history.nextAction.readProjected([&](UndoStack::Iterator i) { return i != mState.history.end(); }) > &AView::setEnabled;
              },
            },
          },
          _new<CircleDrawArea>(aui::ptr::fake(&mState)),
        });
    }
private:
    State mState;
    void undo() {
        mState.history.undo();
    }
    void redo() {
        mState.history.redo();
    }
};
    _new<CircleDrawerWindow>()->show();
    return 0;
}
void setContents(const _< AViewContainer > &container)
Moves (like via std::move) all children and layout of the specified container to this container.
Base class of all UI objects.
Definition AView.h:78
virtual void render(ARenderContext ctx)
Draws this AView. Noone should call this function except rendering routine.
virtual void onPointerMove(glm::vec2 pos, const APointerMoveEvent &event)
Handles pointer hover events.
virtual AMenuModel composeContextMenu()
Produce context (right click) menu.
virtual void onPointerReleased(const APointerReleasedEvent &event)
Called on pointer (mouse) released event.
Represents a window in the underlying windowing system.
Definition AWindow.h:45
static AWindowBase * current()
virtual void roundedRectangleBorder(const ABrush &brush, glm::vec2 position, glm::vec2 size, float radius, int borderWidth)=0
Draws rounded rectangle's border.
virtual void roundedRectangle(const ABrush &brush, glm::vec2 position, glm::vec2 size, float radius)=0
Draws rounded rect (with antialiasing, if msaa enabled).
An std::weak_ptr with AUI extensions.
Definition SharedPtrTypes.h:179
@ HIDDEN_FROM_THIS
Like HIDDEN, but view's ASS-styled background is also affected by mask.
Definition AOverflow.h:40
static decltype(auto) connect(const Signal &signal, Object *object, Function &&function)
Connects signal to the slot of the specified object.
Definition AObject.h:86
#define let
Performs multiple operations on a single object without repeating its name (in place) This function c...
Definition kAUI.h:262
#define AUI_ENTRY
Application entry point.
Definition Entry.h:90
Button
Specifies button(s) to be displayed.
Definition AMessageBox.h:79
Pointing method press event.
Definition APointerReleasedEvent.h:19
AInput::Key asButton
pointerIndex treated as mouse button.
Definition APointerReleasedEvent.h:41
Basic easy-to-use property implementation containing T.
Definition AProperty.h:30
static _< T > fake(T *raw)
Creates fake shared pointer to T* raw with empty destructor, which does nothing. It's useful when som...
Definition SharedPtrTypes.h:429