Refactored desktop (#118)
BIN
Data/assets/desktop_icon_apps.png
Normal file
|
After Width: | Height: | Size: 564 B |
BIN
Data/assets/desktop_icon_files.png
Normal file
|
After Width: | Height: | Size: 724 B |
BIN
Data/assets/desktop_icon_settings.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
@ -1,4 +1,6 @@
|
|||||||
# TODOs
|
# TODOs
|
||||||
|
- Crash logs stored on sdcard or elsewhere: perhaps show crash screen after recovering from crash (with QR code? https://github.com/ricmoo/QRCode)
|
||||||
|
- Logging
|
||||||
- AppContext's onResult should pass the app id (or launch request id!) that was started, so we can differentiate between multiple types of apps being launched
|
- AppContext's onResult should pass the app id (or launch request id!) that was started, so we can differentiate between multiple types of apps being launched
|
||||||
- Loader: Use Timer instead of Thread, and move API to `tt::app::`
|
- Loader: Use Timer instead of Thread, and move API to `tt::app::`
|
||||||
- Gpio: Use Timer instead of Thread
|
- Gpio: Use Timer instead of Thread
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 58 KiB |
BIN
Documentation/pics/screenshot-AppList.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
@ -1,8 +1,6 @@
|
|||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Tactility is a front-end application platform for ESP32.
|
Tactility is an operating system that is focusing on the ESP32 microcontroller.
|
||||||
It is currently intended for touchscreen devices, but the goal is to also support different types of input in the future.
|
|
||||||
Tactility provides an application framework that borrows concepts from [Flipper Zero](https://github.com/flipperdevices/flipperzero-firmware/) and mobile phone operating systems.
|
|
||||||
|
|
||||||
 
|
 
|
||||||
|
|
||||||
@ -14,7 +12,7 @@ Next to desktop functionality, Tactility makes it easy to manage system settings
|
|||||||
|
|
||||||
There are also built-in apps:
|
There are also built-in apps:
|
||||||
|
|
||||||
 
|
 
|
||||||
|
|
||||||
Play with the built-in apps or build your own! Use one of the supported devices or set up the drivers for your own hardware platform.
|
Play with the built-in apps or build your own! Use one of the supported devices or set up the drivers for your own hardware platform.
|
||||||
|
|
||||||
@ -26,8 +24,9 @@ Noteworthy features:
|
|||||||
- Includes a PC simulator build target to speed up development.
|
- Includes a PC simulator build target to speed up development.
|
||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
- ESP32 (any?) with a touchscreen
|
- ESP32 (any?)
|
||||||
- [esp-idf 5.3](https://docs.espressif.com/projects/esp-idf/en/release-v5.3/esp32/get-started/index.html) or a newer v5.3.x
|
- [esp-idf 5.3](https://docs.espressif.com/projects/esp-idf/en/release-v5.3/esp32/get-started/index.html) or a newer v5.3.x
|
||||||
|
- (for PC simulator) SDL2 library, including SDL image
|
||||||
|
|
||||||
## Making apps is easy!
|
## Making apps is easy!
|
||||||
|
|
||||||
|
|||||||
@ -36,6 +36,7 @@ static const std::vector<const service::ServiceManifest*> system_services = {
|
|||||||
|
|
||||||
namespace app {
|
namespace app {
|
||||||
namespace alertdialog { extern const AppManifest manifest; }
|
namespace alertdialog { extern const AppManifest manifest; }
|
||||||
|
namespace applist { extern const AppManifest manifest; }
|
||||||
namespace boot { extern const AppManifest manifest; }
|
namespace boot { extern const AppManifest manifest; }
|
||||||
namespace desktop { extern const AppManifest manifest; }
|
namespace desktop { extern const AppManifest manifest; }
|
||||||
namespace files { extern const AppManifest manifest; }
|
namespace files { extern const AppManifest manifest; }
|
||||||
@ -64,6 +65,7 @@ extern const app::AppManifest screenshot_app;
|
|||||||
|
|
||||||
static const std::vector<const app::AppManifest*> system_apps = {
|
static const std::vector<const app::AppManifest*> system_apps = {
|
||||||
&app::alertdialog::manifest,
|
&app::alertdialog::manifest,
|
||||||
|
&app::applist::manifest,
|
||||||
&app::boot::manifest,
|
&app::boot::manifest,
|
||||||
&app::desktop::manifest,
|
&app::desktop::manifest,
|
||||||
&app::display::manifest,
|
&app::display::manifest,
|
||||||
|
|||||||
64
Tactility/Source/app/applist/AppList.cpp
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#include "app/ManifestRegistry.h"
|
||||||
|
#include "Assets.h"
|
||||||
|
#include "Check.h"
|
||||||
|
#include "lvgl.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include "service/loader/Loader.h"
|
||||||
|
#include "lvgl/Toolbar.h"
|
||||||
|
|
||||||
|
namespace tt::app::applist {
|
||||||
|
|
||||||
|
static void onAppPressed(lv_event_t* e) {
|
||||||
|
lv_event_code_t code = lv_event_get_code(e);
|
||||||
|
if (code == LV_EVENT_CLICKED) {
|
||||||
|
const auto* manifest = static_cast<const AppManifest*>(lv_event_get_user_data(e));
|
||||||
|
service::loader::startApp(manifest->id, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void createAppWidget(const AppManifest* manifest, void* parent) {
|
||||||
|
tt_check(parent);
|
||||||
|
auto* list = static_cast<lv_obj_t*>(parent);
|
||||||
|
const void* icon = !manifest->icon.empty() ? manifest->icon.c_str() : TT_ASSETS_APP_ICON_FALLBACK;
|
||||||
|
lv_obj_t* btn = lv_list_add_button(list, icon, manifest->name.c_str());
|
||||||
|
lv_obj_add_event_cb(btn, &onAppPressed, LV_EVENT_CLICKED, (void*)manifest);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void onShow(TT_UNUSED AppContext& app, lv_obj_t* parent) {
|
||||||
|
auto* toolbar = lvgl::toolbar_create(parent, app);
|
||||||
|
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
|
||||||
|
|
||||||
|
lv_obj_t* list = lv_list_create(parent);
|
||||||
|
lv_obj_set_width(list, LV_PCT(100));
|
||||||
|
lv_obj_align_to(list, toolbar, LV_ALIGN_OUT_BOTTOM_MID, 0, 0);
|
||||||
|
|
||||||
|
auto toolbar_height = lv_obj_get_height(toolbar);
|
||||||
|
auto parent_content_height = lv_obj_get_content_height(parent);
|
||||||
|
lv_obj_set_height(list, parent_content_height - toolbar_height);
|
||||||
|
|
||||||
|
auto manifests = getApps();
|
||||||
|
std::sort(manifests.begin(), manifests.end(), SortAppManifestByName);
|
||||||
|
|
||||||
|
lv_list_add_text(list, "User");
|
||||||
|
for (const auto& manifest: manifests) {
|
||||||
|
if (manifest->type == TypeUser) {
|
||||||
|
createAppWidget(manifest, list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_list_add_text(list, "System");
|
||||||
|
for (const auto& manifest: manifests) {
|
||||||
|
if (manifest->type == TypeSystem) {
|
||||||
|
createAppWidget(manifest, list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern const AppManifest manifest = {
|
||||||
|
.id = "AppList",
|
||||||
|
.name = "Apps",
|
||||||
|
.type = TypeHidden,
|
||||||
|
.onShow = onShow,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
@ -1,49 +1,67 @@
|
|||||||
#include "app/ManifestRegistry.h"
|
#include "app/ManifestRegistry.h"
|
||||||
#include "Assets.h"
|
|
||||||
#include "Check.h"
|
#include "Check.h"
|
||||||
#include "lvgl.h"
|
#include "lvgl.h"
|
||||||
#include <algorithm>
|
|
||||||
#include "service/loader/Loader.h"
|
#include "service/loader/Loader.h"
|
||||||
|
|
||||||
namespace tt::app::desktop {
|
namespace tt::app::desktop {
|
||||||
|
|
||||||
static void onAppPressed(lv_event_t* e) {
|
static void onAppPressed(TT_UNUSED lv_event_t* e) {
|
||||||
lv_event_code_t code = lv_event_get_code(e);
|
auto* appId = (const char*)lv_event_get_user_data(e);
|
||||||
if (code == LV_EVENT_CLICKED) {
|
service::loader::startApp(appId, false);
|
||||||
const auto* manifest = static_cast<const AppManifest*>(lv_event_get_user_data(e));
|
|
||||||
service::loader::startApp(manifest->id, false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void createAppWidget(const AppManifest* manifest, void* parent) {
|
static lv_obj_t* createAppButton(lv_obj_t* parent, const char* title, const char* imageFile, const char* appId, int32_t buttonPaddingLeft) {
|
||||||
tt_check(parent);
|
auto* wrapper = lv_obj_create(parent);
|
||||||
auto* list = static_cast<lv_obj_t*>(parent);
|
lv_obj_set_size(wrapper, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
|
||||||
const void* icon = !manifest->icon.empty() ? manifest->icon.c_str() : TT_ASSETS_APP_ICON_FALLBACK;
|
lv_obj_set_style_pad_ver(wrapper, 0, 0);
|
||||||
lv_obj_t* btn = lv_list_add_button(list, icon, manifest->name.c_str());
|
lv_obj_set_style_pad_left(wrapper, buttonPaddingLeft, 0);
|
||||||
lv_obj_add_event_cb(btn, &onAppPressed, LV_EVENT_CLICKED, (void*)manifest);
|
lv_obj_set_style_pad_right(wrapper, 0, 0);
|
||||||
|
lv_obj_set_style_border_width(wrapper, 0, 0);
|
||||||
|
|
||||||
|
auto* apps_button = lv_button_create(wrapper);
|
||||||
|
lv_obj_set_style_pad_hor(apps_button, 0, 0);
|
||||||
|
lv_obj_set_style_pad_top(apps_button, 0, 0);
|
||||||
|
lv_obj_set_style_pad_bottom(apps_button, 16, 0);
|
||||||
|
lv_obj_set_style_shadow_width(apps_button, 0, 0);
|
||||||
|
lv_obj_set_style_border_width(apps_button, 0, 0);
|
||||||
|
lv_obj_set_style_bg_color(apps_button, lv_color_white(), 0);
|
||||||
|
|
||||||
|
auto* button_image = lv_image_create(apps_button);
|
||||||
|
lv_image_set_src(button_image, imageFile);
|
||||||
|
lv_obj_add_event_cb(apps_button, onAppPressed, LV_EVENT_CLICKED, (void*)appId);
|
||||||
|
lv_obj_set_style_image_recolor(button_image, lv_theme_get_color_primary(parent), 0);
|
||||||
|
lv_obj_set_style_image_recolor_opa(button_image, LV_OPA_COVER, 0);
|
||||||
|
|
||||||
|
auto* label = lv_label_create(wrapper);
|
||||||
|
lv_label_set_text(label, title);
|
||||||
|
lv_obj_align(label, LV_ALIGN_BOTTOM_MID, 0, 0);
|
||||||
|
|
||||||
|
return wrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void onShow(TT_UNUSED AppContext& app, lv_obj_t* parent) {
|
static void onShow(TT_UNUSED AppContext& app, lv_obj_t* parent) {
|
||||||
lv_obj_t* list = lv_list_create(parent);
|
auto* wrapper = lv_obj_create(parent);
|
||||||
lv_obj_set_size(list, LV_PCT(100), LV_PCT(100));
|
|
||||||
lv_obj_center(list);
|
|
||||||
|
|
||||||
auto manifests = getApps();
|
lv_obj_align(wrapper, LV_ALIGN_CENTER, 0, 0);
|
||||||
std::sort(manifests.begin(), manifests.end(), SortAppManifestByName);
|
lv_obj_set_style_pad_all(wrapper, 0, 0);
|
||||||
|
lv_obj_set_size(wrapper, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
|
||||||
|
lv_obj_set_style_border_width(wrapper, 0, 0);
|
||||||
|
lv_obj_set_flex_grow(wrapper, 1);
|
||||||
|
|
||||||
lv_list_add_text(list, "User");
|
auto* display = lv_obj_get_display(parent);
|
||||||
for (const auto& manifest: manifests) {
|
auto orientation = lv_display_get_rotation(display);
|
||||||
if (manifest->type == TypeUser) {
|
if (orientation == LV_DISPLAY_ROTATION_0 || orientation == LV_DISPLAY_ROTATION_180) {
|
||||||
createAppWidget(manifest, list);
|
lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_ROW);
|
||||||
}
|
} else {
|
||||||
|
lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN);
|
||||||
}
|
}
|
||||||
|
|
||||||
lv_list_add_text(list, "System");
|
int32_t available_width = lv_display_get_horizontal_resolution(display) - (3 * 80);
|
||||||
for (const auto& manifest: manifests) {
|
int32_t padding = TT_MIN(available_width / 4, 64);
|
||||||
if (manifest->type == TypeSystem) {
|
|
||||||
createAppWidget(manifest, list);
|
createAppButton(wrapper, "Apps", "A:/assets/desktop_icon_apps.png", "AppList", 0);
|
||||||
}
|
createAppButton(wrapper, "Files", "A:/assets/desktop_icon_files.png", "Files", padding);
|
||||||
}
|
createAppButton(wrapper, "Settings", "A:/assets/desktop_icon_settings.png", "Settings", padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern const AppManifest manifest = {
|
extern const AppManifest manifest = {
|
||||||
|
|||||||
@ -256,7 +256,7 @@ extern const AppManifest manifest = {
|
|||||||
.id = "Files",
|
.id = "Files",
|
||||||
.name = "Files",
|
.name = "Files",
|
||||||
.icon = TT_ASSETS_APP_ICON_FILES,
|
.icon = TT_ASSETS_APP_ICON_FILES,
|
||||||
.type = TypeSystem,
|
.type = TypeHidden,
|
||||||
.onStart = onStart,
|
.onStart = onStart,
|
||||||
.onShow = onShow,
|
.onShow = onShow,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -46,7 +46,7 @@ extern const AppManifest manifest = {
|
|||||||
.id = "Settings",
|
.id = "Settings",
|
||||||
.name = "Settings",
|
.name = "Settings",
|
||||||
.icon = TT_ASSETS_APP_ICON_SETTINGS,
|
.icon = TT_ASSETS_APP_ICON_SETTINGS,
|
||||||
.type = TypeSystem,
|
.type = TypeHidden,
|
||||||
.onShow = onShow,
|
.onShow = onShow,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||