| #include <cmath> |
| #include <cstring> |
| #include <cstdio> |
| #include <cstdlib> |
| #include <list> |
| |
| #include "launcher.hpp" |
| #include "assets.hpp" |
| |
| #include "engine/api_private.hpp" |
| #include "graphics/color.hpp" |
| |
| #include "executable.hpp" |
| #include "metadata.hpp" |
| #include "dialog.hpp" |
| |
| #include "theme.hpp" |
| |
| #include "credits.hpp" |
| |
| using namespace blit; |
| |
| struct PathSave { |
| char last_path[512]; |
| }; |
| |
| static const int path_save_slot = 256; |
| |
| static Dialog dialog; |
| |
| const Font launcher_font(font8x8); |
| |
| constexpr uint32_t qspi_flash_sector_size = 64 * 1024; |
| |
| static Screen current_screen = Screen::main; |
| |
| static bool show_fps = false; |
| static bool sd_detected = true; |
| static Vec2 file_list_scroll_offset(10.0f, 0.0f); |
| static Point game_info_offset(120, 20); |
| static Point game_actions_offset(game_info_offset.x + 128 + 8, 28); |
| static float directory_list_scroll_offset = 0.0f; |
| |
| static std::vector<GameInfo> game_list; |
| static std::list<DirectoryInfo> directory_list; |
| static std::list<DirectoryInfo>::iterator current_directory; |
| |
| static SortBy file_sort = SortBy::name; |
| |
| static GameInfo selected_game; |
| static BlitGameMetadata selected_game_metadata; |
| |
| static Surface *spritesheet; |
| static Surface *screenshot; |
| |
| static AutoRepeat ar_button_up(250, 600); |
| static AutoRepeat ar_button_down(250, 600); |
| static AutoRepeat ar_button_left(0, 0); |
| static AutoRepeat ar_button_right(0, 0); |
| |
| #ifndef PICO_BUILD |
| static uint8_t screenshot_buf[320 * 240 * 3]; |
| #endif |
| |
| static int calc_num_blocks(uint32_t size) { |
| return (size - 1) / qspi_flash_sector_size + 1; |
| } |
| |
| // insertion sort |
| template <class Iterator, class Compare> |
| static void insertion_sort(Iterator first, Iterator last, Compare comp) { |
| if(last - first < 2) |
| return; |
| |
| for(auto it = first + 1; it != last; ++it) { |
| auto temp = it; |
| |
| while(temp != first && comp(*temp, *(temp - 1))) { |
| std::swap(*temp, *(temp - 1)); |
| --temp; |
| } |
| } |
| } |
| |
| static bool parse_file_metadata(const std::string &filename, BlitGameMetadata &metadata, bool unpack_images = false) { |
| blit::File f(filename); |
| |
| if(!f.is_open()) |
| return false; |
| |
| uint32_t offset = 0; |
| |
| uint8_t buf[sizeof(BlitGameHeader)]; |
| auto read = f.read(offset, sizeof(buf), (char *)&buf); |
| |
| // skip relocation data |
| if(memcmp(buf, "RELO", 4) == 0) { |
| uint32_t num_relocs; |
| f.read(4, 4, (char *)&num_relocs); |
| |
| offset = num_relocs * 4 + 8; |
| // re-read header |
| read = f.read(offset, sizeof(buf), (char *)&buf); |
| } |
| |
| // game header - skip to metadata |
| if(memcmp(buf, "BLITMETA", 8) != 0) { |
| auto &header = *(BlitGameHeader *)buf; |
| if(read == sizeof(BlitGameHeader) && header.magic == blit_game_magic) { |
| offset += (header.end & 0x1FFFFFF); |
| read = f.read(offset, 10, (char *)buf); |
| } |
| } |
| |
| if(read >= 10 && memcmp(buf, "BLITMETA", 8) == 0) { |
| // don't bother reading the whole thing if we don't want the images |
| auto metadata_len = unpack_images ? *reinterpret_cast<uint16_t *>(buf + 8) : sizeof(RawMetadata); |
| |
| uint8_t metadata_buf[0xFFFF]; |
| f.read(offset + 10, metadata_len, (char *)metadata_buf); |
| |
| parse_metadata(reinterpret_cast<char *>(metadata_buf), metadata_len, metadata, unpack_images); |
| |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static void sort_file_list() { |
| using Iterator = std::vector<GameInfo>::iterator; |
| using Compare = bool(const GameInfo &, const GameInfo &); |
| |
| if (file_sort == SortBy::name) { |
| // Sort by filename |
| insertion_sort<Iterator, Compare>(game_list.begin(), game_list.end(), [](const auto &a, const auto &b) { return a.title < b.title; }); |
| } |
| |
| if (file_sort == SortBy::size) { |
| // Sort by filesize |
| insertion_sort<Iterator, Compare>(game_list.begin(), game_list.end(), [](const auto &a, const auto &b) { return a.size < b.size; }); |
| } |
| } |
| |
| static void load_file_list(const std::string &directory) { |
| |
| game_list.clear(); |
| |
| auto files = list_files(directory, [&](auto &file) { |
| if(file.flags & FileFlags::directory) |
| return false; |
| |
| if(file.name[0] == '.') // hidden file |
| return false; |
| |
| auto path = directory == "/" ? file.name : directory + "/" + file.name; |
| auto res = api.can_launch(path.c_str()); |
| |
| if(res == CanLaunchResult::UnknownType) { |
| // special case for images |
| auto last_dot = file.name.find_last_of('.'); |
| |
| auto ext = last_dot == std::string::npos ? "" : file.name.substr(last_dot + 1); |
| |
| for(auto &c : ext) |
| c = tolower(c); |
| |
| if(ext == "bmp" || ext == "blim") |
| return true; |
| } |
| |
| // will filter incompatible later |
| return res == CanLaunchResult::Success || res == CanLaunchResult::IncompatibleBlit; |
| }); |
| |
| game_list.reserve(files.size()); // worst case |
| |
| for(auto &file : files) { |
| auto last_dot = file.name.find_last_of('.'); |
| |
| auto ext = last_dot == std::string::npos ? "" : file.name.substr(last_dot + 1); |
| |
| for(auto &c : ext) |
| c = tolower(c); |
| |
| GameInfo game; |
| game.title = file.name.substr(0, file.name.length() - ext.length() - 1); |
| game.filename = directory == "/" ? file.name : directory + "/" + file.name; |
| game.size = file.size; |
| |
| if(ext == "blit") { |
| game.type = GameType::game; |
| game.can_launch = api.can_launch(game.filename.c_str()) == CanLaunchResult::Success; |
| |
| // check for metadata |
| BlitGameMetadata meta; |
| if(parse_file_metadata(game.filename, meta)) |
| game.title = meta.title; |
| |
| } else if(ext == "bmp" || ext == "blim") { |
| game.type = GameType::screenshot; |
| |
| // Special case check for an installed handler for these types, ie: a sprite editor |
| game.can_launch = api.get_type_handler_metadata && api.get_type_handler_metadata(ext.c_str()); |
| } else { |
| // it's launch-able so there must be a handler |
| game.type = GameType::file; |
| strncpy(game.ext, ext.c_str(), 5); |
| game.ext[4] = 0; |
| game.can_launch = true; |
| |
| // check for a metadata file (fall back to handler's metadata) |
| BlitGameMetadata meta; |
| auto meta_filename = game.filename + ".blmeta"; |
| if(parse_file_metadata(meta_filename, meta)) |
| game.title = meta.title; |
| } |
| |
| game_list.push_back(game); |
| } |
| |
| int total_items = (int)game_list.size(); |
| if(selected_menu_item >= total_items) |
| selected_menu_item = std::max(0, total_items - 1); |
| |
| // probably doesn't do anything... |
| game_list.shrink_to_fit(); |
| |
| sort_file_list(); |
| } |
| |
| static void load_directory_list(const std::string &directory) { |
| directory_list.clear(); |
| |
| auto dir_filter = [](const FileInfo &info){ |
| if(!(info.flags & FileFlags::directory)) |
| return false; |
| |
| if(info.name.compare("System Volume Information") == 0 || info.name[0] == '.') |
| return false; |
| |
| return true; |
| }; |
| |
| for(auto &folder : ::list_files(directory, dir_filter)) |
| directory_list.push_back({folder.name, 0, 0}); |
| |
| directory_list.sort([](const auto &a, const auto &b) { return a.name > b.name; }); |
| |
| directory_list.push_front({"/", 0, 0}); |
| directory_list.push_front({"flash:", 0, 0}); |
| |
| // measure positions |
| int x = 0; |
| for(auto &dir : directory_list) { |
| dir.x = x; |
| dir.w = screen.measure_text(dir.name == "/" ? "ROOT" : dir.name, launcher_font).w; |
| |
| x += dir.w + 10; |
| } |
| } |
| |
| static void load_current_game_metadata() { |
| bool loaded = false; |
| |
| if(!game_list.empty()) { |
| selected_game = game_list[selected_menu_item]; |
| |
| if(selected_game.type == GameType::file) { |
| // not a .blit - look for a metadata file |
| auto meta_filename = selected_game.filename + ".blmeta"; |
| if(!parse_file_metadata(meta_filename, selected_game_metadata, true)) { |
| // fallback to handler metadata/placeholders |
| auto handler_meta = (char *)api.get_type_handler_metadata(selected_game.ext); |
| auto len = *reinterpret_cast<uint16_t *>(handler_meta + 8); |
| parse_metadata(handler_meta + 10, len, selected_game_metadata, true); |
| |
| selected_game_metadata.description = "Launches with: " + selected_game_metadata.title; |
| selected_game_metadata.title = selected_game.title; |
| selected_game_metadata.author = ""; |
| selected_game_metadata.version = ""; |
| } |
| loaded = true; |
| } else |
| loaded = parse_file_metadata(selected_game.filename, selected_game_metadata, true); |
| } |
| |
| #ifndef PICO_BUILD |
| if(selected_game.type == GameType::screenshot) { |
| // Free any old buffers |
| if(screenshot) { |
| delete[] screenshot->palette; |
| delete screenshot; |
| screenshot = nullptr; |
| } |
| // Load the new screenshot |
| screenshot = Surface::load(selected_game.filename, screenshot_buf, sizeof(screenshot_buf)); |
| } else { |
| // Not showing a screenshot, free the buffers |
| if(screenshot) { |
| delete[] screenshot->palette; |
| delete screenshot; |
| screenshot = nullptr; |
| } |
| } |
| #endif |
| |
| // no valid metadata, reset |
| if(!loaded) { |
| selected_game_metadata.free_surfaces(); |
| selected_game_metadata = BlitGameMetadata(); |
| } |
| } |
| |
| static bool launch_current_game() { |
| // save last file launched |
| PathSave save{}; |
| strncpy(save.last_path, selected_game.filename.c_str(), sizeof(save.last_path) - 1); |
| write_save(save, path_save_slot); |
| |
| if(!api.launch) |
| return false; |
| |
| return api.launch(selected_game.filename.c_str()); |
| } |
| |
| static void delete_current_game() { |
| dialog.show("Confirm", "Really delete " + selected_game.title + "?", [](bool yes){ |
| if(yes) { |
| if(selected_game.filename.compare(0, 7, "flash:/") == 0) |
| api.erase_game(std::stoi(selected_game.filename.substr(7)) * qspi_flash_sector_size); |
| |
| ::remove_file(selected_game.filename); |
| |
| load_file_list(current_directory->name); |
| load_current_game_metadata(); |
| } |
| }); |
| } |
| |
| static void init_lists() { |
| load_directory_list("/"); |
| current_directory = directory_list.begin(); |
| |
| load_file_list(current_directory->name); |
| |
| load_current_game_metadata(); |
| } |
| |
| static void scan_flash() { |
| if(api.list_installed_games) { |
| api.list_installed_games([](const uint8_t *ptr, uint32_t block, uint32_t size){ |
| File::add_buffer_file("flash:/" + std::to_string(block) + ".blit", ptr, size); |
| }); |
| } |
| } |
| |
| void init() { |
| set_screen_mode(ScreenMode::hires); |
| screen.clear(); |
| |
| // shrink the filename column on narrower screens |
| if(screen.bounds.w < game_actions_offset.x + 24) { |
| int diff = (game_actions_offset.x + 24) - screen.bounds.w; |
| game_info_offset.x -= diff; |
| game_actions_offset.x -= diff; |
| } |
| |
| selected_menu_item = 0; |
| |
| init_theme(); |
| |
| spritesheet = Surface::load(sprites); |
| |
| scan_flash(); |
| init_lists(); |
| |
| // restore previously selected file |
| PathSave save; |
| save.last_path[0] = 0; |
| |
| if(read_save(save, path_save_slot)) { |
| auto path = std::string_view(save.last_path); |
| auto slash = path.find_first_of('/'); |
| |
| std::string_view dir; |
| |
| if(slash == std::string_view::npos) |
| dir = "/"; |
| else |
| dir = path.substr(0, slash); |
| |
| // select dir |
| for(auto it = directory_list.begin(); it != directory_list.end(); ++it) { |
| if(it->name == dir) { |
| if(it != current_directory) |
| load_file_list(it->name); |
| |
| current_directory = it; |
| break; |
| } |
| } |
| |
| // select file |
| for(auto it = game_list.begin(); it != game_list.end(); ++it) { |
| if(it->filename == path) { |
| selected_menu_item = it - game_list.begin(); |
| break; |
| } |
| } |
| |
| load_current_game_metadata(); |
| } |
| |
| credits::prepare(); |
| } |
| |
| static void swoosh(uint32_t time, float t1, float t2, float s1, float s2, int t0, int offset_y=120, int size=60, int alpha=64) { |
| constexpr int swoosh_resolution = 32; |
| int w = (screen.bounds.w + swoosh_resolution - 1) / swoosh_resolution; |
| |
| for(auto x = 0; x < w; x++) { |
| float t_a = (x / s1) + float(time + t0) / t1; |
| float t_b = (x / s2) + float(time + t0) / t2; |
| |
| int y1 = sinf(t_a) * size; |
| int y2 = sinf(t_b) * size; |
| |
| if(y1 > y2) std::swap(y1, y2); |
| |
| y1 += offset_y; |
| y2 += offset_y + 2; |
| |
| int range = y2 - y1; |
| |
| for(auto y = 0; y <= range; y++) { |
| if(y > range / 2) { |
| screen.pen.a = alpha - (alpha * y / range); |
| } else { |
| screen.pen.a = alpha * y / range; |
| } |
| // This is an optimisation, not an aesthetic choice! |
| screen.h_span(Point(x * swoosh_resolution, y1 + y), swoosh_resolution); |
| } |
| } |
| } |
| |
| static void render_fps(uint32_t us_start) { |
| if(!show_fps) return; |
| // draw FPS meter |
| uint32_t us_end = now_us(); |
| uint32_t us_elapsed = us_diff(us_start, us_end); |
| screen.mask = nullptr; |
| |
| screen.pen = Pen(0, 0, 0); |
| screen.rectangle(Rect(Point(0, screen.bounds.h - 14), Size(game_info_offset.x - 10, 14))); |
| |
| screen.pen = Pen(255, 0, 0); |
| for (unsigned int i = 0; i < us_elapsed / 1000; i++) { |
| screen.pen = Pen(i * 5, 255 - (i * 5), 0); |
| screen.rectangle(Rect(i * 3 + 1, screen.bounds.h - 3, 2, 2)); |
| } |
| |
| screen.pen = Pen(255, 255, 255); |
| screen.text(std::to_string(us_elapsed), minimal_font, Point(0, screen.bounds.h - 12)); |
| } |
| |
| static void render_directory_list() { |
| // adjust alignment rect for vertical spacing |
| const int text_align_height = ROW_HEIGHT + launcher_font.spacing_y; |
| |
| // list folders |
| if(directory_list.empty()) |
| return; |
| |
| int width = screen.bounds.w - game_info_offset.x - 10; |
| |
| // darken behind if showing screenshot |
| if(screenshot) { |
| screen.pen = theme.color_background; |
| screen.pen.a = 150; |
| screen.rectangle(Rect(game_info_offset.x - 10, 0, width + 20, 20)); |
| } |
| |
| screen.clip = Rect(game_info_offset.x, 5, width, text_align_height); |
| |
| for(auto &directory : directory_list) { |
| if(directory.name == current_directory->name) |
| screen.pen = theme.color_accent; |
| else |
| screen.pen = theme.color_text; |
| |
| int x = 120 + (width / 2) + directory.x - directory_list_scroll_offset; |
| screen.text(directory.name == "/" ? "ROOT" : directory.name, launcher_font, Rect(x, 5, width, text_align_height), true, TextAlign::center_v); |
| } |
| |
| screen.clip = Rect(Point(0, 0), screen.bounds); |
| } |
| |
| static void render_file_list() { |
| // adjust alignment rect for vertical spacing |
| const int text_align_height = ROW_HEIGHT + launcher_font.spacing_y; |
| |
| if(game_list.empty()) |
| return; |
| |
| // background |
| if(screenshot) { |
| // darken if showing screenshot |
| screen.pen = theme.color_background; |
| screen.pen.a = 150; |
| } else |
| screen.pen = theme.color_overlay; |
| |
| screen.rectangle(Rect(0, 0, game_info_offset.x - 10, screen.bounds.h)); |
| |
| screen.clip = Rect(0, 0, game_info_offset.x - 20, screen.bounds.h); |
| int title_w = screen.clip.w - file_list_scroll_offset.x; |
| |
| int y = (screen.bounds.h / 2) - 5 - file_list_scroll_offset.y; |
| int i = 0; |
| |
| for(auto &file : game_list) { |
| if(i++ == selected_menu_item) |
| screen.pen = theme.color_accent; |
| else |
| screen.pen = theme.color_text; |
| |
| screen.text(file.title, launcher_font, Rect(file_list_scroll_offset.x, y, title_w, text_align_height), true, TextAlign::center_v); |
| y += ROW_HEIGHT; |
| } |
| screen.clip = Rect(Point(0, 0), screen.bounds); |
| } |
| |
| static void render_screenshot() { |
| if(screenshot->bounds.w == screen.bounds.w) { |
| // full screen image |
| screen.blit(screenshot, Rect(Point(0, 0), screenshot->bounds), Point(0, 0)); |
| } else if(screenshot->bounds == Size(128, 128)) { |
| // standard spritesheet size, show in info column |
| screen.pen = Pen(0, 0, 0, 255); |
| screen.rectangle(Rect(game_info_offset, Size(128, 128))); |
| screen.blit(screenshot, Rect(Point(0, 0), screenshot->bounds), game_info_offset); |
| } else { |
| screen.stretch_blit(screenshot, Rect(Point(0, 0), screenshot->bounds), Rect(Point(0, 0), screen.bounds)); |
| } |
| } |
| |
| static void render_game_info() { |
| // run game / launch file |
| if(selected_game.can_launch) { |
| screen.sprite(1, Point(game_actions_offset.x, game_actions_offset.y + 12)); |
| screen.sprite(0, Point(game_actions_offset.x + 10, game_actions_offset.y + 12), SpriteTransform::R90); |
| } |
| |
| // game info |
| if(selected_game_metadata.splash) |
| screen.blit(selected_game_metadata.splash, Rect(Point(0, 0), selected_game_metadata.splash->bounds), game_info_offset); |
| |
| screen.pen = theme.color_accent; |
| std::string wrapped_title = screen.wrap_text(selected_game_metadata.title, screen.bounds.w - game_info_offset.x - 10, launcher_font); |
| |
| Size title_size = screen.measure_text(wrapped_title, launcher_font); |
| screen.text(wrapped_title, launcher_font, Point(game_info_offset.x, game_info_offset.y + 104)); |
| |
| Rect desc_rect(game_info_offset.x, game_info_offset.y + 108 + title_size.h, screen.bounds.w - game_info_offset.x - 10, 64); |
| |
| screen.pen = theme.color_text; |
| std::string wrapped_desc = screen.wrap_text(selected_game_metadata.description, desc_rect.w, launcher_font); |
| screen.text(wrapped_desc, launcher_font, desc_rect); |
| |
| screen.text(selected_game_metadata.author, minimal_font, Point(game_info_offset.x, screen.bounds.h - 32)); |
| screen.text(selected_game_metadata.version, minimal_font, Point(game_info_offset.x, screen.bounds.h - 24)); |
| |
| int num_blocks = calc_num_blocks(selected_game.size); |
| char buf[20]; |
| snprintf(buf, 20, "%i block%s", num_blocks, num_blocks == 1 ? "" : "s"); |
| screen.text(buf, minimal_font, Point(game_info_offset.x, screen.bounds.h - 16)); |
| |
| if(!selected_game.can_launch) { |
| screen.pen = {255, 0, 0}; |
| screen.text("INCOMPATIBLE", minimal_font, Point(screen.bounds.w - 10, screen.bounds.h - 16), true, TextAlign::top_right); |
| } |
| } |
| |
| void render(uint32_t time) { |
| uint32_t us_start = now_us(); |
| screen.sprites = spritesheet; |
| |
| screen.pen = theme.color_background; |
| screen.clear(); |
| |
| // display background swoosh if not displaying a screenshot or the credits |
| if(current_screen != Screen::screenshot && current_screen != Screen::credits && selected_game.type != GameType::screenshot) { |
| screen.pen = Pen(255, 255, 255); |
| swoosh(time, 5100.0f, 3900.0f, 1900.0f, 900.0f, 3500); |
| screen.pen = theme.color_accent; |
| swoosh(time, 5000.0f, 3000.0f, 1000.0f, 1000.0f, 0); |
| screen.pen = Pen(~theme.color_accent.r, ~theme.color_accent.g, ~theme.color_accent.b); |
| swoosh(time, 5100.0f, 3900.0f, 900.0f, 1100.0f, 5000); |
| } |
| |
| // display image preview |
| if(!game_list.empty() && screenshot) |
| render_screenshot(); |
| |
| // don't display lists over fullscreen screenshot |
| if(current_screen != Screen::screenshot) { |
| render_directory_list(); |
| render_file_list(); |
| } |
| |
| // current file info/actions |
| if(!game_list.empty()) { |
| // delete |
| screen.sprite(2, Point(game_actions_offset.x, game_actions_offset.y)); |
| screen.sprite(0, Point(game_actions_offset.x + 10, game_actions_offset.y)); |
| |
| if(selected_game.type == GameType::screenshot) { |
| if(screenshot && screenshot->bounds == Size(128, 128)) { |
| if(selected_game.can_launch){ |
| // edit (in sprite editor, presumably) |
| screen.sprite(1, Point(game_actions_offset.x, game_actions_offset.y + 12)); |
| screen.sprite(0, Point(game_actions_offset.x + 10, game_actions_offset.y + 12), SpriteTransform::R90); |
| } |
| } else if (current_screen == Screen::screenshot) { |
| // exit fullscreen |
| screen.sprite(5, Point(game_actions_offset.x, game_actions_offset.y + 12)); |
| screen.sprite(0, Point(game_actions_offset.x + 10, game_actions_offset.y + 12), SpriteTransform::R180); |
| } else if(screenshot) { |
| // view screenshot fullscreen |
| screen.sprite(4, Point(game_actions_offset.x, game_actions_offset.y + 12)); |
| screen.sprite(0, Point(game_actions_offset.x + 10, game_actions_offset.y + 12), SpriteTransform::R90); |
| } |
| } else { |
| render_game_info(); |
| } |
| } else { |
| screen.pen = theme.color_text; |
| |
| if(current_directory->name != "flash:" && !blit::is_storage_available()) |
| screen.text("No SD Card\nDetected.", launcher_font, Point(screen.bounds.w / 2, screen.bounds.h / 2), true, TextAlign::center_center); |
| else |
| screen.text("No Games Found.", launcher_font, Point(screen.bounds.w / 2, screen.bounds.h / 2), true, TextAlign::center_center); |
| } |
| |
| if (current_screen == Screen::credits) { |
| credits::render(); |
| } |
| |
| //progress.draw(); |
| dialog.draw(); |
| render_fps(us_start); |
| } |
| |
| void update(uint32_t time) { |
| |
| if(blit::is_storage_available() != sd_detected) { |
| init_lists(); |
| sd_detected = blit::is_storage_available(); |
| } |
| |
| bool button_a = buttons.released & Button::A; |
| bool button_b = buttons.pressed & Button::B; |
| bool button_x = buttons.pressed & Button::X; |
| bool button_y = buttons.pressed & Button::Y; |
| bool button_menu = buttons.pressed & Button::MENU; |
| bool button_up = ar_button_up.next(time, buttons.state & Button::DPAD_UP || joystick.y < -0.2f); |
| bool button_down = ar_button_down.next(time, buttons.state & Button::DPAD_DOWN || joystick.y > 0.2f); |
| bool button_left = ar_button_left.next(time, buttons.state & Button::DPAD_LEFT || joystick.x < -0.5f); |
| bool button_right = ar_button_right.next(time, buttons.state & Button::DPAD_RIGHT || joystick.x > 0.5f); |
| |
| if(dialog.update()) |
| return; |
| |
| // update/close credits |
| if (current_screen == Screen::credits) { |
| credits::update(time); |
| |
| if (button_menu) { |
| current_screen = Screen::main; |
| } |
| |
| if(button_y) { |
| show_fps = !show_fps; |
| } |
| |
| return; |
| } |
| |
| // display credits |
| if (button_menu) { |
| credits::reset_scrolling(); |
| current_screen = Screen::credits; |
| } |
| |
| // scroll through file list |
| int total_items = (int)game_list.size(); |
| |
| auto old_menu_item = selected_menu_item; |
| |
| if(button_up) { |
| selected_menu_item--; |
| if(selected_menu_item < 0) { |
| selected_menu_item = total_items - 1; |
| } |
| } |
| |
| if(button_down) { |
| selected_menu_item++; |
| if(selected_menu_item > total_items - 1) { |
| selected_menu_item = 0; |
| } |
| } |
| |
| if(current_screen == Screen::screenshot) { |
| // b to exit full screen screenshot view |
| if(button_b) { |
| current_screen = Screen::main; |
| } |
| } else { |
| // scroll through directories |
| if(button_left) { |
| if(current_directory == directory_list.begin()) |
| current_directory = --directory_list.end(); |
| else |
| --current_directory; |
| } |
| |
| if(button_right) { |
| current_directory++; |
| if(current_directory == directory_list.end()) { |
| current_directory = directory_list.begin(); |
| } |
| } |
| |
| // reload file list if dir changed |
| if(button_left || button_right) { |
| load_file_list(current_directory->name); |
| |
| selected_menu_item = 0; |
| old_menu_item = -1; |
| } |
| |
| // toggle sort mode |
| if (button_y) { |
| file_sort = file_sort == SortBy::name ? SortBy::size : SortBy::name; |
| sort_file_list(); |
| } |
| } |
| |
| // scroll list towards selected item |
| file_list_scroll_offset.y += ((selected_menu_item * ROW_HEIGHT) - file_list_scroll_offset.y) / 5.0f; |
| |
| directory_list_scroll_offset += (current_directory->x + current_directory->w / 2 - directory_list_scroll_offset) / 5.0f; |
| |
| if(game_list.empty()) |
| return; |
| |
| // load metadata for selected item |
| if(selected_menu_item != old_menu_item) { |
| load_current_game_metadata(); |
| } |
| |
| // paranoid bail out if you're browsing screenshots full screen and come across a game |
| if(selected_game.type != GameType::screenshot && current_screen == Screen::screenshot) { |
| current_screen = Screen::main; |
| } |
| |
| // delete current game / screenshot |
| if(button_x) { |
| delete_current_game(); |
| } |
| |
| // launch game / show screenshot fullscreen |
| if(button_a) { |
| if(selected_game.type == GameType::screenshot && !selected_game.can_launch) { |
| current_screen = Screen::screenshot; |
| } else if(selected_game.can_launch) { |
| if(!launch_current_game()) |
| dialog.show("Error!", "Failed to launch " + selected_game.filename, [](bool){}, false); |
| } |
| } |
| } |