mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2025-12-13 05:06:30 +04:00
@@ -64,10 +64,13 @@ static void render_callback(Canvas* const canvas, void* ctx) {
|
||||
case ViewDirectSampling:
|
||||
render_view_direct_sampling(canvas, app);
|
||||
break;
|
||||
case ViewLast:
|
||||
furi_crash(TAG " ViewLast selected");
|
||||
default:
|
||||
furi_crash(TAG "Invalid view selected");
|
||||
break;
|
||||
}
|
||||
|
||||
/* Draw the alert box if set. */
|
||||
ui_draw_alert_if_needed(canvas, app);
|
||||
}
|
||||
|
||||
/* Here all we do is putting the events into the queue that will be handled
|
||||
@@ -79,17 +82,23 @@ static void input_callback(InputEvent* input_event, void* ctx) {
|
||||
|
||||
/* Called to switch view (when left/right is pressed). Handles
|
||||
* changing the current view ID and calling the enter/exit view
|
||||
* callbacks if needed. */
|
||||
static void app_switch_view(ProtoViewApp* app, SwitchViewDirection dir) {
|
||||
* callbacks if needed.
|
||||
*
|
||||
* The 'switchto' parameter can be the identifier of a view, or the
|
||||
* special views ViewGoNext and ViewGoPrev in order to move to
|
||||
* the logical next/prev view. */
|
||||
static void app_switch_view(ProtoViewApp* app, ProtoViewCurrentView switchto) {
|
||||
ProtoViewCurrentView old = app->current_view;
|
||||
if(dir == AppNextView) {
|
||||
if(switchto == ViewGoNext) {
|
||||
app->current_view++;
|
||||
if(app->current_view == ViewLast) app->current_view = 0;
|
||||
} else if(dir == AppPrevView) {
|
||||
} else if(switchto == ViewGoPrev) {
|
||||
if(app->current_view == 0)
|
||||
app->current_view = ViewLast - 1;
|
||||
else
|
||||
app->current_view--;
|
||||
} else {
|
||||
app->current_view = switchto;
|
||||
}
|
||||
ProtoViewCurrentView new = app->current_view;
|
||||
|
||||
@@ -108,6 +117,7 @@ static void app_switch_view(ProtoViewApp* app, SwitchViewDirection dir) {
|
||||
* the main thing. */
|
||||
app->current_subview[old] = 0;
|
||||
memset(app->view_privdata, 0, PROTOVIEW_VIEW_PRIVDATA_LEN);
|
||||
ui_dismiss_alert(app);
|
||||
}
|
||||
|
||||
/* Allocate the application state and initialize a number of stuff.
|
||||
@@ -134,6 +144,7 @@ ProtoViewApp* protoview_app_alloc() {
|
||||
app->view_dispatcher = NULL;
|
||||
app->text_input = NULL;
|
||||
app->show_text_input = false;
|
||||
app->alert_dismiss_time = 0;
|
||||
app->current_view = ViewRawPulses;
|
||||
for(int j = 0; j < ViewLast; j++) app->current_subview[j] = 0;
|
||||
app->direct_sampling_enabled = false;
|
||||
@@ -266,18 +277,26 @@ int32_t protoview_app_entry(void* p) {
|
||||
/* Handle navigation here. Then handle view-specific inputs
|
||||
* in the view specific handling function. */
|
||||
if(input.type == InputTypeShort && input.key == InputKeyBack) {
|
||||
/* Exit the app. */
|
||||
if(app->current_view != ViewRawPulses) {
|
||||
/* If this is not the main app view, go there. */
|
||||
app_switch_view(app, ViewRawPulses);
|
||||
} else {
|
||||
/* If we are in the main app view, warn the user
|
||||
* they needs to long press to really quit. */
|
||||
ui_show_alert(app, "Long press to exit", 1000);
|
||||
}
|
||||
} else if(input.type == InputTypeLong && input.key == InputKeyBack) {
|
||||
app->running = 0;
|
||||
} else if(
|
||||
input.type == InputTypeShort && input.key == InputKeyRight &&
|
||||
get_current_subview(app) == 0) {
|
||||
ui_get_current_subview(app) == 0) {
|
||||
/* Go to the next view. */
|
||||
app_switch_view(app, AppNextView);
|
||||
app_switch_view(app, ViewGoNext);
|
||||
} else if(
|
||||
input.type == InputTypeShort && input.key == InputKeyLeft &&
|
||||
get_current_subview(app) == 0) {
|
||||
ui_get_current_subview(app) == 0) {
|
||||
/* Go to the previous view. */
|
||||
app_switch_view(app, AppPrevView);
|
||||
app_switch_view(app, ViewGoPrev);
|
||||
} else {
|
||||
/* This is where we pass the control to the currently
|
||||
* active view input processing. */
|
||||
@@ -295,8 +314,8 @@ int32_t protoview_app_entry(void* p) {
|
||||
case ViewDirectSampling:
|
||||
process_input_direct_sampling(app, input);
|
||||
break;
|
||||
case ViewLast:
|
||||
furi_crash(TAG " ViewLast selected");
|
||||
default:
|
||||
furi_crash(TAG "Invalid view selected");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,10 +48,12 @@ typedef enum {
|
||||
ViewModulationSettings,
|
||||
ViewDirectSampling,
|
||||
ViewLast, /* Just a sentinel to wrap around. */
|
||||
} ProtoViewCurrentView;
|
||||
|
||||
/* Used by app_switch_view() */
|
||||
typedef enum { AppNextView, AppPrevView } SwitchViewDirection;
|
||||
/* The following are special views that are not iterated, but
|
||||
* have meaning for the API. */
|
||||
ViewGoNext,
|
||||
ViewGoPrev,
|
||||
} ProtoViewCurrentView;
|
||||
|
||||
typedef struct {
|
||||
const char* name; // Name to show to the user.
|
||||
@@ -114,6 +116,7 @@ typedef struct ProtoViewMsgInfo {
|
||||
} ProtoViewMsgInfo;
|
||||
|
||||
/* Our main application context. */
|
||||
#define ALERT_MAX_LEN 32
|
||||
struct ProtoViewApp {
|
||||
/* GUI */
|
||||
Gui* gui;
|
||||
@@ -123,6 +126,8 @@ struct ProtoViewApp {
|
||||
ProtoViewCurrentView current_view; /* Active left-right view ID. */
|
||||
int current_subview[ViewLast]; /* Active up-down subview ID. */
|
||||
FuriMessageQueue* event_queue; /* Keypress events go here. */
|
||||
|
||||
/* Input text state. */
|
||||
ViewDispatcher* view_dispatcher; /* Used only when we want to show
|
||||
the text_input view for a moment.
|
||||
Otherwise it is set to null. */
|
||||
@@ -132,6 +137,12 @@ struct ProtoViewApp {
|
||||
uint32_t text_input_buffer_len;
|
||||
void (*text_input_done_callback)(void*);
|
||||
|
||||
/* Alert state. */
|
||||
uint32_t alert_dismiss_time; /* Millisecond when the alert will be
|
||||
no longer shown. Or zero if the alert
|
||||
is currently not set at all. */
|
||||
char alert_text[ALERT_MAX_LEN]; /* Alert content. */
|
||||
|
||||
/* Radio related. */
|
||||
ProtoViewTxRx* txrx; /* Radio state. */
|
||||
SubGhzSetting* setting; /* A list of valid frequencies. */
|
||||
@@ -245,9 +256,18 @@ void view_exit_direct_sampling(ProtoViewApp* app);
|
||||
void view_exit_settings(ProtoViewApp* app);
|
||||
|
||||
/* ui.c */
|
||||
int get_current_subview(ProtoViewApp* app);
|
||||
void show_available_subviews(Canvas* canvas, ProtoViewApp* app, int last_subview);
|
||||
bool process_subview_updown(ProtoViewApp* app, InputEvent input, int last_subview);
|
||||
int ui_get_current_subview(ProtoViewApp* app);
|
||||
void ui_show_available_subviews(Canvas* canvas, ProtoViewApp* app, int last_subview);
|
||||
bool ui_process_subview_updown(ProtoViewApp* app, InputEvent input, int last_subview);
|
||||
void ui_show_keyboard(
|
||||
ProtoViewApp* app,
|
||||
char* buffer,
|
||||
uint32_t buflen,
|
||||
void (*done_callback)(void*));
|
||||
void ui_dismiss_keyboard(ProtoViewApp* app);
|
||||
void ui_show_alert(ProtoViewApp* app, const char* text, uint32_t ttl);
|
||||
void ui_dismiss_alert(ProtoViewApp* app);
|
||||
void ui_draw_alert_if_needed(Canvas* canvas, ProtoViewApp* app);
|
||||
void canvas_draw_str_with_border(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
@@ -255,8 +275,6 @@ void canvas_draw_str_with_border(
|
||||
const char* str,
|
||||
Color text_color,
|
||||
Color border_color);
|
||||
void show_keyboard(ProtoViewApp* app, char* buffer, uint32_t buflen, void (*done_callback)(void*));
|
||||
void dismiss_keyboard(ProtoViewApp* app);
|
||||
|
||||
/* crc.c */
|
||||
uint8_t crc8(const uint8_t* data, size_t len, uint8_t init, uint8_t poly);
|
||||
|
||||
@@ -10,15 +10,15 @@
|
||||
|
||||
/* Return the ID of the currently selected subview, of the current
|
||||
* view. */
|
||||
int get_current_subview(ProtoViewApp* app) {
|
||||
int ui_get_current_subview(ProtoViewApp* app) {
|
||||
return app->current_subview[app->current_view];
|
||||
}
|
||||
|
||||
/* Called by view rendering callback that has subviews, to show small triangles
|
||||
* facing down/up if there are other subviews the user can access with up
|
||||
* and down. */
|
||||
void show_available_subviews(Canvas* canvas, ProtoViewApp* app, int last_subview) {
|
||||
int subview = get_current_subview(app);
|
||||
void ui_show_available_subviews(Canvas* canvas, ProtoViewApp* app, int last_subview) {
|
||||
int subview = ui_get_current_subview(app);
|
||||
if(subview != 0) canvas_draw_triangle(canvas, 120, 5, 8, 5, CanvasDirectionBottomToTop);
|
||||
if(subview != last_subview - 1)
|
||||
canvas_draw_triangle(canvas, 120, 59, 8, 5, CanvasDirectionTopToBottom);
|
||||
@@ -27,8 +27,8 @@ void show_available_subviews(Canvas* canvas, ProtoViewApp* app, int last_subview
|
||||
/* Handle up/down keys when we are in a subview. If the function catched
|
||||
* such keypress, it returns true, so that the actual view input callback
|
||||
* knows it can just return ASAP without doing anything. */
|
||||
bool process_subview_updown(ProtoViewApp* app, InputEvent input, int last_subview) {
|
||||
int subview = get_current_subview(app);
|
||||
bool ui_process_subview_updown(ProtoViewApp* app, InputEvent input, int last_subview) {
|
||||
int subview = ui_get_current_subview(app);
|
||||
if(input.type == InputTypePress) {
|
||||
if(input.key == InputKeyUp) {
|
||||
if(subview != 0) app->current_subview[app->current_view]--;
|
||||
@@ -57,17 +57,69 @@ bool process_subview_updown(ProtoViewApp* app, InputEvent input, int last_subvie
|
||||
*
|
||||
* Note: if the buffer is not a null-termined zero string, what it contains will
|
||||
* be used as initial input for the user. */
|
||||
void show_keyboard(ProtoViewApp* app, char* buffer, uint32_t buflen, void (*done_callback)(void*)) {
|
||||
void ui_show_keyboard(
|
||||
ProtoViewApp* app,
|
||||
char* buffer,
|
||||
uint32_t buflen,
|
||||
void (*done_callback)(void*)) {
|
||||
app->show_text_input = true;
|
||||
app->text_input_buffer = buffer;
|
||||
app->text_input_buffer_len = buflen;
|
||||
app->text_input_done_callback = done_callback;
|
||||
}
|
||||
|
||||
void dismiss_keyboard(ProtoViewApp* app) {
|
||||
void ui_dismiss_keyboard(ProtoViewApp* app) {
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
}
|
||||
|
||||
/* ================================= Alert ================================== */
|
||||
|
||||
/* Set an alert message to be shown over any currently active view, for
|
||||
* the specified amount of time of 'ttl' milliseconds. */
|
||||
void ui_show_alert(ProtoViewApp* app, const char* text, uint32_t ttl) {
|
||||
app->alert_dismiss_time = furi_get_tick() + furi_ms_to_ticks(ttl);
|
||||
snprintf(app->alert_text, ALERT_MAX_LEN, "%s", text);
|
||||
}
|
||||
|
||||
/* Cancel the alert before its time has elapsed. */
|
||||
void ui_dismiss_alert(ProtoViewApp* app) {
|
||||
app->alert_dismiss_time = 0;
|
||||
}
|
||||
|
||||
/* Show the alert if an alert is set. This is called after the currently
|
||||
* active view displayed its stuff, so we overwrite the screen with the
|
||||
* alert message. */
|
||||
void ui_draw_alert_if_needed(Canvas* canvas, ProtoViewApp* app) {
|
||||
if(app->alert_dismiss_time == 0) {
|
||||
/* No active alert. */
|
||||
return;
|
||||
} else if(app->alert_dismiss_time < furi_get_tick()) {
|
||||
/* Alert just expired. */
|
||||
ui_dismiss_alert(app);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Show the alert. A box with black border and a text inside. */
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
uint8_t w = canvas_string_width(canvas, app->alert_text);
|
||||
uint8_t h = 8; // Font height.
|
||||
uint8_t text_x = 64 - (w / 2);
|
||||
uint8_t text_y = 32 + 4;
|
||||
uint8_t padding = 3;
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_box(
|
||||
canvas, text_x - padding, text_y - padding - h, w + padding * 2, h + padding * 2);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(
|
||||
canvas,
|
||||
text_x - padding + 1,
|
||||
text_y - padding - h + 1,
|
||||
w + padding * 2 - 2,
|
||||
h + padding * 2 - 2);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_str(canvas, text_x, text_y, app->alert_text);
|
||||
}
|
||||
|
||||
/* =========================== Canvas extensions ============================ */
|
||||
|
||||
void canvas_draw_str_with_border(
|
||||
|
||||
@@ -103,7 +103,7 @@ void render_view_info(Canvas* const canvas, ProtoViewApp* app) {
|
||||
return;
|
||||
}
|
||||
|
||||
show_available_subviews(canvas, app, SubViewInfoLast);
|
||||
ui_show_available_subviews(canvas, app, SubViewInfoLast);
|
||||
switch(app->current_subview[app->current_view]) {
|
||||
case SubViewInfoMain:
|
||||
render_subview_main(canvas, app);
|
||||
@@ -126,7 +126,7 @@ void text_input_done_callback(void* context) {
|
||||
furi_string_free(save_path);
|
||||
|
||||
free(privdata->filename);
|
||||
dismiss_keyboard(app);
|
||||
ui_dismiss_keyboard(app);
|
||||
}
|
||||
|
||||
/* Replace all the occurrences of character c1 with c2 in the specified
|
||||
@@ -261,9 +261,12 @@ void notify_signal_sent(ProtoViewApp* app) {
|
||||
|
||||
/* Handle input for the info view. */
|
||||
void process_input_info(ProtoViewApp* app, InputEvent input) {
|
||||
if(process_subview_updown(app, input, SubViewInfoLast)) return;
|
||||
/* If we don't have a decoded signal, we don't allow to go up/down
|
||||
* in the subviews: they are only useful when a loaded signal. */
|
||||
if(app->signal_decoded && ui_process_subview_updown(app, input, SubViewInfoLast)) return;
|
||||
|
||||
InfoViewPrivData* privdata = app->view_privdata;
|
||||
int subview = get_current_subview(app);
|
||||
int subview = ui_get_current_subview(app);
|
||||
|
||||
/* Main subview. */
|
||||
if(subview == SubViewInfoMain) {
|
||||
@@ -280,7 +283,7 @@ void process_input_info(ProtoViewApp* app, InputEvent input) {
|
||||
} else if(input.type == InputTypeLong && input.key == InputKeyOk) {
|
||||
privdata->filename = malloc(SAVE_FILENAME_LEN);
|
||||
set_signal_random_filename(app, privdata->filename, SAVE_FILENAME_LEN);
|
||||
show_keyboard(app, privdata->filename, SAVE_FILENAME_LEN, text_input_done_callback);
|
||||
ui_show_keyboard(app, privdata->filename, SAVE_FILENAME_LEN, text_input_done_callback);
|
||||
} else if(input.type == InputTypeShort && input.key == InputKeyOk) {
|
||||
SendSignalCtx send_state;
|
||||
send_signal_init(&send_state, app);
|
||||
|
||||
Reference in New Issue
Block a user