[FL-2119] BT HID App (#888)
* view_dispatcher: add default back processing for Long events * assets: add ble connected and disconnected assets * bt keyboard: introduce new application * bt keyboard: add logic to keyboard mode * bt: remove debug ble hid application * bt hid: introduce media controller * gui canvas: rename CanvasFontDirection -> CanvasDirection * gui canvas: add arrow element * assets: update finilized assets * bt hid: finalise keynote GUI * bt hid: finalise media player GUI * bt: add media key buttons support * bt: add exit confirm view * bt: change Clicker -> Remote * bt: support f6 target * bt: hopefully final bt hid design * bt hid: add blue led notification when device is connected * bt: leave only bt clicker for now * bt: add display notification on pin code show event
This commit is contained in:
@@ -40,7 +40,7 @@ extern int32_t subghz_app(void* p);
|
||||
extern int32_t usb_mouse_app(void* p);
|
||||
extern int32_t usb_test_app(void* p);
|
||||
extern int32_t vibro_test_app(void* p);
|
||||
extern int32_t ble_keyboard_app(void* p);
|
||||
extern int32_t bt_hid_app(void* p);
|
||||
|
||||
// Plugins
|
||||
extern int32_t music_player_app(void* p);
|
||||
@@ -206,6 +206,9 @@ const size_t FLIPPER_ON_SYSTEM_START_COUNT =
|
||||
|
||||
// Plugin menu
|
||||
const FlipperApplication FLIPPER_PLUGINS[] = {
|
||||
#ifdef APP_BLE_HID
|
||||
{.app = bt_hid_app, .name = "Bluetooth remote", .stack_size = 1024, .icon = NULL},
|
||||
#endif
|
||||
|
||||
#ifdef APP_MUSIC_PLAYER
|
||||
{.app = music_player_app, .name = "Music Player", .stack_size = 1024, .icon = &A_Plugins_14},
|
||||
@@ -220,10 +223,6 @@ const size_t FLIPPER_PLUGINS_COUNT = sizeof(FLIPPER_PLUGINS) / sizeof(FlipperApp
|
||||
|
||||
// Plugin menu
|
||||
const FlipperApplication FLIPPER_DEBUG_APPS[] = {
|
||||
#ifdef APP_BLE_KEYBOARD
|
||||
{.app = ble_keyboard_app, .name = "BLE keyboard demo", .stack_size = 1024, .icon = NULL},
|
||||
#endif
|
||||
|
||||
#ifdef APP_BLINK
|
||||
{.app = blink_test_app, .name = "Blink Test", .stack_size = 1024, .icon = NULL},
|
||||
#endif
|
||||
|
@@ -47,7 +47,7 @@ APP_SD_TEST = 1
|
||||
APP_VIBRO_TEST = 1
|
||||
APP_USB_TEST = 1
|
||||
APP_DISPLAY_TEST = 1
|
||||
APP_BLE_KEYBOARD = 1
|
||||
APP_BLE_HID = 1
|
||||
APP_USB_MOUSE = 1
|
||||
APP_BAD_USB = 1
|
||||
APP_UART_ECHO = 1
|
||||
@@ -167,9 +167,9 @@ CFLAGS += -DAPP_BAD_USB
|
||||
SRV_GUI = 1
|
||||
endif
|
||||
|
||||
APP_BLE_KEYBOARD ?=0
|
||||
ifeq ($(APP_BLE_KEYBOARD), 1)
|
||||
CFLAGS += -DAPP_BLE_KEYBOARD
|
||||
APP_BLE_HID ?=0
|
||||
ifeq ($(APP_BLE_HID), 1)
|
||||
CFLAGS += -DAPP_BLE_HID
|
||||
SRV_GUI = 1
|
||||
endif
|
||||
|
||||
|
169
applications/bt/bt_hid_app/bt_hid.c
Executable file
169
applications/bt/bt_hid_app/bt_hid.c
Executable file
@@ -0,0 +1,169 @@
|
||||
#include "bt_hid.h"
|
||||
#include <furi-hal-bt.h>
|
||||
#include <applications/notification/notification-messages.h>
|
||||
|
||||
#define TAG "BtHidApp"
|
||||
|
||||
enum BtDebugSubmenuIndex {
|
||||
BtHidSubmenuIndexKeynote,
|
||||
BtHidSubmenuIndexMedia,
|
||||
};
|
||||
|
||||
void bt_hid_submenu_callback(void* context, uint32_t index) {
|
||||
furi_assert(context);
|
||||
BtHid* app = context;
|
||||
if(index == BtHidSubmenuIndexKeynote) {
|
||||
app->view_id = BtHidViewKeynote;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeynote);
|
||||
} else if(index == BtHidSubmenuIndexMedia) {
|
||||
app->view_id = BtHidViewMedia;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewMedia);
|
||||
}
|
||||
}
|
||||
|
||||
void bt_hid_dialog_callback(DialogExResult result, void* context) {
|
||||
furi_assert(context);
|
||||
BtHid* app = context;
|
||||
if(result == DialogExResultLeft) {
|
||||
// TODO switch to Submenu after Media is done
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
} else if(result == DialogExResultRight) {
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t bt_hid_exit_confirm_view(void* context) {
|
||||
return BtHidViewExitConfirm;
|
||||
}
|
||||
|
||||
uint32_t bt_hid_exit(void* context) {
|
||||
return VIEW_NONE;
|
||||
}
|
||||
|
||||
void bt_hid_connection_status_changed_callback(BtStatus status, void* context) {
|
||||
furi_assert(context);
|
||||
BtHid* bt_hid = context;
|
||||
bool connected = (status == BtStatusConnected);
|
||||
if(connected) {
|
||||
notification_internal_message(bt_hid->notifications, &sequence_set_blue_255);
|
||||
} else {
|
||||
notification_internal_message(bt_hid->notifications, &sequence_reset_blue);
|
||||
}
|
||||
bt_hid_keynote_set_connected_status(bt_hid->bt_hid_keynote, connected);
|
||||
bt_hid_media_set_connected_status(bt_hid->bt_hid_media, connected);
|
||||
}
|
||||
|
||||
BtHid* bt_hid_app_alloc() {
|
||||
BtHid* app = furi_alloc(sizeof(BtHid));
|
||||
|
||||
// Load Bluetooth settings
|
||||
bt_settings_load(&app->bt_settings);
|
||||
|
||||
// Gui
|
||||
app->gui = furi_record_open("gui");
|
||||
|
||||
// Bt
|
||||
app->bt = furi_record_open("bt");
|
||||
|
||||
// Notifications
|
||||
app->notifications = furi_record_open("notification");
|
||||
|
||||
// View dispatcher
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
// Submenu view
|
||||
app->submenu = submenu_alloc();
|
||||
submenu_add_item(
|
||||
app->submenu, "Keynote", BtHidSubmenuIndexKeynote, bt_hid_submenu_callback, app);
|
||||
submenu_add_item(
|
||||
app->submenu, "Media player", BtHidSubmenuIndexMedia, bt_hid_submenu_callback, app);
|
||||
view_set_previous_callback(submenu_get_view(app->submenu), bt_hid_exit);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, BtHidViewSubmenu, submenu_get_view(app->submenu));
|
||||
|
||||
// Dialog view
|
||||
app->dialog = dialog_ex_alloc();
|
||||
dialog_ex_set_result_callback(app->dialog, bt_hid_dialog_callback);
|
||||
dialog_ex_set_context(app->dialog, app);
|
||||
dialog_ex_set_left_button_text(app->dialog, "Exit");
|
||||
dialog_ex_set_right_button_text(app->dialog, "Stay");
|
||||
dialog_ex_set_header(app->dialog, "Close current app?", 16, 12, AlignLeft, AlignTop);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, BtHidViewExitConfirm, dialog_ex_get_view(app->dialog));
|
||||
|
||||
// Keynote view
|
||||
app->bt_hid_keynote = bt_hid_keynote_alloc();
|
||||
view_set_previous_callback(
|
||||
bt_hid_keynote_get_view(app->bt_hid_keynote), bt_hid_exit_confirm_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, BtHidViewKeynote, bt_hid_keynote_get_view(app->bt_hid_keynote));
|
||||
|
||||
// Media view
|
||||
app->bt_hid_media = bt_hid_media_alloc();
|
||||
view_set_previous_callback(bt_hid_media_get_view(app->bt_hid_media), bt_hid_exit_confirm_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, BtHidViewMedia, bt_hid_media_get_view(app->bt_hid_media));
|
||||
|
||||
// TODO switch to menu after Media is done
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeynote);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
void bt_hid_app_free(BtHid* app) {
|
||||
furi_assert(app);
|
||||
|
||||
// Reset notification
|
||||
notification_internal_message(app->notifications, &sequence_reset_blue);
|
||||
|
||||
// Free views
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewSubmenu);
|
||||
submenu_free(app->submenu);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewExitConfirm);
|
||||
dialog_ex_free(app->dialog);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewKeynote);
|
||||
bt_hid_keynote_free(app->bt_hid_keynote);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewMedia);
|
||||
bt_hid_media_free(app->bt_hid_media);
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
|
||||
// Close records
|
||||
furi_record_close("gui");
|
||||
app->gui = NULL;
|
||||
furi_record_close("notification");
|
||||
app->notifications = NULL;
|
||||
furi_record_close("bt");
|
||||
app->bt = NULL;
|
||||
|
||||
// Free rest
|
||||
free(app);
|
||||
}
|
||||
|
||||
int32_t bt_hid_app(void* p) {
|
||||
// Switch profile to Hid
|
||||
BtHid* app = bt_hid_app_alloc();
|
||||
bt_set_status_changed_callback(app->bt, bt_hid_connection_status_changed_callback, app);
|
||||
// Change profile
|
||||
if(!bt_set_profile(app->bt, BtProfileHidKeyboard)) {
|
||||
FURI_LOG_E(TAG, "Failed to switch profile");
|
||||
bt_hid_app_free(app);
|
||||
return -1;
|
||||
}
|
||||
furi_hal_bt_start_advertising();
|
||||
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
bt_set_status_changed_callback(app->bt, NULL, NULL);
|
||||
// Stop advertising if bt was off
|
||||
if(app->bt_settings.enabled) {
|
||||
furi_hal_bt_stop_advertising();
|
||||
}
|
||||
// Change back profile to Serial
|
||||
bt_set_profile(app->bt, BtProfileSerial);
|
||||
|
||||
bt_hid_app_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
34
applications/bt/bt_hid_app/bt_hid.h
Normal file
34
applications/bt/bt_hid_app/bt_hid.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <bt/bt_service/bt.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <applications/notification/notification.h>
|
||||
#include <applications/bt/bt_settings.h>
|
||||
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
#include "views/bt_hid_keynote.h"
|
||||
#include "views/bt_hid_media.h"
|
||||
|
||||
typedef struct {
|
||||
BtSettings bt_settings;
|
||||
Bt* bt;
|
||||
Gui* gui;
|
||||
NotificationApp* notifications;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Submenu* submenu;
|
||||
DialogEx* dialog;
|
||||
BtHidKeynote* bt_hid_keynote;
|
||||
BtHidMedia* bt_hid_media;
|
||||
uint32_t view_id;
|
||||
} BtHid;
|
||||
|
||||
typedef enum {
|
||||
BtHidViewSubmenu,
|
||||
BtHidViewKeynote,
|
||||
BtHidViewMedia,
|
||||
BtHidViewExitConfirm,
|
||||
} BtHidView;
|
192
applications/bt/bt_hid_app/views/bt_hid_keynote.c
Executable file
192
applications/bt/bt_hid_app/views/bt_hid_keynote.c
Executable file
@@ -0,0 +1,192 @@
|
||||
#include "bt_hid_keynote.h"
|
||||
#include <furi.h>
|
||||
#include <furi-hal-bt-hid.h>
|
||||
#include <furi-hal-usb-hid.h>
|
||||
#include <gui/elements.h>
|
||||
|
||||
struct BtHidKeynote {
|
||||
View* view;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
bool left_pressed;
|
||||
bool up_pressed;
|
||||
bool right_pressed;
|
||||
bool down_pressed;
|
||||
bool ok_pressed;
|
||||
bool connected;
|
||||
} BtHidKeynoteModel;
|
||||
|
||||
static void bt_hid_keynote_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) {
|
||||
canvas_draw_triangle(canvas, x, y, 5, 3, dir);
|
||||
if(dir == CanvasDirectionBottomToTop) {
|
||||
canvas_draw_line(canvas, x, y + 6, x, y - 1);
|
||||
} else if(dir == CanvasDirectionTopToBottom) {
|
||||
canvas_draw_line(canvas, x, y - 6, x, y + 1);
|
||||
} else if(dir == CanvasDirectionRightToLeft) {
|
||||
canvas_draw_line(canvas, x + 6, y, x - 1, y);
|
||||
} else if(dir == CanvasDirectionLeftToRight) {
|
||||
canvas_draw_line(canvas, x - 6, y, x + 1, y);
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
BtHidKeynoteModel* model = context;
|
||||
|
||||
// Header
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text_aligned(canvas, 9, 3, AlignLeft, AlignTop, "Keynote");
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
// Connected status
|
||||
if(model->connected) {
|
||||
canvas_draw_icon(canvas, 18, 18, &I_Ble_connected_38x34);
|
||||
elements_multiline_text_aligned(canvas, 9, 60, AlignLeft, AlignBottom, "Connected");
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 18, 18, &I_Ble_disconnected_24x34);
|
||||
elements_multiline_text_aligned(canvas, 3, 60, AlignLeft, AlignBottom, "Disconnected");
|
||||
}
|
||||
|
||||
// Up
|
||||
canvas_draw_icon(canvas, 86, 4, &I_Button_18x18);
|
||||
if(model->up_pressed) {
|
||||
elements_slightly_rounded_box(canvas, 89, 6, 13, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
bt_hid_keynote_draw_arrow(canvas, 95, 10, CanvasDirectionBottomToTop);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Down
|
||||
canvas_draw_icon(canvas, 86, 25, &I_Button_18x18);
|
||||
if(model->down_pressed) {
|
||||
elements_slightly_rounded_box(canvas, 89, 27, 13, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
bt_hid_keynote_draw_arrow(canvas, 95, 35, CanvasDirectionTopToBottom);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Left
|
||||
canvas_draw_icon(canvas, 65, 25, &I_Button_18x18);
|
||||
if(model->left_pressed) {
|
||||
elements_slightly_rounded_box(canvas, 68, 27, 13, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
bt_hid_keynote_draw_arrow(canvas, 72, 33, CanvasDirectionRightToLeft);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Right
|
||||
canvas_draw_icon(canvas, 107, 25, &I_Button_18x18);
|
||||
if(model->right_pressed) {
|
||||
elements_slightly_rounded_box(canvas, 110, 27, 13, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
bt_hid_keynote_draw_arrow(canvas, 118, 33, CanvasDirectionLeftToRight);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Ok
|
||||
canvas_draw_icon(canvas, 63, 45, &I_Space_65x18);
|
||||
if(model->ok_pressed) {
|
||||
elements_slightly_rounded_box(canvas, 66, 47, 60, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 74, 49, &I_Ok_btn_9x9);
|
||||
elements_multiline_text_aligned(canvas, 91, 56, AlignLeft, AlignBottom, "Space");
|
||||
}
|
||||
|
||||
static void bt_hid_keynote_process_press(BtHidKeynote* bt_hid_keynote, InputEvent* event) {
|
||||
with_view_model(
|
||||
bt_hid_keynote->view, (BtHidKeynoteModel * model) {
|
||||
if(event->key == InputKeyUp) {
|
||||
model->up_pressed = true;
|
||||
furi_hal_bt_hid_kb_press(KEY_UP_ARROW);
|
||||
} else if(event->key == InputKeyDown) {
|
||||
model->down_pressed = true;
|
||||
furi_hal_bt_hid_kb_press(KEY_DOWN_ARROW);
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = true;
|
||||
furi_hal_bt_hid_kb_press(KEY_LEFT_ARROW);
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = true;
|
||||
furi_hal_bt_hid_kb_press(KEY_RIGHT_ARROW);
|
||||
} else if(event->key == InputKeyOk) {
|
||||
model->ok_pressed = true;
|
||||
furi_hal_bt_hid_kb_press(KEY_SPACE);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
static void bt_hid_keynote_process_release(BtHidKeynote* bt_hid_keynote, InputEvent* event) {
|
||||
with_view_model(
|
||||
bt_hid_keynote->view, (BtHidKeynoteModel * model) {
|
||||
if(event->key == InputKeyUp) {
|
||||
model->up_pressed = false;
|
||||
furi_hal_bt_hid_kb_release(KEY_UP_ARROW);
|
||||
} else if(event->key == InputKeyDown) {
|
||||
model->down_pressed = false;
|
||||
furi_hal_bt_hid_kb_release(KEY_DOWN_ARROW);
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = false;
|
||||
furi_hal_bt_hid_kb_release(KEY_LEFT_ARROW);
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = false;
|
||||
furi_hal_bt_hid_kb_release(KEY_RIGHT_ARROW);
|
||||
} else if(event->key == InputKeyOk) {
|
||||
model->ok_pressed = false;
|
||||
furi_hal_bt_hid_kb_release(KEY_SPACE);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
static bool bt_hid_keynote_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
BtHidKeynote* bt_hid_keynote = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event->type == InputTypePress) {
|
||||
bt_hid_keynote_process_press(bt_hid_keynote, event);
|
||||
consumed = true;
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
bt_hid_keynote_process_release(bt_hid_keynote, event);
|
||||
consumed = true;
|
||||
} else if(event->type == InputTypeShort) {
|
||||
if(event->key == InputKeyBack) {
|
||||
furi_hal_hid_kb_release_all();
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
BtHidKeynote* bt_hid_keynote_alloc() {
|
||||
BtHidKeynote* bt_hid_keynote = furi_alloc(sizeof(BtHidKeynote));
|
||||
bt_hid_keynote->view = view_alloc();
|
||||
view_set_context(bt_hid_keynote->view, bt_hid_keynote);
|
||||
view_allocate_model(bt_hid_keynote->view, ViewModelTypeLocking, sizeof(BtHidKeynoteModel));
|
||||
view_set_draw_callback(bt_hid_keynote->view, bt_hid_keynote_draw_callback);
|
||||
view_set_input_callback(bt_hid_keynote->view, bt_hid_keynote_input_callback);
|
||||
|
||||
return bt_hid_keynote;
|
||||
}
|
||||
|
||||
void bt_hid_keynote_free(BtHidKeynote* bt_hid_keynote) {
|
||||
furi_assert(bt_hid_keynote);
|
||||
view_free(bt_hid_keynote->view);
|
||||
free(bt_hid_keynote);
|
||||
}
|
||||
|
||||
View* bt_hid_keynote_get_view(BtHidKeynote* bt_hid_keynote) {
|
||||
furi_assert(bt_hid_keynote);
|
||||
return bt_hid_keynote->view;
|
||||
}
|
||||
|
||||
void bt_hid_keynote_set_connected_status(BtHidKeynote* bt_hid_keynote, bool connected) {
|
||||
furi_assert(bt_hid_keynote);
|
||||
with_view_model(
|
||||
bt_hid_keynote->view, (BtHidKeynoteModel * model) {
|
||||
model->connected = connected;
|
||||
return true;
|
||||
});
|
||||
}
|
13
applications/bt/bt_hid_app/views/bt_hid_keynote.h
Normal file
13
applications/bt/bt_hid_app/views/bt_hid_keynote.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef struct BtHidKeynote BtHidKeynote;
|
||||
|
||||
BtHidKeynote* bt_hid_keynote_alloc();
|
||||
|
||||
void bt_hid_keynote_free(BtHidKeynote* bt_hid_keynote);
|
||||
|
||||
View* bt_hid_keynote_get_view(BtHidKeynote* bt_hid_keynote);
|
||||
|
||||
void bt_hid_keynote_set_connected_status(BtHidKeynote* bt_hid_keynote, bool connected);
|
193
applications/bt/bt_hid_app/views/bt_hid_media.c
Executable file
193
applications/bt/bt_hid_app/views/bt_hid_media.c
Executable file
@@ -0,0 +1,193 @@
|
||||
#include "bt_hid_media.h"
|
||||
#include <furi.h>
|
||||
#include <furi-hal-bt-hid.h>
|
||||
#include <furi-hal-usb-hid.h>
|
||||
#include <gui/elements.h>
|
||||
|
||||
struct BtHidMedia {
|
||||
View* view;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
bool left_pressed;
|
||||
bool up_pressed;
|
||||
bool right_pressed;
|
||||
bool down_pressed;
|
||||
bool ok_pressed;
|
||||
bool connected;
|
||||
} BtHidMediaModel;
|
||||
|
||||
static void bt_hid_media_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) {
|
||||
canvas_draw_triangle(canvas, x, y, 5, 3, dir);
|
||||
if(dir == CanvasDirectionBottomToTop) {
|
||||
canvas_draw_dot(canvas, x, y - 1);
|
||||
} else if(dir == CanvasDirectionTopToBottom) {
|
||||
canvas_draw_dot(canvas, x, y + 1);
|
||||
} else if(dir == CanvasDirectionRightToLeft) {
|
||||
canvas_draw_dot(canvas, x - 1, y);
|
||||
} else if(dir == CanvasDirectionLeftToRight) {
|
||||
canvas_draw_dot(canvas, x + 1, y);
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_hid_media_draw_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
BtHidMediaModel* model = context;
|
||||
|
||||
// Header
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text_aligned(canvas, 9, 3, AlignLeft, AlignTop, "Media player");
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
// Connected status
|
||||
if(model->connected) {
|
||||
canvas_draw_icon(canvas, 23, 17, &I_Ble_connected_38x34);
|
||||
elements_multiline_text_aligned(canvas, 35, 61, AlignCenter, AlignBottom, "Connected");
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 23, 17, &I_Ble_disconnected_24x34);
|
||||
elements_multiline_text_aligned(canvas, 35, 61, AlignCenter, AlignBottom, "Disconnected");
|
||||
}
|
||||
|
||||
// Keypad circles
|
||||
canvas_draw_icon(canvas, 76, 8, &I_Circles_47x47);
|
||||
|
||||
// Up
|
||||
if(model->up_pressed) {
|
||||
canvas_draw_icon(canvas, 93, 9, &I_Pressed_Button_13x13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 96, 12, &I_Volup_8x6);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Down
|
||||
if(model->down_pressed) {
|
||||
canvas_draw_icon(canvas, 93, 41, &I_Pressed_Button_13x13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 96, 45, &I_Voldwn_6x6);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Left
|
||||
if(model->left_pressed) {
|
||||
canvas_draw_icon(canvas, 77, 25, &I_Pressed_Button_13x13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
bt_hid_media_draw_arrow(canvas, 82, 31, CanvasDirectionRightToLeft);
|
||||
bt_hid_media_draw_arrow(canvas, 86, 31, CanvasDirectionRightToLeft);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Right
|
||||
if(model->right_pressed) {
|
||||
canvas_draw_icon(canvas, 109, 25, &I_Pressed_Button_13x13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
bt_hid_media_draw_arrow(canvas, 112, 31, CanvasDirectionLeftToRight);
|
||||
bt_hid_media_draw_arrow(canvas, 116, 31, CanvasDirectionLeftToRight);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Ok
|
||||
if(model->ok_pressed) {
|
||||
canvas_draw_icon(canvas, 93, 25, &I_Pressed_Button_13x13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
bt_hid_media_draw_arrow(canvas, 96, 31, CanvasDirectionLeftToRight);
|
||||
canvas_draw_line(canvas, 100, 29, 100, 33);
|
||||
canvas_draw_line(canvas, 102, 29, 102, 33);
|
||||
}
|
||||
|
||||
static void bt_hid_media_process_press(BtHidMedia* bt_hid_media, InputEvent* event) {
|
||||
with_view_model(
|
||||
bt_hid_media->view, (BtHidMediaModel * model) {
|
||||
if(event->key == InputKeyUp) {
|
||||
model->up_pressed = true;
|
||||
furi_hal_bt_hid_media_press(FuriHalBtHidMediaVolumeUp);
|
||||
} else if(event->key == InputKeyDown) {
|
||||
model->down_pressed = true;
|
||||
furi_hal_bt_hid_media_press(FuriHalBtHidMediaVolumeDown);
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = true;
|
||||
furi_hal_bt_hid_media_press(FuriHalBtHidMediaScanPrevious);
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = true;
|
||||
furi_hal_bt_hid_media_press(FuriHalBtHidMediaScanNext);
|
||||
} else if(event->key == InputKeyOk) {
|
||||
model->ok_pressed = true;
|
||||
furi_hal_bt_hid_media_press(FuriHalBtHidMediaPlayPause);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
static void bt_hid_media_process_release(BtHidMedia* bt_hid_media, InputEvent* event) {
|
||||
with_view_model(
|
||||
bt_hid_media->view, (BtHidMediaModel * model) {
|
||||
if(event->key == InputKeyUp) {
|
||||
model->up_pressed = false;
|
||||
furi_hal_bt_hid_media_release(FuriHalBtHidMediaVolumeUp);
|
||||
} else if(event->key == InputKeyDown) {
|
||||
model->down_pressed = false;
|
||||
furi_hal_bt_hid_media_release(FuriHalBtHidMediaVolumeDown);
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = false;
|
||||
furi_hal_bt_hid_media_release(FuriHalBtHidMediaScanPrevious);
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = false;
|
||||
furi_hal_bt_hid_media_release(FuriHalBtHidMediaScanNext);
|
||||
} else if(event->key == InputKeyOk) {
|
||||
model->ok_pressed = false;
|
||||
furi_hal_bt_hid_media_release(FuriHalBtHidMediaPlayPause);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
static bool bt_hid_media_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
BtHidMedia* bt_hid_media = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event->type == InputTypePress) {
|
||||
bt_hid_media_process_press(bt_hid_media, event);
|
||||
consumed = true;
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
bt_hid_media_process_release(bt_hid_media, event);
|
||||
consumed = true;
|
||||
} else if(event->type == InputTypeShort) {
|
||||
if(event->key == InputKeyBack) {
|
||||
furi_hal_bt_hid_media_release_all();
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
BtHidMedia* bt_hid_media_alloc() {
|
||||
BtHidMedia* bt_hid_media = furi_alloc(sizeof(BtHidMedia));
|
||||
bt_hid_media->view = view_alloc();
|
||||
view_set_context(bt_hid_media->view, bt_hid_media);
|
||||
view_allocate_model(bt_hid_media->view, ViewModelTypeLocking, sizeof(BtHidMediaModel));
|
||||
view_set_draw_callback(bt_hid_media->view, bt_hid_media_draw_callback);
|
||||
view_set_input_callback(bt_hid_media->view, bt_hid_media_input_callback);
|
||||
|
||||
return bt_hid_media;
|
||||
}
|
||||
|
||||
void bt_hid_media_free(BtHidMedia* bt_hid_media) {
|
||||
furi_assert(bt_hid_media);
|
||||
view_free(bt_hid_media->view);
|
||||
free(bt_hid_media);
|
||||
}
|
||||
|
||||
View* bt_hid_media_get_view(BtHidMedia* bt_hid_media) {
|
||||
furi_assert(bt_hid_media);
|
||||
return bt_hid_media->view;
|
||||
}
|
||||
|
||||
void bt_hid_media_set_connected_status(BtHidMedia* bt_hid_media, bool connected) {
|
||||
furi_assert(bt_hid_media);
|
||||
with_view_model(
|
||||
bt_hid_media->view, (BtHidMediaModel * model) {
|
||||
model->connected = connected;
|
||||
return true;
|
||||
});
|
||||
}
|
13
applications/bt/bt_hid_app/views/bt_hid_media.h
Normal file
13
applications/bt/bt_hid_app/views/bt_hid_media.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef struct BtHidMedia BtHidMedia;
|
||||
|
||||
BtHidMedia* bt_hid_media_alloc();
|
||||
|
||||
void bt_hid_media_free(BtHidMedia* bt_hid_media);
|
||||
|
||||
View* bt_hid_media_get_view(BtHidMedia* bt_hid_media);
|
||||
|
||||
void bt_hid_media_set_connected_status(BtHidMedia* bt_hid_media, bool connected);
|
@@ -2,6 +2,8 @@
|
||||
#include "battery_service.h"
|
||||
#include "bt_keys_storage.h"
|
||||
|
||||
#include <applications/notification/notification-messages.h>
|
||||
|
||||
#define TAG "BtSrv"
|
||||
|
||||
#define BT_RPC_EVENT_BUFF_SENT (1UL << 0)
|
||||
@@ -29,6 +31,7 @@ static ViewPort* bt_statusbar_view_port_alloc(Bt* bt) {
|
||||
|
||||
static void bt_pin_code_show_event_handler(Bt* bt, uint32_t pin) {
|
||||
furi_assert(bt);
|
||||
notification_message(bt->notification, &sequence_display_on);
|
||||
string_t pin_str;
|
||||
dialog_message_set_icon(bt->dialog_message, &I_BLE_Pairing_128x64, 0, 0);
|
||||
string_init_printf(pin_str, "Pairing code\n%06d", pin);
|
||||
@@ -41,6 +44,7 @@ static void bt_pin_code_show_event_handler(Bt* bt, uint32_t pin) {
|
||||
|
||||
static bool bt_pin_code_verify_event_handler(Bt* bt, uint32_t pin) {
|
||||
furi_assert(bt);
|
||||
notification_message(bt->notification, &sequence_display_on);
|
||||
string_t pin_str;
|
||||
dialog_message_set_icon(bt->dialog_message, &I_BLE_Pairing_128x64, 0, 0);
|
||||
string_init_printf(pin_str, "Verify code\n%06d", pin);
|
||||
@@ -80,6 +84,8 @@ Bt* bt_alloc() {
|
||||
|
||||
// Setup statusbar view port
|
||||
bt->statusbar_view_port = bt_statusbar_view_port_alloc(bt);
|
||||
// Notification
|
||||
bt->notification = furi_record_open("notification");
|
||||
// Gui
|
||||
bt->gui = furi_record_open("gui");
|
||||
gui_add_view_port(bt->gui, bt->statusbar_view_port, GuiLayerStatusBarLeft);
|
||||
@@ -288,6 +294,9 @@ int32_t bt_srv() {
|
||||
if(message.type == BtMessageTypeUpdateStatusbar) {
|
||||
// Update statusbar
|
||||
bt_statusbar_update(bt);
|
||||
if(bt->status_changed_cb) {
|
||||
bt->status_changed_cb(bt->status, bt->status_changed_ctx);
|
||||
}
|
||||
} else if(message.type == BtMessageTypeUpdateBatteryLevel) {
|
||||
// Update battery level
|
||||
furi_hal_bt_update_battery_level(message.data.battery_level);
|
||||
|
@@ -9,13 +9,20 @@ extern "C" {
|
||||
|
||||
typedef struct Bt Bt;
|
||||
|
||||
typedef enum {
|
||||
BtStatusOff,
|
||||
BtStatusAdvertising,
|
||||
BtStatusConnected,
|
||||
} BtStatus;
|
||||
|
||||
typedef enum {
|
||||
BtProfileSerial,
|
||||
BtProfileHidKeyboard,
|
||||
} BtProfile;
|
||||
|
||||
/**
|
||||
* Change BLE Profile
|
||||
typedef void (*BtStatusChangedCallback)(BtStatus status, void* context);
|
||||
|
||||
/** Change BLE Profile
|
||||
* @note Call of this function leads to 2nd core restart
|
||||
*
|
||||
* @param bt Bt instance
|
||||
@@ -25,6 +32,14 @@ typedef enum {
|
||||
*/
|
||||
bool bt_set_profile(Bt* bt, BtProfile profile);
|
||||
|
||||
/** Set callback for Bluetooth status change notification
|
||||
*
|
||||
* @param bt Bt instance
|
||||
* @param callback BtStatusChangedCallback instance
|
||||
* @param context pointer to context
|
||||
*/
|
||||
void bt_set_status_changed_callback(Bt* bt, BtStatusChangedCallback callback, void* context);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@@ -13,3 +13,10 @@ bool bt_set_profile(Bt* bt, BtProfile profile) {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void bt_set_status_changed_callback(Bt* bt, BtStatusChangedCallback callback, void* context) {
|
||||
furi_assert(bt);
|
||||
|
||||
bt->status_changed_cb = callback;
|
||||
bt->status_changed_ctx = context;
|
||||
}
|
||||
|
@@ -12,17 +12,12 @@
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <power/power_service/power.h>
|
||||
#include <applications/rpc/rpc.h>
|
||||
#include <applications/notification/notification.h>
|
||||
|
||||
#include "../bt_settings.h"
|
||||
|
||||
#define BT_API_UNLOCK_EVENT (1UL << 0)
|
||||
|
||||
typedef enum {
|
||||
BtStatusOff,
|
||||
BtStatusAdvertising,
|
||||
BtStatusConnected,
|
||||
} BtStatus;
|
||||
|
||||
typedef enum {
|
||||
BtMessageTypeUpdateStatusbar,
|
||||
BtMessageTypeUpdateBatteryLevel,
|
||||
@@ -51,6 +46,7 @@ struct Bt {
|
||||
BtStatus status;
|
||||
BtProfile profile;
|
||||
osMessageQueueId_t message_queue;
|
||||
NotificationApp* notification;
|
||||
Gui* gui;
|
||||
ViewPort* statusbar_view_port;
|
||||
DialogsApp* dialogs;
|
||||
@@ -60,4 +56,6 @@ struct Bt {
|
||||
RpcSession* rpc_session;
|
||||
osEventFlagsId_t rpc_event;
|
||||
osEventFlagsId_t api_event;
|
||||
BtStatusChangedCallback status_changed_cb;
|
||||
void* status_changed_ctx;
|
||||
};
|
||||
|
@@ -1,138 +0,0 @@
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <bt/bt_service/bt.h>
|
||||
#include <furi-hal-bt.h>
|
||||
#include <furi-hal-bt-hid.h>
|
||||
#include <furi-hal-usb-hid.h>
|
||||
|
||||
#define TAG "BleKeyboardApp"
|
||||
|
||||
typedef enum {
|
||||
EventTypeInput,
|
||||
} EventType;
|
||||
|
||||
typedef struct {
|
||||
union {
|
||||
InputEvent input;
|
||||
};
|
||||
EventType type;
|
||||
} BleKeyboardEvent;
|
||||
|
||||
static void ble_keyboard_render_callback(Canvas* canvas, void* ctx) {
|
||||
canvas_clear(canvas);
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str(canvas, 0, 10, "BLE keypad demo");
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str(canvas, 0, 63, "Hold [back] to exit");
|
||||
}
|
||||
|
||||
static void ble_keyboard_input_callback(InputEvent* input_event, void* ctx) {
|
||||
osMessageQueueId_t event_queue = ctx;
|
||||
|
||||
BleKeyboardEvent event;
|
||||
event.type = EventTypeInput;
|
||||
event.input = *input_event;
|
||||
osMessageQueuePut(event_queue, &event, 0, osWaitForever);
|
||||
}
|
||||
|
||||
int32_t ble_keyboard_app(void* p) {
|
||||
Bt* bt = furi_record_open("bt");
|
||||
if(!bt_set_profile(bt, BtProfileHidKeyboard)) {
|
||||
FURI_LOG_E(TAG, "Failed to switch profile");
|
||||
furi_record_close("bt");
|
||||
return -1;
|
||||
}
|
||||
bool bt_turned_on = furi_hal_bt_is_active();
|
||||
if(!bt_turned_on) {
|
||||
furi_hal_bt_start_advertising();
|
||||
}
|
||||
|
||||
osMessageQueueId_t event_queue = osMessageQueueNew(8, sizeof(BleKeyboardEvent), NULL);
|
||||
furi_check(event_queue);
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
|
||||
view_port_draw_callback_set(view_port, ble_keyboard_render_callback, NULL);
|
||||
view_port_input_callback_set(view_port, ble_keyboard_input_callback, event_queue);
|
||||
|
||||
// Open GUI and register view_port
|
||||
Gui* gui = furi_record_open("gui");
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
BleKeyboardEvent event;
|
||||
while(1) {
|
||||
osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, osWaitForever);
|
||||
|
||||
if(event_status == osOK) {
|
||||
if(event.type == EventTypeInput) {
|
||||
if(event.input.type == InputTypeLong && event.input.key == InputKeyBack) {
|
||||
furi_hal_bt_hid_kb_release_all();
|
||||
break;
|
||||
}
|
||||
|
||||
if(event.input.key == InputKeyBack) {
|
||||
if(event.input.type == InputTypePress) {
|
||||
furi_hal_bt_hid_kb_press(KEY_ESC);
|
||||
} else if(event.input.type == InputTypeRelease) {
|
||||
furi_hal_bt_hid_kb_release(KEY_ESC);
|
||||
}
|
||||
}
|
||||
|
||||
if(event.input.key == InputKeyOk) {
|
||||
if(event.input.type == InputTypePress) {
|
||||
furi_hal_bt_hid_kb_press(KEY_ENTER);
|
||||
} else if(event.input.type == InputTypeRelease) {
|
||||
furi_hal_bt_hid_kb_release(KEY_ENTER);
|
||||
}
|
||||
}
|
||||
|
||||
if(event.input.key == InputKeyRight) {
|
||||
if(event.input.type == InputTypePress) {
|
||||
furi_hal_bt_hid_kb_press(KEY_RIGHT_ARROW);
|
||||
} else if(event.input.type == InputTypeRelease) {
|
||||
furi_hal_bt_hid_kb_release(KEY_RIGHT_ARROW);
|
||||
}
|
||||
}
|
||||
|
||||
if(event.input.key == InputKeyLeft) {
|
||||
if(event.input.type == InputTypePress) {
|
||||
furi_hal_bt_hid_kb_press(KEY_LEFT_ARROW);
|
||||
} else if(event.input.type == InputTypeRelease) {
|
||||
furi_hal_bt_hid_kb_release(KEY_LEFT_ARROW);
|
||||
}
|
||||
}
|
||||
|
||||
if(event.input.key == InputKeyDown) {
|
||||
if(event.input.type == InputTypePress) {
|
||||
furi_hal_bt_hid_kb_press(KEY_DOWN_ARROW);
|
||||
} else if(event.input.type == InputTypeRelease) {
|
||||
furi_hal_bt_hid_kb_release(KEY_DOWN_ARROW);
|
||||
}
|
||||
}
|
||||
|
||||
if(event.input.key == InputKeyUp) {
|
||||
if(event.input.type == InputTypePress) {
|
||||
furi_hal_bt_hid_kb_press(KEY_UP_ARROW);
|
||||
} else if(event.input.type == InputTypeRelease) {
|
||||
furi_hal_bt_hid_kb_release(KEY_UP_ARROW);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
view_port_update(view_port);
|
||||
}
|
||||
|
||||
if(bt_turned_on) {
|
||||
furi_hal_bt_stop_advertising();
|
||||
}
|
||||
// remove & free all stuff created by app
|
||||
gui_remove_view_port(gui, view_port);
|
||||
view_port_free(view_port);
|
||||
osMessageQueueDelete(event_queue);
|
||||
furi_record_close("gui");
|
||||
bt_set_profile(bt, BtProfileSerial);
|
||||
furi_record_close("bt");
|
||||
return 0;
|
||||
}
|
31
applications/gui/canvas.c
Normal file → Executable file
31
applications/gui/canvas.c
Normal file → Executable file
@@ -47,7 +47,7 @@ void canvas_reset(Canvas* canvas) {
|
||||
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_set_font_direction(canvas, CanvasFontDirectionLeftToRight);
|
||||
canvas_set_font_direction(canvas, CanvasDirectionLeftToRight);
|
||||
}
|
||||
|
||||
void canvas_commit(Canvas* canvas) {
|
||||
@@ -115,7 +115,7 @@ void canvas_set_color(Canvas* canvas, Color color) {
|
||||
u8g2_SetDrawColor(&canvas->fb, color);
|
||||
}
|
||||
|
||||
void canvas_set_font_direction(Canvas* canvas, CanvasFontDirection dir) {
|
||||
void canvas_set_font_direction(Canvas* canvas, CanvasDirection dir) {
|
||||
furi_assert(canvas);
|
||||
u8g2_SetFontDirection(&canvas->fb, dir);
|
||||
}
|
||||
@@ -304,6 +304,33 @@ void canvas_draw_disc(Canvas* canvas, uint8_t x, uint8_t y, uint8_t radius) {
|
||||
u8g2_DrawDisc(&canvas->fb, x, y, radius, U8G2_DRAW_ALL);
|
||||
}
|
||||
|
||||
void canvas_draw_triangle(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t base,
|
||||
uint8_t height,
|
||||
CanvasDirection dir) {
|
||||
furi_assert(canvas);
|
||||
if(dir == CanvasDirectionBottomToTop) {
|
||||
canvas_draw_line(canvas, x - base / 2, y, x + base / 2, y);
|
||||
canvas_draw_line(canvas, x - base / 2, y, x, y - height + 1);
|
||||
canvas_draw_line(canvas, x, y - height + 1, x + base / 2, y);
|
||||
} else if(dir == CanvasDirectionTopToBottom) {
|
||||
canvas_draw_line(canvas, x - base / 2, y, x + base / 2, y);
|
||||
canvas_draw_line(canvas, x - base / 2, y, x, y + height - 1);
|
||||
canvas_draw_line(canvas, x, y + height - 1, x + base / 2, y);
|
||||
} else if(dir == CanvasDirectionRightToLeft) {
|
||||
canvas_draw_line(canvas, x, y - base / 2, x, y + base / 2);
|
||||
canvas_draw_line(canvas, x, y - base / 2, x - height + 1, y);
|
||||
canvas_draw_line(canvas, x - height + 1, y, x, y + base / 2);
|
||||
} else if(dir == CanvasDirectionLeftToRight) {
|
||||
canvas_draw_line(canvas, x, y - base / 2, x, y + base / 2);
|
||||
canvas_draw_line(canvas, x, y - base / 2, x + height - 1, y);
|
||||
canvas_draw_line(canvas, x + height - 1, y, x, y + base / 2);
|
||||
}
|
||||
}
|
||||
|
||||
void canvas_draw_xbm(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
|
29
applications/gui/canvas.h
Normal file → Executable file
29
applications/gui/canvas.h
Normal file → Executable file
@@ -47,11 +47,11 @@ typedef enum {
|
||||
|
||||
/** Font Direction */
|
||||
typedef enum {
|
||||
CanvasFontDirectionLeftToRight,
|
||||
CanvasFontDirectionTopToDown,
|
||||
CanvasFontDirectionRightToLeft,
|
||||
CanvasFontDirectionDownToTop,
|
||||
} CanvasFontDirection;
|
||||
CanvasDirectionLeftToRight,
|
||||
CanvasDirectionTopToBottom,
|
||||
CanvasDirectionRightToLeft,
|
||||
CanvasDirectionBottomToTop,
|
||||
} CanvasDirection;
|
||||
|
||||
/** Font parameters */
|
||||
typedef struct {
|
||||
@@ -116,7 +116,7 @@ void canvas_set_color(Canvas* canvas, Color color);
|
||||
* @param canvas Canvas instance
|
||||
* @param dir Direction font
|
||||
*/
|
||||
void canvas_set_font_direction(Canvas* canvas, CanvasFontDirection dir);
|
||||
void canvas_set_font_direction(Canvas* canvas, CanvasDirection dir);
|
||||
|
||||
/** Invert drawing color
|
||||
*
|
||||
@@ -273,6 +273,23 @@ void canvas_draw_circle(Canvas* canvas, uint8_t x, uint8_t y, uint8_t r);
|
||||
*/
|
||||
void canvas_draw_disc(Canvas* canvas, uint8_t x, uint8_t y, uint8_t r);
|
||||
|
||||
/** Draw triangle with given base and height lengths and their intersection coordinate
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param x x coordinate of base and height intersection
|
||||
* @param y y coordinate of base and height intersection
|
||||
* @param base length of triangle side
|
||||
* @param height length of triangle height
|
||||
* @param dir CanvasDirection triangle orientaion
|
||||
*/
|
||||
void canvas_draw_triangle(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t base,
|
||||
uint8_t height,
|
||||
CanvasDirection dir);
|
||||
|
||||
/** Draw glyph
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
|
@@ -258,7 +258,7 @@ void view_dispatcher_handle_input(ViewDispatcher* view_dispatcher, InputEvent* e
|
||||
if(view_dispatcher->current_view) {
|
||||
is_consumed = view_input(view_dispatcher->current_view, event);
|
||||
}
|
||||
if(!is_consumed && event->type == InputTypeShort) {
|
||||
if(!is_consumed && (event->type == InputTypeShort || event->type == InputTypeLong)) {
|
||||
// TODO remove view navigation handlers
|
||||
uint32_t view_id = VIEW_IGNORE;
|
||||
if(event->key == InputKeyBack) {
|
||||
|
Reference in New Issue
Block a user