Added image viewer app (#73)
This commit is contained in:
parent
369180cb5a
commit
18e4383bcf
@ -13,6 +13,8 @@ typedef void* App;
|
||||
typedef enum {
|
||||
/** A desktop app sits at the root of the app stack managed by the Loader service */
|
||||
AppTypeDesktop,
|
||||
/** Apps that generally aren't started from the desktop (e.g. image viewer) */
|
||||
AppTypeHidden,
|
||||
/** Standard apps, provided by the system. */
|
||||
AppTypeSystem,
|
||||
/** The apps that are launched/shown by the Settings app. The Settings app itself is of type AppTypeSystem. */
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#include "files_data.h"
|
||||
|
||||
#include "app.h"
|
||||
#include "apps/system/image_viewer/image_viewer.h"
|
||||
#include "assets.h"
|
||||
#include "check.h"
|
||||
#include "file_utils.h"
|
||||
@ -9,13 +10,14 @@
|
||||
#include "string_utils.h"
|
||||
#include "ui/toolbar.h"
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define TAG "files_app"
|
||||
|
||||
/**
|
||||
* Lower case check to see if the given file matches the provided file extension
|
||||
* Case-insensitive check to see if the given file matches the provided file extension.
|
||||
* @param path the full path to the file
|
||||
* @param extension the extension to look for, including the period symbol
|
||||
* @param extension the extension to look for, including the period symbol, in lower case
|
||||
* @return true on match
|
||||
*/
|
||||
static bool has_file_extension(const char* path, const char* extension) {
|
||||
@ -26,7 +28,7 @@ static bool has_file_extension(const char* path, const char* extension) {
|
||||
}
|
||||
|
||||
for (int i = (int)postfix_len - 1; i >= 0; i--) {
|
||||
if (tolower(path[base_len - postfix_len + i]) != extension[i]) {
|
||||
if (tolower(path[base_len - postfix_len + i]) != tolower(extension[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -34,12 +36,9 @@ static bool has_file_extension(const char* path, const char* extension) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool is_image_file(const char* filename) {
|
||||
return has_file_extension(filename, ".jpg") ||
|
||||
has_file_extension(filename, ".png") ||
|
||||
has_file_extension(filename, ".jpeg") ||
|
||||
has_file_extension(filename, ".svg") ||
|
||||
has_file_extension(filename, ".bmp");
|
||||
static bool is_supported_image_file(const char* filename) {
|
||||
// Currently only the PNG library is built into Tactility
|
||||
return has_file_extension(filename, ".png");
|
||||
}
|
||||
|
||||
// region Views
|
||||
@ -62,6 +61,44 @@ static void on_exit_app_pressed(TT_UNUSED lv_event_t* event) {
|
||||
loader_stop_app();
|
||||
}
|
||||
|
||||
static void view_file(const char* path, const char* filename) {
|
||||
size_t path_len = strlen(path);
|
||||
size_t filename_len = strlen(filename);
|
||||
char* filepath = malloc(path_len + filename_len + 2);
|
||||
sprintf(filepath, "%s/%s", path, filename);
|
||||
|
||||
// For PC we need to make the path relative to the current work directory,
|
||||
// because that's how LVGL maps its 'drive letter' to the file system.
|
||||
char* processed_filepath;
|
||||
if (tt_get_platform() == PlatformPc) {
|
||||
char cwd[PATH_MAX];
|
||||
if (getcwd(cwd, sizeof(cwd)) == NULL) {
|
||||
TT_LOG_E(TAG, "Failed to get current working directory");
|
||||
return;
|
||||
}
|
||||
if (!strstr(filepath, cwd)) {
|
||||
TT_LOG_E(TAG, "Can only work with files in working directory %s", cwd);
|
||||
return;
|
||||
}
|
||||
char* substr = filepath + strlen(cwd);
|
||||
processed_filepath = substr;
|
||||
} else {
|
||||
processed_filepath = filepath;
|
||||
}
|
||||
|
||||
TT_LOG_I(TAG, "Clicked %s", filepath);
|
||||
|
||||
if (is_supported_image_file(filename)) {
|
||||
Bundle bundle = tt_bundle_alloc();
|
||||
tt_bundle_put_string(bundle, IMAGE_VIEWER_FILE_ARGUMENT, processed_filepath);
|
||||
loader_start_app("image_viewer", false, bundle);
|
||||
} else {
|
||||
TT_LOG_W(TAG, "opening files of this type is not supported");
|
||||
}
|
||||
|
||||
free(filepath);
|
||||
}
|
||||
|
||||
static void on_file_pressed(lv_event_t* event) {
|
||||
lv_event_code_t code = lv_event_get_code(event);
|
||||
if (code == LV_EVENT_CLICKED) {
|
||||
@ -80,10 +117,12 @@ static void on_file_pressed(lv_event_t* event) {
|
||||
TT_LOG_W(TAG, "opening links is not supported");
|
||||
break;
|
||||
case TT_DT_REG:
|
||||
TT_LOG_W(TAG, "opening files is not supported");
|
||||
view_file(files_data->current_path, dir_entry->d_name);
|
||||
break;
|
||||
default:
|
||||
TT_LOG_W(TAG, "file type %d is not supported", dir_entry->d_type);
|
||||
// Assume it's a file
|
||||
// TODO: Find a better way to identify a file
|
||||
view_file(files_data->current_path, dir_entry->d_name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -95,7 +134,7 @@ static void create_file_widget(FilesData* files_data, lv_obj_t* parent, struct d
|
||||
const char* symbol;
|
||||
if (dir_entry->d_type == TT_DT_DIR) {
|
||||
symbol = LV_SYMBOL_DIRECTORY;
|
||||
} else if (is_image_file(dir_entry->d_name)) {
|
||||
} else if (is_supported_image_file(dir_entry->d_name)) {
|
||||
symbol = LV_SYMBOL_IMAGE;
|
||||
} else if (dir_entry->d_type == TT_DT_LNK) {
|
||||
symbol = LV_SYMBOL_LOOP;
|
||||
@ -136,7 +175,19 @@ static void on_show(App app, lv_obj_t* parent) {
|
||||
|
||||
static void on_start(App app) {
|
||||
FilesData* data = files_data_alloc();
|
||||
files_data_set_entries_for_path(data, "/");
|
||||
// PC platform is bound to current work directory because of the LVGL file system mapping
|
||||
if (tt_get_platform() == PlatformPc) {
|
||||
char cwd[PATH_MAX];
|
||||
if (getcwd(cwd, sizeof(cwd)) != NULL) {
|
||||
files_data_set_entries_for_path(data, cwd);
|
||||
} else {
|
||||
TT_LOG_E(TAG, "Failed to get current work directory files");
|
||||
files_data_set_entries_for_path(data, "/");
|
||||
}
|
||||
} else {
|
||||
files_data_set_entries_for_path(data, "/");
|
||||
}
|
||||
|
||||
tt_app_set_data(app, data);
|
||||
}
|
||||
|
||||
|
||||
@ -5,22 +5,20 @@
|
||||
|
||||
#define TAG "files_app"
|
||||
|
||||
static bool get_child_path(char* base_path, const char* child_path, char* out_path, size_t out_size) {
|
||||
static bool get_child_path(char* base_path, const char* child_path, char* out_path, size_t max_chars) {
|
||||
size_t current_path_length = strlen(base_path);
|
||||
size_t added_path_length = strlen(child_path);
|
||||
size_t total_path_length = current_path_length + added_path_length + 1; // two paths with `/`
|
||||
|
||||
if (total_path_length >= out_size) {
|
||||
if (total_path_length >= max_chars) {
|
||||
TT_LOG_E(TAG, "Path limit reached (%d chars)", MAX_PATH_LENGTH);
|
||||
return false;
|
||||
} else {
|
||||
memcpy(out_path, base_path, current_path_length);
|
||||
// Postfix with "/" when the current path isn't "/"
|
||||
if (current_path_length != 1) {
|
||||
out_path[current_path_length] = '/';
|
||||
strcpy(&out_path[current_path_length + 1], child_path);
|
||||
sprintf(out_path, "%s/%s", base_path, child_path);
|
||||
} else {
|
||||
strcpy(&out_path[current_path_length], child_path);
|
||||
sprintf(out_path, "/%s", child_path);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -101,7 +99,7 @@ bool files_data_set_entries_for_path(FilesData* data, const char* path) {
|
||||
}
|
||||
|
||||
bool files_data_set_entries_for_child_path(FilesData* data, const char* child_path) {
|
||||
char new_absolute_path[MAX_PATH_LENGTH];
|
||||
char new_absolute_path[MAX_PATH_LENGTH + 1];
|
||||
if (get_child_path(data->current_path, child_path, new_absolute_path, MAX_PATH_LENGTH)) {
|
||||
TT_LOG_I(TAG, "Navigating from %s to %s", data->current_path, new_absolute_path);
|
||||
return files_data_set_entries_for_path(data, new_absolute_path);
|
||||
|
||||
44
tactility/src/apps/system/image_viewer/image_viewer.c
Normal file
44
tactility/src/apps/system/image_viewer/image_viewer.c
Normal file
@ -0,0 +1,44 @@
|
||||
#include "image_viewer.h"
|
||||
#include "log.h"
|
||||
#include "lvgl.h"
|
||||
#include "ui/style.h"
|
||||
#include "ui/toolbar.h"
|
||||
#include <tactility_core.h>
|
||||
|
||||
#define TAG "image_viewer"
|
||||
|
||||
static void app_show(App app, lv_obj_t* parent) {
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
tt_toolbar_create_for_app(parent, app);
|
||||
|
||||
lv_obj_t* wrapper = lv_obj_create(parent);
|
||||
lv_obj_set_width(wrapper, LV_PCT(100));
|
||||
lv_obj_set_flex_grow(wrapper, 1);
|
||||
lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN);
|
||||
tt_lv_obj_set_style_no_padding(wrapper);
|
||||
tt_lv_obj_set_style_bg_invisible(wrapper);
|
||||
|
||||
lv_obj_t* image = lv_img_create(wrapper);
|
||||
lv_obj_align(image, LV_ALIGN_CENTER, 0, 0);
|
||||
Bundle bundle = tt_app_get_parameters(app);
|
||||
if (tt_bundle_has_string(bundle, IMAGE_VIEWER_FILE_ARGUMENT)) {
|
||||
const char* file = tt_bundle_get_string(bundle, IMAGE_VIEWER_FILE_ARGUMENT);
|
||||
char* prefixed_path = malloc(strlen(file) + 3);
|
||||
tt_assert(prefixed_path != NULL);
|
||||
sprintf(prefixed_path, "A:%s", file);
|
||||
TT_LOG_I(TAG, "Opening %s", prefixed_path);
|
||||
lv_img_set_src(image, prefixed_path);
|
||||
free(prefixed_path);
|
||||
}
|
||||
}
|
||||
|
||||
const AppManifest image_viewer_app = {
|
||||
.id = "image_viewer",
|
||||
.name = "Image Viewer",
|
||||
.icon = NULL,
|
||||
.type = AppTypeDesktop,
|
||||
.on_start = NULL,
|
||||
.on_stop = NULL,
|
||||
.on_show = &app_show,
|
||||
.on_hide = NULL
|
||||
};
|
||||
11
tactility/src/apps/system/image_viewer/image_viewer.h
Normal file
11
tactility/src/apps/system/image_viewer/image_viewer.h
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define IMAGE_VIEWER_FILE_ARGUMENT "file"
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -35,6 +35,7 @@ static const ServiceManifest* const system_services[] = {
|
||||
extern const AppManifest desktop_app;
|
||||
extern const AppManifest display_app;
|
||||
extern const AppManifest files_app;
|
||||
extern const AppManifest image_viewer_app;
|
||||
extern const AppManifest power_app;
|
||||
extern const AppManifest settings_app;
|
||||
extern const AppManifest system_info_app;
|
||||
@ -51,6 +52,7 @@ static const AppManifest* const system_apps[] = {
|
||||
&desktop_app,
|
||||
&display_app,
|
||||
&files_app,
|
||||
&image_viewer_app,
|
||||
&settings_app,
|
||||
&system_info_app,
|
||||
&wifi_connect_app,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user