Creating an application
About the
UFZ::Application class
The UntitledFlipperZero library uses the
UFZ::Application class to represent the current application
instance. This object manages your whole application.
It looks like this:
class Application
{
public:
explicit Application(const std::vector<UWidget*>& widgetsRef, void* userPointer, const std::function<void(Application&)>& begin, uint32_t tickPeriod = 0) noexcept;
void run(const std::vector<UWidget*>& widgetsRef, void* userPointer, const std::function<void(Application&)>& begin, uint32_t tickPeriod = 0) noexcept;
template<typename T>
T* getWidget(size_t i) noexcept;
const ViewDispatcher& getViewDispatcher() noexcept;
const SceneManager& getSceneManager() noexcept;
const Filesystem& getFilesystem() noexcept;
void* getUserPointer() noexcept;
void destroy() noexcept;
};The most important member functions are the following:
runor the non-default constructor - Initialises the application with a list of widgets, a user pointer, a begin callback function, and a tick interval that defaults to0. If the tick interval is above0a tick handler will be added.getWidget- given a widget ID, returns the widget with the specified typegetUserPointer- Returns the user pointer that was set using theinitfunctiongetViewDispatcherandgetSceneManager- Return the view dispatcher and scene manager respectively. More info heredestroy- Call this before returning from the entry point
Caution
Always call destroy before returning from the entry
point. The UFZ::Application class DOES NOT HAVE RAII!
Creating an instance of the application
To create an instance of the application, do the following:
- Call its default constructor
- Call its non-default constructor or the
initmember function - Call destroy or let the destructor handle deallocation
Example:
UFZ::Application application{};
... // Create list and popup widgets here
application.run({ &list, &popup }, nullptr);Creating widgets
All built-in widgets depend on the UFZ::UWidget abstract
class, which looks like this:
class UWidget
{
public:
UWidget(AppSceneOnEnterCallback onEnter, AppSceneOnEventCallback onEvent, AppSceneOnExitCallback onExit, const std::vector<View*>& additionalViews = {}) noexcept
: enter(onEnter), event(onEvent), exit(onExit), views(additionalViews)
{
}
void destroy();
virtual void reset() noexcept = 0;
Application* application = nullptr;
};As you can see, each widget is created using a constructor with an initialiser list, that depends on 3 callback functions for the following events:
- Enter - when the widget is opened
- Event - when the widget's main action is triggered
- Exit - when the widget is closed
It also takes a list of UFZ::View, which defaults to an
empty list. More info can be found here.
A list of built-in widget types:
Menu- Similar to the main application menu. Displays large menu items with animated iconsButtonMenu- A button menuButtonPanel- A button panelByteInput- Input using bytesDialogEx- A customisable popup dialog with 3 optionsEmptyScreen- An empty screenLoading- A loading screenPopup- A simple popupSubmenu- A minimal list of text-only itemsTextBox- A text boxTextInput- An on-screen keyboard & input box widgetVariableItemListWidget- A custom widget builder class
More info can be found in the UntitledFlipperZero source code down from the specified line.
Creating a simple "Hello, World!" popup
To create a simple popup, create an enum of scenes, so that you can easily track the ID later, when the application becomes larger:
enum Scenes
{
LANDING_POPUP
};Next, create the 3 event callbacks for the popup:
void popup_enter(void* context) noexcept
{
auto* popup = GET_WIDGET_P(context, UFZ::Popup, LANDING_POPUP); // Gets a widget instance from an ID
popup->reset(); // Reset the image so that it's already empty
popup->setContext(popup->application) // Build the widget
.setHeader("Hello, World!", 64, 4, AlignCenter, AlignTop)
.setIcon(-1, -1, nullptr) // Disable the optional icon
.setText("This text describes the popup and can be longer", 4, 16, AlignLeft, AlignTop);
RENDER_VIEW(popup->application, LANDING_POPUP); // Render the newly-created widget
}
bool popup_event(void* context, SceneManagerEvent event) noexcept
{
// No event handling for the widget's event callback
UNUSED(context); UNUSED(event);
return false;
}
void popup_exit(void* context) noexcept
{
UNUSED(context);
}Next, in your application's entry point, create an instance of the
UFZ::Popup class and add it to your application like
this:
UFZ::Application application{};
UFZ::Popup popup{ popup_enter, popup_event, popup_exit };
application.init({ &popup }, nullptr);Now run the application, and you should be able to see your popup.
Views and the view stack system
Every widget contains an instance of UFZ::View, which is
a class that allows for interacting with the currently displayed
content. It looks like this:
class View
{
public:
void setDeferredSetupCallback(const std::function<void(View&)>& f) noexcept;
[[nodiscard]] const View& setDrawCallback(ViewDrawCallback callback) const noexcept;
[[nodiscard]] const View& setInputCallback(ViewInputCallback callback) const noexcept;
[[nodiscard]] const View& setCustomCallback(ViewCustomCallback callback) const noexcept;
[[nodiscard]] const View& setPreviousCallback(ViewNavigationCallback callback) const noexcept;
[[nodiscard]] const View& setEnterCallback(ViewCallback callback) const noexcept;
[[nodiscard]] const View& setExitCallback(ViewCallback callback) const noexcept;
[[nodiscard]] const View& setUpdateCallback(ViewUpdateCallback callback) const noexcept;
[[nodiscard]] const View& setUpdateCallbackContext(void* context) const noexcept;
[[nodiscard]] const View& setContext(void* context) const noexcept;
[[nodiscard]] const View& setOrientation(ViewOrientation orientation) const noexcept;
[[nodiscard]] const View& allocateModel(ViewModelType type, size_t size) const noexcept;
[[nodiscard]] const View& freeModel() const noexcept;
[[nodiscard]] void* getModel() const noexcept;
[[nodiscard]] const View& commitModel(bool bUpdate) const noexcept;
};Views have a number of callbacks that you can set, where you can handle different types of events.
Adding additional views to a widget
As said previously, every widget has a UFZ::View
instance, however you can add additional views to the widget. This is a
side effect of an unfortunate feature of the Flipper Zero GUI service
design.
When using a default widget, you cannot use custom event handlers, without overriding the default handler. To fix this issue, internally, we use a view stack, which merges all views in the stack into 1 view that can be added to the view dispatcher.
To add another UFZ::View to a widget, do the
following:
- Create an instance of the
UFZ::Viewclass after default-initialising the application - Call the
UFZ::View::setDeferredCallbackfunction with a callback function that will add an event handler to the view - Add a pointer to the view in the
additionalViewsvariable, part of the given widget's initialiser list
Example:
UFZ::Application application{}
UFZ::View view{};
view.setDeferredSetupCallack([](UFZ::View& v) -> void {
view.setInputCallback([](InputEvent* event, void* context) -> bool {
if (event->type == InputTypePress)
FURI_LOG_I("MYAPP", "Key is pressed");
});
});
UFZ::Popup popup{ popup_enter, popup_event, popup_exit, { &view } };
application.init({ &popup }, nullptr);Navigating between scenes
The part of the application that's responsible for navigating between
scenes is the UFZ::SceneManager class. It looks like
this:
class SceneManager
{
public:
void setSceneState(uint32_t id, uint32_t state) const noexcept;
[[nodiscard]] uint32_t getSceneState(uint32_t id) const noexcept;
[[nodiscard]] bool handleCustomEvent(uint32_t event) const noexcept;
[[nodiscard]] bool handleBackEvent() const noexcept;
void handleTickEvent() const noexcept;
void nextScene(uint32_t id) const noexcept;
[[nodiscard]] bool previousScene() const noexcept;
[[nodiscard]] bool hasPreviousScene(uint32_t id) const noexcept;
[[nodiscard]] bool searchAndSwitchToPreviousScene(uint32_t id) const noexcept;
bool searchAndSwitchToPreviousSceneOneOf(const uint32_t* ids, size_t idsSize) const noexcept;
[[nodiscard]] bool searchAndSwitchToAnotherScene(uint32_t id) const noexcept;
};There are 2 ways to navigate to a scene:
- By preserving the previous scenes list - Allows you to go to the last scenes using the back button
- By wiping the previous scenes list
To go to a scene, while preserving the scenes list, use the
SceneManager::nextScene member function like this:
app->getSceneManager().nextScene(numericID);To go to a scene, while wiping the scenes list, use the
SceneManager::searchAndSwitchToAnotherScene, like this:
app->getSceneManager().searchAndSwitchToAnotherScene(numericID);Caution
Calls to the scene manager SHOULD ONLY BE DONE IN THE EVENT CALLBACK
FOR A WIDGET. To route the event from a widget's callback call(the
callback function that's called when the user interacts with the widget)
use the SEND_CUSTOM_EVENT(application, SceneID) macro.
Tip
You can use the NEXT_SCENE and
FORCE_NEXT_SCENE macros, instead of calling the
UFZ::SceneManager::nextScene and
UFZ::SceneManager::searchAndSwitchToAnotherScene functions
respectively.