Upside down / left handed orientation support (#2462)

* Add backup files to .gitignore
* Added lefty support in Settings > System > hand Orient: Fixes: #1015
* Left handed mode
* Fix lefthanded mode on vertical interfaces
* Input: new composite sequence identifier
* Gui: move input mapping from Canvas to ViewPort, properly handle input mapping on View switch in ViewDispatcher
* Rpc: proper input sequencing and tagging in RpcGui
* Rpc: remove magic from RpcGui

Co-authored-by: MrDaGree <5050898+MrDaGree@users.noreply.github.com>
Co-authored-by: Willy-JL <willy.leslie@icloud.com>
Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
Co-authored-by: Sergey Gavrilov <who.just.the.doctor@gmail.com>
This commit is contained in:
Michal Suchánek 2023-03-09 18:13:18 +01:00 committed by GitHub
parent 4fd043398a
commit 780da7d4d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 133 additions and 48 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
*~
*.swp *.swp
*.swo *.swo
*.gdb_history *.gdb_history

View File

@ -4,6 +4,7 @@
#include <furi.h> #include <furi.h>
#include <furi_hal.h> #include <furi_hal.h>
#include <furi_hal_rtc.h>
#include <stdint.h> #include <stdint.h>
#include <u8g2_glue.h> #include <u8g2_glue.h>
@ -376,39 +377,36 @@ void canvas_set_bitmap_mode(Canvas* canvas, bool alpha) {
void canvas_set_orientation(Canvas* canvas, CanvasOrientation orientation) { void canvas_set_orientation(Canvas* canvas, CanvasOrientation orientation) {
furi_assert(canvas); furi_assert(canvas);
const u8g2_cb_t* rotate_cb = NULL;
bool need_swap = false;
if(canvas->orientation != orientation) { if(canvas->orientation != orientation) {
switch(orientation) { switch(orientation) {
case CanvasOrientationHorizontal: case CanvasOrientationHorizontal:
if(canvas->orientation == CanvasOrientationVertical || need_swap = canvas->orientation == CanvasOrientationVertical ||
canvas->orientation == CanvasOrientationVerticalFlip) { canvas->orientation == CanvasOrientationVerticalFlip;
FURI_SWAP(canvas->width, canvas->height); rotate_cb = U8G2_R0;
}
u8g2_SetDisplayRotation(&canvas->fb, U8G2_R0);
break; break;
case CanvasOrientationHorizontalFlip: case CanvasOrientationHorizontalFlip:
if(canvas->orientation == CanvasOrientationVertical || need_swap = canvas->orientation == CanvasOrientationVertical ||
canvas->orientation == CanvasOrientationVerticalFlip) { canvas->orientation == CanvasOrientationVerticalFlip;
FURI_SWAP(canvas->width, canvas->height); rotate_cb = U8G2_R2;
}
u8g2_SetDisplayRotation(&canvas->fb, U8G2_R2);
break; break;
case CanvasOrientationVertical: case CanvasOrientationVertical:
if(canvas->orientation == CanvasOrientationHorizontal || need_swap = canvas->orientation == CanvasOrientationHorizontal ||
canvas->orientation == CanvasOrientationHorizontalFlip) { canvas->orientation == CanvasOrientationHorizontalFlip;
FURI_SWAP(canvas->width, canvas->height); rotate_cb = U8G2_R3;
};
u8g2_SetDisplayRotation(&canvas->fb, U8G2_R3);
break; break;
case CanvasOrientationVerticalFlip: case CanvasOrientationVerticalFlip:
if(canvas->orientation == CanvasOrientationHorizontal || need_swap = canvas->orientation == CanvasOrientationHorizontal ||
canvas->orientation == CanvasOrientationHorizontalFlip) { canvas->orientation == CanvasOrientationHorizontalFlip;
FURI_SWAP(canvas->width, canvas->height); rotate_cb = U8G2_R1;
}
u8g2_SetDisplayRotation(&canvas->fb, U8G2_R1);
break; break;
default: default:
furi_assert(0); furi_assert(0);
} }
if(need_swap) FURI_SWAP(canvas->width, canvas->height);
u8g2_SetDisplayRotation(&canvas->fb, rotate_cb);
canvas->orientation = orientation; canvas->orientation = orientation;
} }
} }

View File

@ -50,7 +50,13 @@ static void gui_redraw_status_bar(Gui* gui, bool need_attention) {
uint8_t left_used = 0; uint8_t left_used = 0;
uint8_t right_used = 0; uint8_t right_used = 0;
uint8_t width; uint8_t width;
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagHandOrient)) {
canvas_set_orientation(gui->canvas, CanvasOrientationHorizontalFlip);
} else {
canvas_set_orientation(gui->canvas, CanvasOrientationHorizontal); canvas_set_orientation(gui->canvas, CanvasOrientationHorizontal);
}
canvas_frame_set( canvas_frame_set(
gui->canvas, GUI_STATUS_BAR_X, GUI_STATUS_BAR_Y, GUI_DISPLAY_WIDTH, GUI_STATUS_BAR_HEIGHT); gui->canvas, GUI_STATUS_BAR_X, GUI_STATUS_BAR_Y, GUI_DISPLAY_WIDTH, GUI_STATUS_BAR_HEIGHT);

View File

@ -8,6 +8,7 @@
#include "gui.h" #include "gui.h"
#include <furi.h> #include <furi.h>
#include <furi_hal_rtc.h>
#include <m-array.h> #include <m-array.h>
#include <m-algo.h> #include <m-algo.h>
#include <stdio.h> #include <stdio.h>

View File

@ -320,6 +320,13 @@ void view_dispatcher_send_custom_event(ViewDispatcher* view_dispatcher, uint32_t
furi_message_queue_put(view_dispatcher->queue, &message, FuriWaitForever) == FuriStatusOk); furi_message_queue_put(view_dispatcher->queue, &message, FuriWaitForever) == FuriStatusOk);
} }
static const ViewPortOrientation view_dispatcher_view_port_orientation_table[] = {
[ViewOrientationVertical] = ViewPortOrientationVertical,
[ViewOrientationVerticalFlip] = ViewPortOrientationVerticalFlip,
[ViewOrientationHorizontal] = ViewPortOrientationHorizontal,
[ViewOrientationHorizontalFlip] = ViewPortOrientationHorizontalFlip,
};
void view_dispatcher_set_current_view(ViewDispatcher* view_dispatcher, View* view) { void view_dispatcher_set_current_view(ViewDispatcher* view_dispatcher, View* view) {
furi_assert(view_dispatcher); furi_assert(view_dispatcher);
// Dispatch view exit event // Dispatch view exit event
@ -330,15 +337,12 @@ void view_dispatcher_set_current_view(ViewDispatcher* view_dispatcher, View* vie
view_dispatcher->current_view = view; view_dispatcher->current_view = view;
// Dispatch view enter event // Dispatch view enter event
if(view_dispatcher->current_view) { if(view_dispatcher->current_view) {
if(view->orientation == ViewOrientationVertical) { ViewPortOrientation orientation =
view_port_set_orientation(view_dispatcher->view_port, ViewPortOrientationVertical); view_dispatcher_view_port_orientation_table[view->orientation];
} else if(view->orientation == ViewOrientationVerticalFlip) { if(view_port_get_orientation(view_dispatcher->view_port) != orientation) {
view_port_set_orientation(view_dispatcher->view_port, ViewPortOrientationVerticalFlip); view_port_set_orientation(view_dispatcher->view_port, orientation);
} else if(view->orientation == ViewOrientationHorizontal) { // we just rotated input keys, now it's time to sacrifice some input
view_port_set_orientation(view_dispatcher->view_port, ViewPortOrientationHorizontal); view_dispatcher->ongoing_input = 0;
} else if(view->orientation == ViewOrientationHorizontalFlip) {
view_port_set_orientation(
view_dispatcher->view_port, ViewPortOrientationHorizontalFlip);
} }
view_enter(view_dispatcher->current_view); view_enter(view_dispatcher->current_view);
view_port_enabled_set(view_dispatcher->view_port, true); view_port_enabled_set(view_dispatcher->view_port, true);

View File

@ -1,6 +1,8 @@
#include "view_port_i.h" #include "view_port_i.h"
#include <furi.h> #include <furi.h>
#include <furi_hal.h>
#include <furi_hal_rtc.h>
#include "gui.h" #include "gui.h"
#include "gui_i.h" #include "gui_i.h"
@ -48,27 +50,45 @@ static const InputKey view_port_input_mapping[ViewPortOrientationMAX][InputKeyMA
InputKeyBack}, //ViewPortOrientationVerticalFlip InputKeyBack}, //ViewPortOrientationVerticalFlip
}; };
static const InputKey view_port_left_hand_input_mapping[InputKeyMAX] =
{InputKeyDown, InputKeyUp, InputKeyLeft, InputKeyRight, InputKeyOk, InputKeyBack};
static const CanvasOrientation view_port_orientation_mapping[ViewPortOrientationMAX] = {
[ViewPortOrientationHorizontal] = CanvasOrientationHorizontal,
[ViewPortOrientationHorizontalFlip] = CanvasOrientationHorizontalFlip,
[ViewPortOrientationVertical] = CanvasOrientationVertical,
[ViewPortOrientationVerticalFlip] = CanvasOrientationVerticalFlip,
};
// Remaps directional pad buttons on Flipper based on ViewPort orientation // Remaps directional pad buttons on Flipper based on ViewPort orientation
static void view_port_map_input(InputEvent* event, ViewPortOrientation orientation) { static void view_port_map_input(InputEvent* event, ViewPortOrientation orientation) {
furi_assert(orientation < ViewPortOrientationMAX && event->key < InputKeyMAX); furi_assert(orientation < ViewPortOrientationMAX && event->key < InputKeyMAX);
if(event->sequence_source != INPUT_SEQUENCE_SOURCE_HARDWARE) {
return;
}
if(orientation == ViewPortOrientationHorizontal ||
orientation == ViewPortOrientationHorizontalFlip) {
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagHandOrient)) {
event->key = view_port_left_hand_input_mapping[event->key];
}
}
event->key = view_port_input_mapping[orientation][event->key]; event->key = view_port_input_mapping[orientation][event->key];
} }
static void view_port_setup_canvas_orientation(const ViewPort* view_port, Canvas* canvas) { static void view_port_setup_canvas_orientation(const ViewPort* view_port, Canvas* canvas) {
switch(view_port->orientation) { CanvasOrientation orientation = view_port_orientation_mapping[view_port->orientation];
case ViewPortOrientationHorizontalFlip:
canvas_set_orientation(canvas, CanvasOrientationHorizontalFlip); if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagHandOrient)) {
break; if(orientation == CanvasOrientationHorizontal) {
case ViewPortOrientationVertical: orientation = CanvasOrientationHorizontalFlip;
canvas_set_orientation(canvas, CanvasOrientationVertical); } else if(orientation == CanvasOrientationHorizontalFlip) {
break; orientation = CanvasOrientationHorizontal;
case ViewPortOrientationVerticalFlip: }
canvas_set_orientation(canvas, CanvasOrientationVerticalFlip); }
break;
default: canvas_set_orientation(canvas, orientation);
canvas_set_orientation(canvas, CanvasOrientationHorizontal);
break;
};
} }
ViewPort* view_port_alloc() { ViewPort* view_port_alloc() {

View File

@ -23,7 +23,8 @@ inline static void input_timer_stop(FuriTimer* timer_id) {
void input_press_timer_callback(void* arg) { void input_press_timer_callback(void* arg) {
InputPinState* input_pin = arg; InputPinState* input_pin = arg;
InputEvent event; InputEvent event;
event.sequence = input_pin->counter; event.sequence_source = INPUT_SEQUENCE_SOURCE_HARDWARE;
event.sequence_counter = input_pin->counter;
event.key = input_pin->pin->key; event.key = input_pin->pin->key;
input_pin->press_counter++; input_pin->press_counter++;
if(input_pin->press_counter == INPUT_LONG_PRESS_COUNTS) { if(input_pin->press_counter == INPUT_LONG_PRESS_COUNTS) {
@ -114,16 +115,17 @@ int32_t input_srv(void* p) {
// Common state info // Common state info
InputEvent event; InputEvent event;
event.sequence_source = INPUT_SEQUENCE_SOURCE_HARDWARE;
event.key = input->pin_states[i].pin->key; event.key = input->pin_states[i].pin->key;
// Short / Long / Repeat timer routine // Short / Long / Repeat timer routine
if(state) { if(state) {
input->counter++; input->counter++;
input->pin_states[i].counter = input->counter; input->pin_states[i].counter = input->counter;
event.sequence = input->pin_states[i].counter; event.sequence_counter = input->pin_states[i].counter;
input_timer_start(input->pin_states[i].press_timer, INPUT_PRESS_TICKS); input_timer_start(input->pin_states[i].press_timer, INPUT_PRESS_TICKS);
} else { } else {
event.sequence = input->pin_states[i].counter; event.sequence_counter = input->pin_states[i].counter;
input_timer_stop(input->pin_states[i].press_timer); input_timer_stop(input->pin_states[i].press_timer);
if(input->pin_states[i].press_counter < INPUT_LONG_PRESS_COUNTS) { if(input->pin_states[i].press_counter < INPUT_LONG_PRESS_COUNTS) {
event.type = InputTypeShort; event.type = InputTypeShort;

View File

@ -12,6 +12,8 @@ extern "C" {
#endif #endif
#define RECORD_INPUT_EVENTS "input_events" #define RECORD_INPUT_EVENTS "input_events"
#define INPUT_SEQUENCE_SOURCE_HARDWARE (0u)
#define INPUT_SEQUENCE_SOURCE_SOFTWARE (1u)
/** Input Types /** Input Types
* Some of them are physical events and some logical * Some of them are physical events and some logical
@ -27,7 +29,13 @@ typedef enum {
/** Input Event, dispatches with FuriPubSub */ /** Input Event, dispatches with FuriPubSub */
typedef struct { typedef struct {
union {
uint32_t sequence; uint32_t sequence;
struct {
uint8_t sequence_source : 2;
uint32_t sequence_counter : 30;
};
};
InputKey key; InputKey key;
InputType type; InputType type;
} InputEvent; } InputEvent;

View File

@ -12,6 +12,8 @@ typedef enum {
#define RpcGuiWorkerFlagAny (RpcGuiWorkerFlagTransmit | RpcGuiWorkerFlagExit) #define RpcGuiWorkerFlagAny (RpcGuiWorkerFlagTransmit | RpcGuiWorkerFlagExit)
#define RPC_GUI_INPUT_RESET (0u)
typedef struct { typedef struct {
RpcSession* session; RpcSession* session;
Gui* gui; Gui* gui;
@ -26,6 +28,9 @@ typedef struct {
bool virtual_display_not_empty; bool virtual_display_not_empty;
bool is_streaming; bool is_streaming;
uint32_t input_key_counter[InputKeyMAX];
uint32_t input_counter;
} RpcGuiSystem; } RpcGuiSystem;
static void static void
@ -194,6 +199,22 @@ static void
return; return;
} }
// Event sequence shenanigans
event.sequence_source = INPUT_SEQUENCE_SOURCE_SOFTWARE;
if(event.type == InputTypePress) {
rpc_gui->input_counter++;
if(rpc_gui->input_counter == RPC_GUI_INPUT_RESET) rpc_gui->input_counter++;
rpc_gui->input_key_counter[event.key] = rpc_gui->input_counter;
}
if(rpc_gui->input_key_counter[event.key] == RPC_GUI_INPUT_RESET) {
FURI_LOG_W(TAG, "Out of sequence input event: key %d, type %d,", event.key, event.type);
}
event.sequence_counter = rpc_gui->input_key_counter[event.key];
if(event.type == InputTypeRelease) {
rpc_gui->input_key_counter[event.key] = RPC_GUI_INPUT_RESET;
}
// Submit event
FuriPubSub* input_events = furi_record_open(RECORD_INPUT_EVENTS); FuriPubSub* input_events = furi_record_open(RECORD_INPUT_EVENTS);
furi_check(input_events); furi_check(input_events);
furi_pubsub_publish(input_events, &event); furi_pubsub_publish(input_events, &event);

View File

@ -124,6 +124,23 @@ static void date_format_changed(VariableItem* item) {
locale_set_date_format(date_format_value[index]); locale_set_date_format(date_format_value[index]);
} }
const char* const hand_mode[] = {
"Righty",
"Lefty",
};
static void hand_orient_changed(VariableItem* item) {
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, hand_mode[index]);
if(index) {
furi_hal_rtc_set_flag(FuriHalRtcFlagHandOrient);
} else {
furi_hal_rtc_reset_flag(FuriHalRtcFlagHandOrient);
}
loader_update_menu();
}
static uint32_t system_settings_exit(void* context) { static uint32_t system_settings_exit(void* context) {
UNUSED(context); UNUSED(context);
return VIEW_NONE; return VIEW_NONE;
@ -145,6 +162,12 @@ SystemSettings* system_settings_alloc() {
uint8_t value_index; uint8_t value_index;
app->var_item_list = variable_item_list_alloc(); app->var_item_list = variable_item_list_alloc();
item = variable_item_list_add(
app->var_item_list, "Hand Orient", COUNT_OF(hand_mode), hand_orient_changed, app);
value_index = furi_hal_rtc_is_flag_set(FuriHalRtcFlagHandOrient) ? 1 : 0;
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, hand_mode[value_index]);
item = variable_item_list_add( item = variable_item_list_add(
app->var_item_list, app->var_item_list,
"Units", "Units",

View File

@ -29,6 +29,7 @@ typedef enum {
FuriHalRtcFlagFactoryReset = (1 << 1), FuriHalRtcFlagFactoryReset = (1 << 1),
FuriHalRtcFlagLock = (1 << 2), FuriHalRtcFlagLock = (1 << 2),
FuriHalRtcFlagC2Update = (1 << 3), FuriHalRtcFlagC2Update = (1 << 3),
FuriHalRtcFlagHandOrient = (1 << 4),
} FuriHalRtcFlag; } FuriHalRtcFlag;
typedef enum { typedef enum {