Skip to content

User Interface

Derek Jamison edited this page Apr 30, 2023 · 91 revisions

The Flipper Zero user interface is composed of a LCD monochrome display (128x64) and a directional pad (Up/Down/Left/Right/Ok) with Back button. There are lots of other ways for the Flipper Zero to get input and provide output (GPIO, Sub 1-GHz, IR, USB-C, Micro SD, RFID NFC, RFID 125KHz, iButton, vibration, LED, timers, etc.) but those will all be covered in different sections of this wiki.

For this section of the wiki, User Interface, is focused on rendering and d-pad input.

ViewPort

The most basic UI application uses a ViewPort object. In this case, the GUI will invoke your callback for Draw and Input. Orientation is handled for you, mapping the screen canvas and directional pad. Your code can enable/disable rendering.

An application using the ViewPort typically registers the callbacks on application start and uses the draw/input callbacks for the lifetime of the application. Currently official firmware allows view_port_draw_callback_set and view_port_input_callback_set to be invoked multiple times (and subsequent calls will replace the current callbacks) so it is possible to have your input handler switch the draw_callback/input_callback methods to render very different experiences. Most applications seem to have the input_callback/draw_callback switch based on application state and call different helper methods rather than swapping the callbacks (or they use a ViewDispatcher instead of the ViewPort).

Key concepts

Import the gui header file, which will import view_port, canvas. NOTE: This will also end up importing input/input.h, furi_hal_resources.h, furi.h and a few other headers.

#include <gui/gui.h>

Define a callback function to get invoked for drawing. You can name the function anything. In this example, we use "my_draw_callback". Typically, your callback will render on the Canvas.

static void my_draw_callback(Canvas* canvas, void* context) {
  // TODO: If we need the context, cast the context to the correct type.
  // TODO: Render something on the canvas.
}

Define a callback function to get invoked for input. You can name the function anything. In this example, we use "my_input_callback". Typically, your callback will _put messages in the MessageQueue to be processed by your application.

static void my_input_callback(InputEvent* input_event, void* context) {
  // TODO: queue an input event 
}

Allocate the ViewPort using the view_port_alloc method.

ViewPort* view_port = view_port_alloc();

Register our draw callback.

view_port_draw_callback_set(view_port, my_draw_callback, my_context);

Register our input callback.

view_port_input_callback_set(view_port, my_input_callback, my_context);

Get a reference to the Gui and add the viewport.

Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);

When your application is done, you should disable the viewport and free the resources you allocated.

view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
furi_record_close(RECORD_GUI);
view_port_free(view_port);

Sample

TODO

  • TODO: RESEARCH: There is a get/set width/height. applications\services\gui\gui.c seems to default width to 8 and doesn't seem to use height? Also, seems to be used only for viewports the status bar?
  • TODO: RESEARCH: I have only used GuiLayerFullscreen (passing to gui_add_view_port). Need to try GuiLayerWindow, GuiLayerStatusBarLeft, GuiLayerStatusBarRight.
  • TODO: RESEARCH: I have only used default ViewPort orientation. Need to try ViewPortOrientationHorizontalFlip, ViewPortOrientationVertical, and ViewPortOrientationVerticalFlip.

Canvas

The Canvas is an object for drawing on the LCD monochrome display (128x64). Depending on the orientation, the display may be a 64x128. Methods are exposed to get the canvas_width and canvas_height of a canvas. Typically, your drawing callback is invoked with a Canvas object that you can use to draw on.

Key concepts

Clear the canvas buffer, so the display memory is in the reset state.

canvas_clear(canvas)

Set the color used for drawing. The choices are ColorWhite, ColorBlack and ColorXOR. If you use XOR then drawing will invert the existing pixels state (Black->White, White->Black).

canvas_set_color(canvas, ColorWhite);

Set the font used for drawing strings. The choices are FontPrimary, FontSecondary, FontKeyboard and FontBigNumbers.

canvas_set_font(canvas, FontPrimary);

Draw a string using the current font (Left/bottom aligned). NOTE: The message is a cstr (e.g. a pointer to a null-terminated array of char). If you have a FuriString, you can call furi_string_get_cstr(furiString) to get the cstr.

uint8_t x = 30;
uint8_t y = 10;
char* message = "hello";
canvas_draw_str(canvas, x, y, message);

Draw an aligned string using the current font. NOTE: The message is a cstr (e.g. a pointer to a null-terminated array of char). If you have a FuriString, you can call furi_string_get_cstr(furiString) to get the cstr. Horizontal values are AlignLeft, AlignRight & AlignCenter. Vertical values are AlignBottom, AlignTop & AlignCenter.

uint8_t x = 30;
uint8_t y = 10;
char* message = "hello";
canvas_draw_str(canvas, x, y, AlignLeft, AlignBottom, message);

Draw a pixel using the current color.

uint8_t x = 50;
uint8_t y = 30;
canvas_draw_dot(canvas, x, y);

Draw a box or frame of the specified width and height.

uint8_t x = 50;
uint8_t y = 30;
uint8_t w = 20;
uint8_t h = 5;
canvas_draw_box(canvas, x, y, w, h);
canvas_draw_frame(canvas, x, y, w, h);

Draw a rounded corner box or frame of the specified width and height.

uint8_t x = 50;
uint8_t y = 30;
uint8_t w = 20;
uint8_t h = 5;
uint8_t r = 2;
canvas_draw_rbox(canvas, x, y, w, h, r);
canvas_draw_rframe(canvas, x, y, w, h, r);

Draws a line from (x1,y1) to (x2,y2).

uint8_t x1 = 10;
uint8_t y1 = 20;
uint8_t x2 = 30;
uint8_t y2 = 25;
canvas_draw_line(canvas, x1, y1, x2, y2);

Draws a circle at (x,y) with radius r.

uint8_t x = 63;
uint8_t y = 20;
uint8_t r = 10;
canvas_draw_circle(canvas, x, y, r);

Draws an icon. In this example, the file MyImage_96x59.png is in the folder specified in the fap_icon_assets property of the application.fam file.

uint8_t x = 63;
uint8_t y = 20;
Icon* icon = &I_MyImage_96x59;
canvas_draw_icon(canvas, x, y, icon);

ViewDispatcher

The view dispatcher is used for applications that want multiple View objects. The application can easily switch between views. Views are registered with an id, and then the dispatcher can switch between the views. If the back button is pressed and the view does not return true from the input callback, then the view's navigation callback is used to determine the view id to switch to.

Key concepts

Import the view_dispatcher header file, which will import view, gui, and scene_manager. NOTE: the gui header file, will import view_port, canvas, which will also end up importing input/input.h, furi_hal_resources.h, furi.h and a few other headers.

#include <gui/view_dispatcher.h>

Allocate a ViewDispatcher and enable its queue.

ViewDispatcher* view_dispatcher = view_dispatcher_alloc();
view_dispatcher_enable_queue(view_dispatcher);

Create an enum of view identifiers.

typedef enum {
  MyDemoViewId,
  MyOtherDemoViewId,
} MyViewIds;

Add the View to your view dispatcher using the custom id. See the View section for more information on how to create a populated View object.

// View* view = replace_with_function_to_get_a_view_object(); // Get a populated View* object.
view_dispatcher_add_view(view_dispatcher, MyDemoViewId, view);

// view = replace_with_function_to_get_another_view_object(); // Get a populated View* object.
view_dispatcher_add_view(view_dispatcher, MyOtherDemoViewId, view);

Attach the view dispatcher to the GUI.

Gui* gui = furi_record_open(RECORD_GUI);
view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen);

Switch to one of the registered views and start running the dispatcher.

view_dispatcher_switch_to_view(view_dispatcher, MyDemoViewId);
view_dispatcher_run(view_dispatcher);

In your callback code: switch to a different view when something interesting happens.

view_dispatcher_switch_to_view(view_dispatcher, MyOtherDemoViewId);

In your callback code: stop view needed.

view_dispatcher_stop(view_dispatcher);

When your application is done, you should disable the free the resources you allocated.

view_dispatcher_remove_view(view_dispatcher, MyDemoViewId);
view_free(view);
view_dispatcher_free(view_dispatcher);
furi_record_close(RECORD_GUI);

Advanced Topics

Custom events

view_dispatcher_send_custom_event can be used to send an event_id to the registered custom_event_callback method. The view_dispatcher_send_custom_event will queue the event using the ViewDispatcher's queue. The event will first be passed to the View's custom_callback handler, but it that returns false then the ViewDispatcher custom_event_callback will be invoked.

bool my_view_dispatcher_custom_event_callback(void* context, uint32_t event) {
  // NOTE: The return value is not currently used by the ViewDispatcher,
  // however I recommend returning true if you handled the event and false
  // if it is still unhandled, since the API may change in the future.
  return true; 
}
view_dispatcher_set_custom_event_callback(view_dispatcher, my_view_dispatcher_custom_event_callback);

uint32_t event_id = 42; // Send a custom event of 42 to the custom_event_callback.
view_dispatcher_send_custom_event(view_dispatcher, event_id);

Navigation events

If the View's previous_callback is not set, or returns VIEW_IGNORE, then the navigation_event_callback will be invoked. If navigation_event_callback returns false, then the view_dispatcher_stop will be invoked.

bool my_view_dispatcher_navigation_event_callback(void* context) {
  // Return true if you handled the event, or if you want to ignore the event.
  // Only return false if you want the ViewDispatcher to stop.
  return true; 
}
view_dispatcher_set_navigation_event_callback(view_dispatcher, my_view_dispatcher_navigation_event_callback);

Tick events

The Tick event callback will get invoked whenever the ViewDispatcher did not have any events in its queue for the tick_period.

void my_view_dispatcher_tick_event_callback(void* context) {
}
uint32_t tick_period = 1000; // See TODO section below.
view_dispatcher_set_tick_event_callback(view_dispatcher, my_view_dispatcher_tick_event_callback, tick_period);

Sample

  • See TODO section below.

TODO

  • How long is a tick?
  • Create sample that only uses ViewDispatcher.

View

A View is the objects that are added to the ViewDispatcher and referenced later in the ViewDispatcher by id. A View is similar to a ViewPort, in that callbacks can be set for draw and input (view_set_draw_callback and view_set_input_callback), but there are a lot more callbacks that can also be registered with a View. There are many Gui modules for various tasks, like input, file dialogs, menus, etc. and each of them have a View associated with the module (typically module_name_get_view(module) is the function that returns the View).

In a View, a custom callback can be set which is invoked by the view_dispatcher_send_custom_event method. A previous callback can be set, which gets invoked when the Back button is pressed. If the previous callback returns a view id (that was previously registered with the ViewDispatcher) then the navigation is changed to view associated with that id. There are also enter/exit callbacks that can be registered to know when a View is switched to/or away from.

There are many pre-built modules, which expose a configured View object. You can use the View* that is returned from the Modules _get_view(...) function.

Key concepts

Import the view header file, which will import canvas, input/input.h, and a few other headers.

#include <gui/view.h>

Define a callback function to get invoked for drawing. You can name the function anything. In this example, we use "my_draw_callback". Typically, your callback will render on the Canvas.

static void my_draw_callback(Canvas* canvas, void* model) {
  // TODO: If we need the model, cast the model to the correct type.
  // TODO: Render something on the canvas.
}

Define a callback function to get invoked for input. You can name the function anything. In this example, we use "my_input_callback". Typically, your callback will _put messages in the MessageQueue to be processed by your application.

static void my_input_callback(InputEvent* input_event, void* context) {
  // TODO: queue an input event 
}

Allocate the View using the view_alloc method.

View* view = view_alloc();

Create a model structure to hold your View's data.

typedef struct MyModel {
  FuriString* buffer;
  uint32_t counter;  
} MyModel;

Allocate a model. If the model is all atomic types and partial update is okay use ViewModelTypeLockFree, otherwise use ViewModelTypeLocking to have the model guarded by a mutex. The model is a void*, so you need to specify the sizeof your model struct.

view_allocate_model(view, ViewModelTypeLockFree, sizeof(MyModel));

Register our draw callback.

// The callback will be invoked with the model, if it was allocated.
view_set_draw_callback(view_port, my_draw_callback);

Register our input callback and context.

// Set context to whatever context you want when input callback gets invoked.
void* context = NULL; 
view_set_context(view_port, context);
view_set_input_callback(view_port, my_input_callback);

When your application is done, you should free the resources you allocated.

view_free(view);

Advanced Topics

Previous callback

The navigation callback method will get invoked when the Back button is pressed. This method should return the view id that matches one of the identifiers registered in the ViewDispatcher. You can use VIEW_NONE to hide the view_port and VIEW_IGNORE to ignore the request. The context passed to the callback is the object specified in view_set_context(view, context);

Create the callback method that will get invoked when the Back button is pressed.

uint32_t my_view_navigation_callback(void* context) {
  return MyDemoViewId; // Return the view id that is registered in the ViewDispatcher.
}

Register the callback method.

view_set_previous_callback(view, my_view_navigation_callback);

Enter callback

The enter callback method will get invoked when the view_dispatcher_set_current_view is called and the view is switched to.

Create the callback method that will get invoked when the view is switched.

void my_enter_view_callback(void* context) {
}

Register the callback method.

view_set_enter_callback(view, my_enter_view_callback)

Exit callback

The exit callback method will get invoked on the previous View when the view_dispatcher_set_current_view is called.

Create the callback method that will get invoked when the view is switched.

void my_exit_view_callback(void* context) {
}

Register the callback method.

view_set_exit_callback(view, my_exit_view_callback)

With view model

Update callback w/context

Custom callback

Set orientation

Get model

Commit model

TODO

  • Write about all of the advanced topics.

Modules

Modules are found in the applications/services/gui/modules folder. For a list of all of the modules available in applications/services/gui, please see A Visual Guide to Flipper Zero GUI Components over on brodan.biz/blog.

Key concepts

Modules typically expose three core methods:

  • Allocate the module.
Modulename* module = modulename_alloc();
  • Get View* associated with the module.
View* view = modulename_get_view(module);
  • Free the module when our program exits.
modulename_free(module);

For example, the Loading module:

Loading* module = loading_alloc();
View* view = loading_get_view(module);
// Free the module when our program exits.
loading_free(module);

Many modules also expose additional methods to configure the module and to set additional callbacks needed by the module (like result_callback, validator_callback, etc.)

TODO

  • Create a Modules page, with details about each module.

SceneManager

Clone this wiki locally