New GUI/View ASCII input API

This commit is contained in:
Willy-JL
2024-01-17 22:41:28 +00:00
parent cc6805891a
commit 9d877eb59d
16 changed files with 326 additions and 1 deletions

View File

@@ -55,6 +55,16 @@ void gui_input_events_callback(const void* value, void* ctx) {
furi_thread_flags_set(gui->thread_id, GUI_THREAD_FLAG_INPUT);
}
void gui_ascii_events_callback(const void* value, void* ctx) {
furi_assert(value);
furi_assert(ctx);
Gui* gui = ctx;
furi_message_queue_put(gui->ascii_queue, value, FuriWaitForever);
furi_thread_flags_set(gui->thread_id, GUI_THREAD_FLAG_ASCII);
}
// Only Fullscreen supports vertical display for now
static bool gui_redraw_fs(Gui* gui) {
canvas_set_orientation(gui->canvas, CanvasOrientationHorizontal);
@@ -375,6 +385,35 @@ static void gui_input(Gui* gui, InputEvent* input_event) {
gui_unlock(gui);
}
static void gui_ascii(Gui* gui, AsciiEvent* ascii_event) {
furi_assert(gui);
furi_assert(ascii_event);
gui_lock(gui);
do {
if(gui->direct_draw) {
break;
}
ViewPort* view_port = NULL;
if(gui->lockdown) {
view_port = gui_view_port_find_enabled(gui->layers[GuiLayerDesktop]);
} else {
view_port = gui_view_port_find_enabled(gui->layers[GuiLayerFullscreen]);
if(!view_port) view_port = gui_view_port_find_enabled(gui->layers[GuiLayerWindow]);
if(!view_port) view_port = gui_view_port_find_enabled(gui->layers[GuiLayerDesktop]);
}
if(view_port) {
view_port_ascii(view_port, ascii_event);
}
} while(false);
gui_unlock(gui);
}
void gui_lock(Gui* gui) {
furi_assert(gui);
furi_check(furi_mutex_acquire(gui->mutex, FuriWaitForever) == FuriStatusOk);
@@ -598,9 +637,13 @@ Gui* gui_alloc() {
// Input
gui->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
gui->input_events = furi_record_open(RECORD_INPUT_EVENTS);
gui->ascii_queue = furi_message_queue_alloc(8, sizeof(AsciiEvent));
gui->ascii_events = furi_record_open(RECORD_ASCII_EVENTS);
furi_check(gui->input_events);
furi_pubsub_subscribe(gui->input_events, gui_input_events_callback, gui);
furi_check(gui->ascii_events);
furi_pubsub_subscribe(gui->ascii_events, gui_ascii_events_callback, gui);
Storage* storage = furi_record_open(RECORD_STORAGE);
gui_add_view_port(gui, storage->sd_gui.view_port, GuiLayerStatusBarLeft);
@@ -626,6 +669,14 @@ int32_t gui_srv(void* p) {
gui_input(gui, &input_event);
}
}
// Process and dispatch ascii
if(flags & GUI_THREAD_FLAG_ASCII) {
// Process till queue become empty
AsciiEvent ascii_event;
while(furi_message_queue_get(gui->ascii_queue, &ascii_event, 0) == FuriStatusOk) {
gui_ascii(gui, &ascii_event);
}
}
// Process and dispatch draw call
if(flags & GUI_THREAD_FLAG_DRAW) {
// Clear flags that arrived on input step

View File

@@ -40,7 +40,8 @@
#define GUI_THREAD_FLAG_DRAW (1 << 0)
#define GUI_THREAD_FLAG_INPUT (1 << 1)
#define GUI_THREAD_FLAG_ALL (GUI_THREAD_FLAG_DRAW | GUI_THREAD_FLAG_INPUT)
#define GUI_THREAD_FLAG_ASCII (1 << 2)
#define GUI_THREAD_FLAG_ALL (GUI_THREAD_FLAG_DRAW | GUI_THREAD_FLAG_INPUT | GUI_THREAD_FLAG_ASCII)
ARRAY_DEF(ViewPortArray, ViewPort*, M_PTR_OPLIST);
@@ -75,6 +76,9 @@ struct Gui {
ViewPort* ongoing_input_view_port;
uint16_t hide_statusbar_count;
FuriMessageQueue* ascii_queue;
FuriPubSub* ascii_events;
};
/** Find enabled ViewPort in ViewPortArray

View File

@@ -27,6 +27,11 @@ void view_set_input_callback(View* view, ViewInputCallback callback) {
view->input_callback = callback;
}
void view_set_ascii_callback(View* view, ViewAsciiCallback callback) {
furi_assert(view);
view->ascii_callback = callback;
}
void view_set_custom_callback(View* view, ViewCustomCallback callback) {
furi_assert(view);
view->custom_callback = callback;
@@ -156,6 +161,15 @@ bool view_input(View* view, InputEvent* event) {
}
}
bool view_ascii(View* view, AsciiEvent* event) {
furi_assert(view);
if(view->ascii_callback) {
return view->ascii_callback(event, view->context);
} else {
return false;
}
}
bool view_custom(View* view, uint32_t event) {
furi_assert(view);
if(view->custom_callback) {

View File

@@ -48,6 +48,14 @@ typedef void (*ViewDrawCallback)(Canvas* canvas, void* model);
*/
typedef bool (*ViewInputCallback)(InputEvent* event, void* context);
/** View Ascii callback
* @param event, pointer to ascii event data
* @param context, pointer to context
* @return true if event handled, false if event ignored
* @warning called from GUI thread
*/
typedef bool (*ViewAsciiCallback)(AsciiEvent* event, void* context);
/** View Custom callback
* @param event, number of custom event
* @param context, pointer to context
@@ -122,6 +130,13 @@ void view_set_draw_callback(View* view, ViewDrawCallback callback);
*/
void view_set_input_callback(View* view, ViewInputCallback callback);
/** Set View Ascii callback
*
* @param view View instance
* @param callback ascii callback
*/
void view_set_ascii_callback(View* view, ViewAsciiCallback callback);
/** Set View Custom callback
*
* @param view View instance

View File

@@ -10,6 +10,8 @@ ViewDispatcher* view_dispatcher_alloc() {
view_dispatcher->view_port, view_dispatcher_draw_callback, view_dispatcher);
view_port_input_callback_set(
view_dispatcher->view_port, view_dispatcher_input_callback, view_dispatcher);
view_port_ascii_callback_set(
view_dispatcher->view_port, view_dispatcher_ascii_callback, view_dispatcher);
view_port_enabled_set(view_dispatcher->view_port, false);
ViewDict_init(view_dispatcher->views);
@@ -89,6 +91,8 @@ void view_dispatcher_run(ViewDispatcher* view_dispatcher) {
break;
} else if(message.type == ViewDispatcherMessageTypeInput) {
view_dispatcher_handle_input(view_dispatcher, &message.input);
} else if(message.type == ViewDispatcherMessageTypeAscii) {
view_dispatcher_handle_ascii(view_dispatcher, &message.ascii);
} else if(message.type == ViewDispatcherMessageTypeCustomEvent) {
view_dispatcher_handle_custom_event(view_dispatcher, message.custom_event);
}
@@ -233,6 +237,24 @@ void view_dispatcher_input_callback(InputEvent* event, void* context) {
}
}
bool view_dispatcher_ascii_callback(AsciiEvent* event, void* context) {
// Due to queue we cannot know ahead of time if event is consumed
// So instead ViewDispatcher tells ViewPort that all events are consumed
// Then ViewDispatcher handles fallbacks the same way as ViewPort would have done
ViewDispatcher* view_dispatcher = context;
if(view_dispatcher->queue) {
ViewDispatcherMessage message;
message.type = ViewDispatcherMessageTypeAscii;
message.ascii = *event;
furi_check(
furi_message_queue_put(view_dispatcher->queue, &message, FuriWaitForever) ==
FuriStatusOk);
} else {
view_dispatcher_handle_ascii(view_dispatcher, event);
}
return true;
}
void view_dispatcher_handle_input(ViewDispatcher* view_dispatcher, InputEvent* event) {
// Check input complementarity
uint8_t key_bit = (1 << event->key);
@@ -290,6 +312,48 @@ void view_dispatcher_handle_input(ViewDispatcher* view_dispatcher, InputEvent* e
}
}
void view_dispatcher_handle_ascii(ViewDispatcher* view_dispatcher, AsciiEvent* event) {
// Deliver event
if(view_dispatcher->current_view) {
// Dispatch ascii to current view
bool is_consumed = view_ascii(view_dispatcher->current_view, event);
// Navigate if ascii is not consumed
if(!is_consumed) {
InputKey fallback_key = InputKeyMAX;
switch(event->value) {
case AsciiValueBS: // Backspace
case AsciiValueESC: // Escape
fallback_key = InputKeyBack;
break;
case AsciiValueDC1: // Up
case AsciiValueDC2: // Down
case AsciiValueDC3: // Right
case AsciiValueDC4: // Left
fallback_key = InputKeyUp + (event->value - AsciiValueDC1);
break;
case AsciiValueCR: // Enter
fallback_key = InputKeyOk;
break;
default:
break;
}
if(fallback_key != InputKeyMAX) {
// Fallback to directional input, needs press-short-release complementarity
InputEvent fallback_event = {
.key = fallback_key,
.type = InputTypePress,
};
view_dispatcher_handle_input(view_dispatcher, &fallback_event);
fallback_event.type = InputTypeShort;
view_dispatcher_handle_input(view_dispatcher, &fallback_event);
fallback_event.type = InputTypeRelease;
view_dispatcher_handle_input(view_dispatcher, &fallback_event);
}
}
}
}
void view_dispatcher_handle_tick_event(ViewDispatcher* view_dispatcher) {
if(view_dispatcher->tick_event_callback) {
view_dispatcher->tick_event_callback(view_dispatcher->event_context);

View File

@@ -34,6 +34,7 @@ struct ViewDispatcher {
typedef enum {
ViewDispatcherMessageTypeInput,
ViewDispatcherMessageTypeAscii,
ViewDispatcherMessageTypeCustomEvent,
ViewDispatcherMessageTypeStop,
} ViewDispatcherMessageType;
@@ -42,6 +43,7 @@ typedef struct {
ViewDispatcherMessageType type;
union {
InputEvent input;
AsciiEvent ascii;
uint32_t custom_event;
};
} ViewDispatcherMessage;
@@ -52,9 +54,15 @@ void view_dispatcher_draw_callback(Canvas* canvas, void* context);
/** ViewPort Input Callback */
void view_dispatcher_input_callback(InputEvent* event, void* context);
/** ViewPort Ascii Callback */
bool view_dispatcher_ascii_callback(AsciiEvent* event, void* context);
/** Input handler */
void view_dispatcher_handle_input(ViewDispatcher* view_dispatcher, InputEvent* event);
/** Ascii handler */
void view_dispatcher_handle_ascii(ViewDispatcher* view_dispatcher, AsciiEvent* event);
/** Tick handler */
void view_dispatcher_handle_tick_event(ViewDispatcher* view_dispatcher);

View File

@@ -19,6 +19,7 @@ struct ViewHolder {
static void view_holder_draw_callback(Canvas* canvas, void* context);
static void view_holder_input_callback(InputEvent* event, void* context);
static bool view_holder_ascii_callback(AsciiEvent* event, void* context);
ViewHolder* view_holder_alloc() {
ViewHolder* view_holder = malloc(sizeof(ViewHolder));
@@ -26,6 +27,7 @@ ViewHolder* view_holder_alloc() {
view_holder->view_port = view_port_alloc();
view_port_draw_callback_set(view_holder->view_port, view_holder_draw_callback, view_holder);
view_port_input_callback_set(view_holder->view_port, view_holder_input_callback, view_holder);
view_port_ascii_callback_set(view_holder->view_port, view_holder_ascii_callback, view_holder);
view_port_enabled_set(view_holder->view_port, false);
return view_holder;
@@ -156,3 +158,15 @@ static void view_holder_input_callback(InputEvent* event, void* context) {
}
}
}
static bool view_holder_ascii_callback(AsciiEvent* event, void* context) {
ViewHolder* view_holder = context;
bool is_consumed = false;
if(view_holder->view) {
is_consumed = view_ascii(view_holder->view, event);
}
return is_consumed;
}

View File

@@ -29,6 +29,8 @@ struct View {
void* model;
void* context;
ViewAsciiCallback ascii_callback;
};
/** IconAnimation tie callback */
@@ -43,6 +45,9 @@ void view_draw(View* view, Canvas* canvas);
/** Input Callback for View dispatcher */
bool view_input(View* view, InputEvent* event);
/** Ascii Callback for View dispatcher */
bool view_ascii(View* view, AsciiEvent* event);
/** Custom Callback for View dispatcher */
bool view_custom(View* view, uint32_t event);

View File

@@ -174,6 +174,17 @@ void view_port_input_callback_set(
furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk);
}
void view_port_ascii_callback_set(
ViewPort* view_port,
ViewPortAsciiCallback callback,
void* context) {
furi_assert(view_port);
furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk);
view_port->ascii_callback = callback;
view_port->ascii_callback_context = context;
furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk);
}
void view_port_update(ViewPort* view_port) {
furi_assert(view_port);
@@ -228,6 +239,53 @@ void view_port_input(ViewPort* view_port, InputEvent* event) {
furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk);
}
void view_port_ascii(ViewPort* view_port, AsciiEvent* event) {
furi_assert(view_port);
furi_assert(event);
furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk);
furi_check(view_port->gui);
bool is_consumed = false;
if(view_port->ascii_callback) {
is_consumed = view_port->ascii_callback(event, view_port->ascii_callback_context);
}
if(!is_consumed) {
InputKey fallback_key = InputKeyMAX;
switch(event->value) {
case AsciiValueBS: // Backspace
case AsciiValueESC: // Escape
fallback_key = InputKeyBack;
break;
case AsciiValueDC1: // Up
case AsciiValueDC2: // Down
case AsciiValueDC3: // Right
case AsciiValueDC4: // Left
fallback_key = InputKeyUp + (event->value - AsciiValueDC1);
break;
case AsciiValueCR: // Enter
fallback_key = InputKeyOk;
break;
default:
break;
}
if(fallback_key != InputKeyMAX) {
// Fallback to directional input, needs press-short-release complementarity
InputEvent fallback_event = {
.key = fallback_key,
.type = InputTypePress,
};
view_port_input(view_port, &fallback_event);
fallback_event.type = InputTypeShort;
view_port_input(view_port, &fallback_event);
fallback_event.type = InputTypeRelease;
view_port_input(view_port, &fallback_event);
}
}
furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk);
}
void view_port_set_orientation(ViewPort* view_port, ViewPortOrientation orientation) {
furi_assert(view_port);
furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk);

View File

@@ -32,6 +32,12 @@ typedef void (*ViewPortDrawCallback)(Canvas* canvas, void* context);
*/
typedef void (*ViewPortInputCallback)(InputEvent* event, void* context);
/** ViewPort Ascii callback
* @return true if event handled, false if event ignored
* @warning called from GUI thread
*/
typedef bool (*ViewPortAsciiCallback)(AsciiEvent* event, void* context);
/** ViewPort allocator
*
* always returns view_port or stops system if not enough memory.
@@ -88,6 +94,10 @@ void view_port_input_callback_set(
ViewPort* view_port,
ViewPortInputCallback callback,
void* context);
void view_port_ascii_callback_set(
ViewPort* view_port,
ViewPortAsciiCallback callback,
void* context);
/** Emit update signal to GUI system.
*

View File

@@ -22,6 +22,9 @@ struct ViewPort {
ViewPortInputCallback input_callback;
void* input_callback_context;
ViewPortAsciiCallback ascii_callback;
void* ascii_callback_context;
};
/** Set GUI reference.
@@ -50,3 +53,12 @@ void view_port_draw(ViewPort* view_port, Canvas* canvas);
* @param event pointer to input event
*/
void view_port_input(ViewPort* view_port, InputEvent* event);
/** Process ascii. Calls ascii callback.
*
* To be used by GUI, called on ascii dispatch.
*
* @param view_port ViewPort instance
* @param event pointer to ascii event
*/
void view_port_ascii(ViewPort* view_port, AsciiEvent* event);

View File

@@ -16,6 +16,7 @@ struct ViewStack {
static void view_stack_draw(Canvas* canvas, void* model);
static bool view_stack_input(InputEvent* event, void* context);
static bool view_stack_ascii(AsciiEvent* event, void* context);
static void view_stack_update_callback(View* view_top_or_bottom, void* context) {
furi_assert(view_top_or_bottom);
@@ -69,6 +70,7 @@ ViewStack* view_stack_alloc(void) {
view_allocate_model(view_stack->view, ViewModelTypeLocking, sizeof(ViewStackModel));
view_set_draw_callback(view_stack->view, view_stack_draw);
view_set_input_callback(view_stack->view, view_stack_input);
view_set_ascii_callback(view_stack->view, view_stack_ascii);
view_set_context(view_stack->view, view_stack);
view_set_enter_callback(view_stack->view, view_stack_enter);
view_set_exit_callback(view_stack->view, view_stack_exit);
@@ -121,6 +123,25 @@ static bool view_stack_input(InputEvent* event, void* context) {
return consumed;
}
static bool view_stack_ascii(AsciiEvent* event, void* context) {
furi_assert(event);
furi_assert(context);
ViewStack* view_stack = context;
bool consumed = false;
ViewStackModel* model = view_get_model(view_stack->view);
for(int i = MAX_VIEWS - 1; i >= 0; i--) {
if(model->views[i] && view_ascii(model->views[i], event)) {
consumed = true;
break;
}
}
view_commit_model(view_stack->view, false);
return consumed;
}
void view_stack_add_view(ViewStack* view_stack, View* view) {
furi_assert(view_stack);
furi_assert(view);

View File

@@ -77,6 +77,8 @@ int32_t input_srv(void* p) {
input->thread_id = furi_thread_get_current_id();
input->event_pubsub = furi_pubsub_alloc();
furi_record_create(RECORD_INPUT_EVENTS, input->event_pubsub);
input->ascii_pubsub = furi_pubsub_alloc();
furi_record_create(RECORD_ASCII_EVENTS, input->ascii_pubsub);
#if INPUT_DEBUG
furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeOutputPushPull);

View File

@@ -12,6 +12,7 @@ extern "C" {
#endif
#define RECORD_INPUT_EVENTS "input_events"
#define RECORD_ASCII_EVENTS "ascii_events"
#define INPUT_SEQUENCE_SOURCE_HARDWARE (0u)
#define INPUT_SEQUENCE_SOURCE_SOFTWARE (1u)
@@ -40,6 +41,48 @@ typedef struct {
InputType type;
} InputEvent;
typedef enum {
AsciiValueNUL = 0x00, // NULL
_AsciiValueSOH = 0x01, // Start of Heading
_AsciiValueSTX = 0x02, // Start of Text
_AsciiValueETX = 0x03, // End of Text
_AsciiValueEOT = 0x04, // End of Transmission
_AsciiValueENQ = 0x05, // Enquiry
_AsciiValueACK = 0x06, // Acknowledgement
_AsciiValueBEL = 0x07, // Bell
AsciiValueBS = 0x08, // Backspace
_AsciiValueTAB = 0x09, // Horizontal Tab
_AsciiValueLF = 0x0A, // Line Feed
_AsciiValueVT = 0x0B, // Vertical Tab
_AsciiValueFF = 0x0C, // Form Feed
AsciiValueCR = 0x0D, // Carriage Return
_AsciiValueSO = 0x0E, // Shift Out
_AsciiValueSI = 0x0F, // Shift In
_AsciiValueDLE = 0x10, // Data Link Escape
AsciiValueDC1 = 0x11, // Device Control 1
AsciiValueDC2 = 0x12, // Device Control 2
AsciiValueDC3 = 0x13, // Device Control 3
AsciiValueDC4 = 0x14, // Device Control 4
_AsciiValueNAK = 0x15, // Negative Acknowledgement
_AsciiValueSYN = 0x16, // Synchronous Idle
_AsciiValueETB = 0x17, // End of Transmission Block
_AsciiValueCAN = 0x18, // Cancel
_AsciiValueEM = 0x19, // End of Medium
_AsciiValueSUB = 0x1A, // Substitute
AsciiValueESC = 0x1B, // Escape
_AsciiValueSF = 0x1C, // File Separator
_AsciiValueGS = 0x1D, // Group Separator
_AsciiValueRS = 0x1E, // Record Separator
_AsciiValueUS = 0x1F, // Unit Separator
// Printable Ascii 0x20-0x7E
_AsciiValueDEL = 0x7F, // Delete
} AsciiValue;
/** Ascii Event, dispatches with FuriPubSub */
typedef struct {
uint8_t value;
} AsciiEvent;
/** Get human readable input key name
* @param key - InputKey
* @return string

View File

@@ -36,6 +36,8 @@ typedef struct {
InputPinState* pin_states;
Cli* cli;
volatile uint32_t counter;
FuriPubSub* ascii_pubsub;
} Input;
/** Input press timer callback */