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>
* getWidget(size_t i) noexcept;
T
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:
run
or 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 above0
a 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 theinit
functiongetViewDispatcher
andgetSceneManager
- 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
init
member function - Call destroy or let the destructor handle deallocation
Example:
::Application application{};
UFZ... // Create list and popup widgets here
.run({ &list, &popup }, nullptr); application
Creating widgets
All built-in widgets depend on the UFZ::UWidget
abstract
class, which looks like this:
class UWidget
{
public:
(AppSceneOnEnterCallback onEnter, AppSceneOnEventCallback onEvent, AppSceneOnExitCallback onExit, const std::vector<View*>& additionalViews = {}) noexcept
UWidget: enter(onEnter), event(onEvent), exit(onExit), views(additionalViews)
{
}
void destroy();
virtual void reset() noexcept = 0;
* application = nullptr;
Application};
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 widgetVariableItemList
Widget
- 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
->reset(); // Reset the image so that it's already empty
popup->setContext(popup->application) // Build the widget
popup.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);
(popup->application, LANDING_POPUP); // Render the newly-created widget
RENDER_VIEW}
bool popup_event(void* context, SceneManagerEvent event) noexcept
{
// No event handling for the widget's event callback
(context); UNUSED(event);
UNUSEDreturn false;
}
void popup_exit(void* context) noexcept
{
(context);
UNUSED}
Next, in your application's entry point, create an instance of the
UFZ::Popup
class and add it to your application like
this:
::Application application{};
UFZ::Popup popup{ popup_enter, popup_event, popup_exit };
UFZ.init({ &popup }, nullptr); application
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::View
class after default-initialising the application - Call the
UFZ::View::setDeferredCallback
function with a callback function that will add an event handler to the view - Add a pointer to the view in the
additionalViews
variable, part of the given widget's initialiser list
Example:
::Application application{}
UFZ
::View view{};
UFZ.setDeferredSetupCallack([](UFZ::View& v) -> void {
view.setInputCallback([](InputEvent* event, void* context) -> bool {
viewif (event->type == InputTypePress)
("MYAPP", "Key is pressed");
FURI_LOG_I});
});
::Popup popup{ popup_enter, popup_event, popup_exit, { &view } };
UFZ.init({ &popup }, nullptr); application
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:
->getSceneManager().nextScene(numericID); app
To go to a scene, while wiping the scenes list, use the
SceneManager::searchAndSwitchToAnotherScene
, like this:
->getSceneManager().searchAndSwitchToAnotherScene(numericID); app
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.