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:
parent
4fd043398a
commit
780da7d4d5
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
|
*~
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
*.gdb_history
|
*.gdb_history
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
canvas_set_orientation(gui->canvas, CanvasOrientationHorizontal);
|
|
||||||
|
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagHandOrient)) {
|
||||||
|
canvas_set_orientation(gui->canvas, CanvasOrientationHorizontalFlip);
|
||||||
|
} else {
|
||||||
|
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);
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -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);
|
||||||
|
@ -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() {
|
||||||
|
@ -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;
|
||||||
|
@ -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 {
|
||||||
uint32_t sequence;
|
union {
|
||||||
|
uint32_t sequence;
|
||||||
|
struct {
|
||||||
|
uint8_t sequence_source : 2;
|
||||||
|
uint32_t sequence_counter : 30;
|
||||||
|
};
|
||||||
|
};
|
||||||
InputKey key;
|
InputKey key;
|
||||||
InputType type;
|
InputType type;
|
||||||
} InputEvent;
|
} InputEvent;
|
||||||
|
@ -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);
|
||||||
|
@ -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",
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user