diff --git a/rpcs3/Emu/RSX/Overlays/overlay_controls.h b/rpcs3/Emu/RSX/Overlays/overlay_controls.h index a456eed7ff..c17458f02e 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_controls.h +++ b/rpcs3/Emu/RSX/Overlays/overlay_controls.h @@ -129,289 +129,15 @@ namespace rsx std::vector glyph_data; bool initialized = false; - font(const char *ttf_name, f32 size) - { - // Init glyph - std::vector bytes; - std::vector font_dirs; - std::vector fallback_fonts; -#ifdef _WIN32 - font_dirs.push_back("C:/Windows/Fonts/"); - fallback_fonts.push_back("C:/Windows/Fonts/Arial.ttf"); -#else - char *home = getenv("HOME"); - if (home == nullptr) - home = getpwuid(getuid())->pw_dir; + font(const char* ttf_name, f32 size); - font_dirs.push_back(home); - if (home[font_dirs[0].length() - 1] == '/') - font_dirs[0] += ".fonts/"; - else - font_dirs[0] += "/.fonts/"; + stbtt_aligned_quad get_char(char c, f32& x_advance, f32& y_advance); - fallback_fonts.push_back("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"); //ubuntu - fallback_fonts.push_back("/usr/share/fonts/TTF/DejaVuSans.ttf"); //arch -#endif - // Search dev_flash for the font too - font_dirs.push_back(g_cfg.vfs.get_dev_flash() + "data/font/"); - font_dirs.push_back(g_cfg.vfs.get_dev_flash() + "data/font/SONY-CC/"); + void render_text_ex(std::vector& result, f32& x_advance, f32& y_advance, const char* text, u32 char_limit, u16 max_width, bool wrap); - // Attempt to load a font from dev_flash as a last resort - fallback_fonts.push_back(g_cfg.vfs.get_dev_flash() + "data/font/SCE-PS3-VR-R-LATIN.TTF"); + std::vector render_text(const char* text, u16 max_width = UINT16_MAX, bool wrap = false); - // Attemt to load requested font - std::string file_path; - bool font_found = false; - for (auto& font_dir : font_dirs) - { - std::string requested_file = font_dir + ttf_name; - - // Append ".ttf" if not present - std::string font_lower(requested_file); - - std::transform(requested_file.begin(), requested_file.end(), font_lower.begin(), ::tolower); - if (font_lower.substr(font_lower.size() - 4) != ".ttf") - requested_file += ".ttf"; - - file_path = requested_file; - - if (fs::is_file(requested_file)) - { - font_found = true; - break; - } - } - - // Attemt to load a fallback if request font wasn't found - if (!font_found) - { - for (auto &fallback_font : fallback_fonts) - { - if (fs::is_file(fallback_font)) - { - file_path = fallback_font; - font_found = true; - - LOG_NOTICE(RSX, "Found font file '%s' as a replacement for '%s'", fallback_font.c_str(), ttf_name); - break; - } - } - } - - // Read font - if (font_found) - { - fs::file f(file_path); - f.read(bytes, f.size()); - } - else - { - LOG_ERROR(RSX, "Failed to initialize font '%s.ttf'", ttf_name); - return; - } - - glyph_data.resize(width * height); - pack_info.resize(256); - - stbtt_pack_context context; - if (!stbtt_PackBegin(&context, glyph_data.data(), width, height, 0, 1, nullptr)) - { - LOG_ERROR(RSX, "Font packing failed"); - return; - } - - stbtt_PackSetOversampling(&context, oversample, oversample); - - // Convert pt to px - size_px = ceilf((f32)size * 96.f / 72.f); - size_pt = size; - - if (!stbtt_PackFontRange(&context, bytes.data(), 0, size_px, 0, 256, pack_info.data())) - { - LOG_ERROR(RSX, "Font packing failed"); - stbtt_PackEnd(&context); - return; - } - - stbtt_PackEnd(&context); - - font_name = ttf_name; - initialized = true; - - f32 unused; - get_char('m', em_size, unused); - } - - stbtt_aligned_quad get_char(char c, f32 &x_advance, f32 &y_advance) - { - if (!initialized) - return{}; - - stbtt_aligned_quad quad; - stbtt_GetPackedQuad(pack_info.data(), width, height, u8(c), &x_advance, &y_advance, &quad, false); - return quad; - } - - void render_text_ex(std::vector& result, f32& x_advance, f32& y_advance, const char* text, u32 char_limit, u16 max_width, bool wrap) - { - x_advance = 0.f; - y_advance = 0.f; - result.clear(); - - if (!initialized) - { - return; - } - - u32 i = 0u; - bool skip_whitespace = false; - - while (true) - { - if (char c = text[i++]; c && (i <= char_limit)) - { - if (u8(c) >= char_count) - { - // Unsupported glyph, render null for now - c = ' '; - c = ' '; - } - - switch (c) - { - case '\n': - { - y_advance += size_px + 2.f; - x_advance = 0.f; - continue; - } - case '\r': - { - x_advance = 0.f; - continue; - } - default: - { - stbtt_aligned_quad quad; - if (skip_whitespace && text[i - 1] == ' ') - { - quad = {}; - } - else - { - quad = get_char(c, x_advance, y_advance); - skip_whitespace = false; - } - - if (quad.x1 > max_width) - { - bool wrapped = false; - bool non_whitespace_break = false; - - if (wrap) - { - // scan previous chars - for (int j = i - 1, nb_chars = 0; j > 0; j--, nb_chars++) - { - if (text[j] == '\n') - break; - - if (text[j] == ' ') - { - non_whitespace_break = true; - continue; - } - - if (non_whitespace_break) - { - if (nb_chars > 1) - { - nb_chars--; - - auto first_affected = result.size() - (nb_chars * 4); - f32 base_x = result[first_affected].values[0]; - - for (size_t n = first_affected; n < result.size(); ++n) - { - auto char_index = n / 4; - if (text[char_index] == ' ') - { - // Skip character - result[n++].vec2(0.f, 0.f); - result[n++].vec2(0.f, 0.f); - result[n++].vec2(0.f, 0.f); - result[n].vec2(0.f, 0.f); - continue; - } - - result[n].values[0] -= base_x; - result[n].values[1] += size_px + 2.f; - } - - x_advance = result.back().values[0]; - } - else - { - x_advance = 0.f; - } - - wrapped = true; - y_advance += size_px + 2.f; - - if (text[i - 1] == ' ') - { - quad = {}; - skip_whitespace = true; - } - else - { - quad = get_char(c, x_advance, y_advance); - } - - break; - } - } - } - - if (!wrapped) - { - // TODO: Ellipsize - break; - } - } - - result.push_back({ quad.x0, quad.y0, quad.s0, quad.t0 }); - result.push_back({ quad.x1, quad.y0, quad.s1, quad.t0 }); - result.push_back({ quad.x0, quad.y1, quad.s0, quad.t1 }); - result.push_back({ quad.x1, quad.y1, quad.s1, quad.t1 }); - break; - } - } // switch - } - else - { - break; - } - } - } - - std::vector render_text(const char *text, u16 max_width = UINT16_MAX, bool wrap = false) - { - std::vector result; - f32 unused_x, unused_y; - - render_text_ex(result, unused_x, unused_y, text, UINT32_MAX, max_width, wrap); - return result; - } - - std::pair get_char_offset(const char *text, u16 max_length, u16 max_width = UINT16_MAX, bool wrap = false) - { - std::vector unused; - f32 loc_x, loc_y; - - render_text_ex(unused, loc_x, loc_y, text, max_length, max_width, wrap); - return { loc_x, loc_y }; - } + std::pair get_char_offset(const char* text, u16 max_length, u16 max_width = UINT16_MAX, bool wrap = false); }; // TODO: Singletons are cancer @@ -1417,99 +1143,18 @@ namespace rsx f32 m_value = 0.f; public: - progress_bar() - { - text_view.back_color = { 0.f, 0.f, 0.f, 0.f }; - } + progress_bar(); + void inc(f32 value); + void dec(f32 value); + void set_limit(f32 limit); + void set_value(f32 value); + void set_pos(u16 _x, u16 _y) override; + void set_size(u16 _w, u16 _h) override; + void translate(s16 dx, s16 dy) override; + void set_text(const char* str) override; + void set_text(const std::string& str) override; - void inc(f32 value) - { - set_value(m_value + value); - } - - void dec(f32 value) - { - set_value(m_value - value); - } - - void set_limit(f32 limit) - { - m_limit = limit; - is_compiled = false; - } - - void set_value(f32 value) - { - m_value = std::clamp(value, 0.f, m_limit); - - f32 indicator_width = (w * m_value) / m_limit; - indicator.set_size((u16)indicator_width, h); - is_compiled = false; - } - - void set_pos(u16 _x, u16 _y) override - { - u16 text_w, text_h; - text_view.measure_text(text_w, text_h); - text_h += 13; - - overlay_element::set_pos(_x, _y + text_h); - indicator.set_pos(_x, _y + text_h); - text_view.set_pos(_x, _y); - } - - void set_size(u16 _w, u16 _h) override - { - overlay_element::set_size(_w, _h); - text_view.set_size(_w, text_view.h); - set_value(m_value); - } - - void translate(s16 dx, s16 dy) override - { - set_pos(x + dx, y + dy); - } - - void set_text(const char* str) override - { - text_view.set_text(str); - text_view.align_text(text_align::center); - - u16 text_w, text_h; - text_view.measure_text(text_w, text_h); - text_view.set_size(w, text_h); - - set_pos(text_view.x, text_view.y); - is_compiled = false; - } - - void set_text(const std::string& str) override - { - text_view.set_text(str); - text_view.align_text(text_align::center); - - u16 text_w, text_h; - text_view.measure_text(text_w, text_h); - text_view.set_size(w, text_h); - - set_pos(text_view.x, text_view.y); - is_compiled = false; - } - - compiled_resource& get_compiled() override - { - if (!is_compiled) - { - auto& compiled = overlay_element::get_compiled(); - compiled.add(text_view.get_compiled()); - - indicator.back_color = fore_color; - indicator.refresh(); - compiled.add(indicator.get_compiled()); - } - - return compiled_resources; - } + compiled_resource& get_compiled() override; }; struct list_view : public vertical_layout @@ -1528,181 +1173,23 @@ namespace rsx bool m_cancel_only = false; public: - list_view(u16 width, u16 height) - { - w = width; - h = height; + list_view(u16 width, u16 height); - m_scroll_indicator_top = std::make_unique(width, 5); - m_scroll_indicator_bottom = std::make_unique(width, 5); - m_accept_btn = std::make_unique(120, 20); - m_cancel_btn = std::make_unique(120, 20); - m_highlight_box = std::make_unique(width, 0); + void update_selection(); - m_scroll_indicator_top->set_size(width, 40); - m_scroll_indicator_bottom->set_size(width, 40); - m_accept_btn->set_size(120, 30); - m_cancel_btn->set_size(120, 30); + void select_next(); + void select_previous(); - m_scroll_indicator_top->set_image_resource(resource_config::standard_image_resource::fade_top); - m_scroll_indicator_bottom->set_image_resource(resource_config::standard_image_resource::fade_bottom); + void add_entry(std::unique_ptr& entry); - if (g_cfg.sys.enter_button_assignment == enter_button_assign::circle) - { - m_accept_btn->set_image_resource(resource_config::standard_image_resource::circle); - m_cancel_btn->set_image_resource(resource_config::standard_image_resource::cross); - } - else - { - m_accept_btn->set_image_resource(resource_config::standard_image_resource::cross); - m_cancel_btn->set_image_resource(resource_config::standard_image_resource::circle); - } + int get_selected_index(); - m_scroll_indicator_bottom->set_pos(0, height - 40); - m_accept_btn->set_pos(30, height + 20); - m_cancel_btn->set_pos(180, height + 20); + std::string get_selected_item(); - m_accept_btn->set_text("Select"); - m_cancel_btn->set_text("Cancel"); + void set_cancel_only(bool cancel_only); + void translate(s16 _x, s16 _y) override; - m_accept_btn->set_font("Arial", 16); - m_cancel_btn->set_font("Arial", 16); - - auto_resize = false; - back_color = { 0.15f, 0.15f, 0.15f, 0.8f }; - - m_highlight_box->back_color = { .5f, .5f, .8f, 0.2f }; - m_highlight_box->pulse_effect_enabled = true; - m_scroll_indicator_top->fore_color.a = 0.f; - m_scroll_indicator_bottom->fore_color.a = 0.f; - } - - void update_selection() - { - auto current_element = m_items[m_selected_entry * 2].get(); - - // Calculate bounds - auto min_y = current_element->y - y; - auto max_y = current_element->y + current_element->h + pack_padding + 2 - y; - - if (min_y < scroll_offset_value) - { - scroll_offset_value = min_y; - } - else if (max_y > (h + scroll_offset_value)) - { - scroll_offset_value = max_y - h - 2; - } - - if ((scroll_offset_value + h + 2) >= m_elements_height) - m_scroll_indicator_bottom->fore_color.a = 0.f; - else - m_scroll_indicator_bottom->fore_color.a = 0.5f; - - if (scroll_offset_value == 0) - m_scroll_indicator_top->fore_color.a = 0.f; - else - m_scroll_indicator_top->fore_color.a = 0.5f; - - m_highlight_box->set_pos(current_element->x, current_element->y); - m_highlight_box->h = current_element->h + pack_padding; - m_highlight_box->y -= scroll_offset_value; - - m_highlight_box->refresh(); - m_scroll_indicator_top->refresh(); - m_scroll_indicator_bottom->refresh(); - refresh(); - } - - void select_next() - { - if (m_selected_entry < (m_elements_count - 1)) - { - m_selected_entry++; - update_selection(); - } - } - - void select_previous() - { - if (m_selected_entry > 0) - { - m_selected_entry--; - update_selection(); - } - } - - void add_entry(std::unique_ptr& entry) - { - // Add entry view - add_element(entry); - m_elements_count++; - - // Add separator - auto separator = std::make_unique(); - separator->back_color = fore_color; - separator->w = w; - separator->h = 2; - add_element(separator); - - if (m_selected_entry < 0) - m_selected_entry = 0; - - m_elements_height = advance_pos; - update_selection(); - } - - int get_selected_index() - { - return m_selected_entry; - } - - std::string get_selected_item() - { - if (m_selected_entry < 0) - return{}; - - return m_items[m_selected_entry]->text; - } - - void set_cancel_only(bool cancel_only) - { - if (cancel_only) - m_cancel_btn->set_pos(x + 30, y + h + 20); - else - m_cancel_btn->set_pos(x + 180, y + h + 20); - - m_cancel_only = cancel_only; - is_compiled = false; - } - - void translate(s16 _x, s16 _y) override - { - layout_container::translate(_x, _y); - m_scroll_indicator_top->translate(_x, _y); - m_scroll_indicator_bottom->translate(_x, _y); - m_accept_btn->translate(_x, _y); - m_cancel_btn->translate(_x, _y); - } - - compiled_resource& get_compiled() override - { - if (!is_compiled) - { - auto compiled = vertical_layout::get_compiled(); - compiled.add(m_highlight_box->get_compiled()); - compiled.add(m_scroll_indicator_top->get_compiled()); - compiled.add(m_scroll_indicator_bottom->get_compiled()); - compiled.add(m_cancel_btn->get_compiled()); - - if (!m_cancel_only) - compiled.add(m_accept_btn->get_compiled()); - - compiled_resources = compiled; - } - - return compiled_resources; - } + compiled_resource& get_compiled() override; }; struct edit_text : public label @@ -1720,112 +1207,11 @@ namespace rsx using label::label; - void move_caret(direction dir) - { - switch (dir) - { - case left: - { - if (caret_position) - { - caret_position--; - refresh(); - } - break; - } - case right: - { - if (caret_position < text.length()) - { - caret_position++; - refresh(); - } - break; - } - case up: - case down: - // TODO - break; - } - } + void move_caret(direction dir); + void insert_text(const std::string& str); + void erase(); - void insert_text(const std::string& str) - { - if (caret_position == 0) - { - // Start - text = str + text; - } - else if (caret_position == text.length()) - { - // End - text += str; - } - else - { - // Middle - text.insert(caret_position, str); - } - - caret_position += ::narrow(str.length()); - refresh(); - } - - void erase() - { - if (!caret_position) - { - return; - } - - if (caret_position == 1) - { - text = text.length() > 1? text.substr(1) : ""; - } - else if (caret_position == text.length()) - { - text = text.substr(0, caret_position - 1); - } - else - { - text = text.substr(0, caret_position - 1) + text.substr(caret_position); - } - - caret_position--; - refresh(); - } - - compiled_resource& get_compiled() override - { - if (!is_compiled) - { - auto& compiled = label::get_compiled(); - - overlay_element caret; - auto renderer = get_font(); - const auto caret_loc = renderer->get_char_offset(text.c_str(), caret_position, - clip_text ? w : UINT16_MAX, wrap_text); - - caret.set_pos(u16(caret_loc.first + padding_left + x), u16(caret_loc.second + padding_top + y)); - caret.set_size(1, u16(renderer->size_px + 2)); - caret.fore_color = fore_color; - caret.back_color = fore_color; - caret.pulse_effect_enabled = true; - - compiled.add(caret.get_compiled()); - - for (auto &cmd : compiled.draw_commands) - { - // TODO: Scrolling by using scroll offset - cmd.config.clip_region = true; - cmd.config.clip_rect = { f32(x), f32(y), f32(x + w), f32(y + h) }; - } - - is_compiled = true; - } - - return compiled_resources; - } + compiled_resource& get_compiled() override; }; } } diff --git a/rpcs3/Emu/RSX/Overlays/overlay_edit_text.cpp b/rpcs3/Emu/RSX/Overlays/overlay_edit_text.cpp new file mode 100644 index 0000000000..1cbd84733e --- /dev/null +++ b/rpcs3/Emu/RSX/Overlays/overlay_edit_text.cpp @@ -0,0 +1,114 @@ +#include "stdafx.h" +#include "overlay_controls.h" + +namespace rsx +{ + namespace overlays + { + void edit_text::move_caret(direction dir) + { + switch (dir) + { + case left: + { + if (caret_position) + { + caret_position--; + refresh(); + } + break; + } + case right: + { + if (caret_position < text.length()) + { + caret_position++; + refresh(); + } + break; + } + case up: + case down: + // TODO + break; + } + } + + void edit_text::insert_text(const std::string& str) + { + if (caret_position == 0) + { + // Start + text = str + text; + } + else if (caret_position == text.length()) + { + // End + text += str; + } + else + { + // Middle + text.insert(caret_position, str); + } + + caret_position += ::narrow(str.length()); + refresh(); + } + + void edit_text::erase() + { + if (!caret_position) + { + return; + } + + if (caret_position == 1) + { + text = text.length() > 1 ? text.substr(1) : ""; + } + else if (caret_position == text.length()) + { + text = text.substr(0, caret_position - 1); + } + else + { + text = text.substr(0, caret_position - 1) + text.substr(caret_position); + } + + caret_position--; + refresh(); + } + + compiled_resource& edit_text::get_compiled() + { + if (!is_compiled) + { + auto& compiled = label::get_compiled(); + + overlay_element caret; + auto renderer = get_font(); + const auto caret_loc = renderer->get_char_offset(text.c_str(), caret_position, clip_text ? w : UINT16_MAX, wrap_text); + + caret.set_pos(u16(caret_loc.first + padding_left + x), u16(caret_loc.second + padding_top + y)); + caret.set_size(1, u16(renderer->size_px + 2)); + caret.fore_color = fore_color; + caret.back_color = fore_color; + caret.pulse_effect_enabled = true; + + compiled.add(caret.get_compiled()); + + for (auto& cmd : compiled.draw_commands) + { + // TODO: Scrolling by using scroll offset + cmd.config.clip_region = true; + cmd.config.clip_rect = {f32(x), f32(y), f32(x + w), f32(y + h)}; + } + + is_compiled = true; + } + + return compiled_resources; + } + } // namespace overlays +} // namespace rsx diff --git a/rpcs3/Emu/RSX/Overlays/overlay_font.cpp b/rpcs3/Emu/RSX/Overlays/overlay_font.cpp new file mode 100644 index 0000000000..c9b8d082a8 --- /dev/null +++ b/rpcs3/Emu/RSX/Overlays/overlay_font.cpp @@ -0,0 +1,292 @@ +#include "stdafx.h" +#include "overlay_controls.h" + +namespace rsx +{ + namespace overlays + { + font::font(const char* ttf_name, f32 size) + { + // Init glyph + std::vector bytes; + std::vector font_dirs; + std::vector fallback_fonts; +#ifdef _WIN32 + font_dirs.push_back("C:/Windows/Fonts/"); + fallback_fonts.push_back("C:/Windows/Fonts/Arial.ttf"); +#else + char* home = getenv("HOME"); + if (home == nullptr) + home = getpwuid(getuid())->pw_dir; + + font_dirs.push_back(home); + if (home[font_dirs[0].length() - 1] == '/') + font_dirs[0] += ".fonts/"; + else + font_dirs[0] += "/.fonts/"; + + fallback_fonts.push_back("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"); // ubuntu + fallback_fonts.push_back("/usr/share/fonts/TTF/DejaVuSans.ttf"); // arch +#endif + // Search dev_flash for the font too + font_dirs.push_back(g_cfg.vfs.get_dev_flash() + "data/font/"); + font_dirs.push_back(g_cfg.vfs.get_dev_flash() + "data/font/SONY-CC/"); + + // Attempt to load a font from dev_flash as a last resort + fallback_fonts.push_back(g_cfg.vfs.get_dev_flash() + "data/font/SCE-PS3-VR-R-LATIN.TTF"); + + // Attemt to load requested font + std::string file_path; + bool font_found = false; + for (auto& font_dir : font_dirs) + { + std::string requested_file = font_dir + ttf_name; + + // Append ".ttf" if not present + std::string font_lower(requested_file); + + std::transform(requested_file.begin(), requested_file.end(), font_lower.begin(), ::tolower); + if (font_lower.substr(font_lower.size() - 4) != ".ttf") + requested_file += ".ttf"; + + file_path = requested_file; + + if (fs::is_file(requested_file)) + { + font_found = true; + break; + } + } + + // Attemt to load a fallback if request font wasn't found + if (!font_found) + { + for (auto& fallback_font : fallback_fonts) + { + if (fs::is_file(fallback_font)) + { + file_path = fallback_font; + font_found = true; + + LOG_NOTICE(RSX, "Found font file '%s' as a replacement for '%s'", fallback_font.c_str(), ttf_name); + break; + } + } + } + + // Read font + if (font_found) + { + fs::file f(file_path); + f.read(bytes, f.size()); + } + else + { + LOG_ERROR(RSX, "Failed to initialize font '%s.ttf'", ttf_name); + return; + } + + glyph_data.resize(width * height); + pack_info.resize(256); + + stbtt_pack_context context; + if (!stbtt_PackBegin(&context, glyph_data.data(), width, height, 0, 1, nullptr)) + { + LOG_ERROR(RSX, "Font packing failed"); + return; + } + + stbtt_PackSetOversampling(&context, oversample, oversample); + + // Convert pt to px + size_px = ceilf((f32)size * 96.f / 72.f); + size_pt = size; + + if (!stbtt_PackFontRange(&context, bytes.data(), 0, size_px, 0, 256, pack_info.data())) + { + LOG_ERROR(RSX, "Font packing failed"); + stbtt_PackEnd(&context); + return; + } + + stbtt_PackEnd(&context); + + font_name = ttf_name; + initialized = true; + + f32 unused; + get_char('m', em_size, unused); + } + + stbtt_aligned_quad font::get_char(char c, f32& x_advance, f32& y_advance) + { + if (!initialized) + return {}; + + stbtt_aligned_quad quad; + stbtt_GetPackedQuad(pack_info.data(), width, height, u8(c), &x_advance, &y_advance, &quad, false); + return quad; + } + + void font::render_text_ex(std::vector& result, f32& x_advance, f32& y_advance, const char* text, u32 char_limit, u16 max_width, bool wrap) + { + x_advance = 0.f; + y_advance = 0.f; + result.clear(); + + if (!initialized) + { + return; + } + + u32 i = 0u; + bool skip_whitespace = false; + + while (true) + { + if (char c = text[i++]; c && (i <= char_limit)) + { + if (u8(c) >= char_count) + { + // Unsupported glyph, render null for now + c = ' '; + c = ' '; + } + + switch (c) + { + case '\n': + { + y_advance += size_px + 2.f; + x_advance = 0.f; + continue; + } + case '\r': + { + x_advance = 0.f; + continue; + } + default: + { + stbtt_aligned_quad quad; + if (skip_whitespace && text[i - 1] == ' ') + { + quad = {}; + } + else + { + quad = get_char(c, x_advance, y_advance); + skip_whitespace = false; + } + + if (quad.x1 > max_width) + { + bool wrapped = false; + bool non_whitespace_break = false; + + if (wrap) + { + // scan previous chars + for (int j = i - 1, nb_chars = 0; j > 0; j--, nb_chars++) + { + if (text[j] == '\n') + break; + + if (text[j] == ' ') + { + non_whitespace_break = true; + continue; + } + + if (non_whitespace_break) + { + if (nb_chars > 1) + { + nb_chars--; + + auto first_affected = result.size() - (nb_chars * 4); + f32 base_x = result[first_affected].values[0]; + + for (size_t n = first_affected; n < result.size(); ++n) + { + auto char_index = n / 4; + if (text[char_index] == ' ') + { + // Skip character + result[n++].vec2(0.f, 0.f); + result[n++].vec2(0.f, 0.f); + result[n++].vec2(0.f, 0.f); + result[n].vec2(0.f, 0.f); + continue; + } + + result[n].values[0] -= base_x; + result[n].values[1] += size_px + 2.f; + } + + x_advance = result.back().values[0]; + } + else + { + x_advance = 0.f; + } + + wrapped = true; + y_advance += size_px + 2.f; + + if (text[i - 1] == ' ') + { + quad = {}; + skip_whitespace = true; + } + else + { + quad = get_char(c, x_advance, y_advance); + } + + break; + } + } + } + + if (!wrapped) + { + // TODO: Ellipsize + break; + } + } + + result.push_back({quad.x0, quad.y0, quad.s0, quad.t0}); + result.push_back({quad.x1, quad.y0, quad.s1, quad.t0}); + result.push_back({quad.x0, quad.y1, quad.s0, quad.t1}); + result.push_back({quad.x1, quad.y1, quad.s1, quad.t1}); + break; + } + } // switch + } + else + { + break; + } + } + } + + std::vector font::render_text(const char* text, u16 max_width, bool wrap) + { + std::vector result; + f32 unused_x, unused_y; + + render_text_ex(result, unused_x, unused_y, text, UINT32_MAX, max_width, wrap); + return result; + } + + std::pair font::get_char_offset(const char* text, u16 max_length, u16 max_width, bool wrap) + { + std::vector unused; + f32 loc_x, loc_y; + + render_text_ex(unused, loc_x, loc_y, text, max_length, max_width, wrap); + return {loc_x, loc_y}; + } + } // namespace overlays +} // namespace rsx diff --git a/rpcs3/Emu/RSX/Overlays/overlay_list_view.cpp b/rpcs3/Emu/RSX/Overlays/overlay_list_view.cpp new file mode 100644 index 0000000000..129968d4c4 --- /dev/null +++ b/rpcs3/Emu/RSX/Overlays/overlay_list_view.cpp @@ -0,0 +1,184 @@ +#include "stdafx.h" +#include "overlay_controls.h" + +namespace rsx +{ + namespace overlays + { + list_view::list_view(u16 width, u16 height) + { + w = width; + h = height; + + m_scroll_indicator_top = std::make_unique(width, 5); + m_scroll_indicator_bottom = std::make_unique(width, 5); + m_accept_btn = std::make_unique(120, 20); + m_cancel_btn = std::make_unique(120, 20); + m_highlight_box = std::make_unique(width, 0); + + m_scroll_indicator_top->set_size(width, 40); + m_scroll_indicator_bottom->set_size(width, 40); + m_accept_btn->set_size(120, 30); + m_cancel_btn->set_size(120, 30); + + m_scroll_indicator_top->set_image_resource(resource_config::standard_image_resource::fade_top); + m_scroll_indicator_bottom->set_image_resource(resource_config::standard_image_resource::fade_bottom); + + if (g_cfg.sys.enter_button_assignment == enter_button_assign::circle) + { + m_accept_btn->set_image_resource(resource_config::standard_image_resource::circle); + m_cancel_btn->set_image_resource(resource_config::standard_image_resource::cross); + } + else + { + m_accept_btn->set_image_resource(resource_config::standard_image_resource::cross); + m_cancel_btn->set_image_resource(resource_config::standard_image_resource::circle); + } + + m_scroll_indicator_bottom->set_pos(0, height - 40); + m_accept_btn->set_pos(30, height + 20); + m_cancel_btn->set_pos(180, height + 20); + + m_accept_btn->set_text("Select"); + m_cancel_btn->set_text("Cancel"); + + m_accept_btn->set_font("Arial", 16); + m_cancel_btn->set_font("Arial", 16); + + auto_resize = false; + back_color = {0.15f, 0.15f, 0.15f, 0.8f}; + + m_highlight_box->back_color = {.5f, .5f, .8f, 0.2f}; + m_highlight_box->pulse_effect_enabled = true; + m_scroll_indicator_top->fore_color.a = 0.f; + m_scroll_indicator_bottom->fore_color.a = 0.f; + } + + void list_view::update_selection() + { + auto current_element = m_items[m_selected_entry * 2].get(); + + // Calculate bounds + auto min_y = current_element->y - y; + auto max_y = current_element->y + current_element->h + pack_padding + 2 - y; + + if (min_y < scroll_offset_value) + { + scroll_offset_value = min_y; + } + else if (max_y > (h + scroll_offset_value)) + { + scroll_offset_value = max_y - h - 2; + } + + if ((scroll_offset_value + h + 2) >= m_elements_height) + m_scroll_indicator_bottom->fore_color.a = 0.f; + else + m_scroll_indicator_bottom->fore_color.a = 0.5f; + + if (scroll_offset_value == 0) + m_scroll_indicator_top->fore_color.a = 0.f; + else + m_scroll_indicator_top->fore_color.a = 0.5f; + + m_highlight_box->set_pos(current_element->x, current_element->y); + m_highlight_box->h = current_element->h + pack_padding; + m_highlight_box->y -= scroll_offset_value; + + m_highlight_box->refresh(); + m_scroll_indicator_top->refresh(); + m_scroll_indicator_bottom->refresh(); + refresh(); + } + + void list_view::select_next() + { + if (m_selected_entry < (m_elements_count - 1)) + { + m_selected_entry++; + update_selection(); + } + } + + void list_view::select_previous() + { + if (m_selected_entry > 0) + { + m_selected_entry--; + update_selection(); + } + } + + void list_view::add_entry(std::unique_ptr& entry) + { + // Add entry view + add_element(entry); + m_elements_count++; + + // Add separator + auto separator = std::make_unique(); + separator->back_color = fore_color; + separator->w = w; + separator->h = 2; + add_element(separator); + + if (m_selected_entry < 0) + m_selected_entry = 0; + + m_elements_height = advance_pos; + update_selection(); + } + + int list_view::get_selected_index() + { + return m_selected_entry; + } + + std::string list_view::get_selected_item() + { + if (m_selected_entry < 0) + return {}; + + return m_items[m_selected_entry]->text; + } + + void list_view::set_cancel_only(bool cancel_only) + { + if (cancel_only) + m_cancel_btn->set_pos(x + 30, y + h + 20); + else + m_cancel_btn->set_pos(x + 180, y + h + 20); + + m_cancel_only = cancel_only; + is_compiled = false; + } + + void list_view::translate(s16 _x, s16 _y) + { + layout_container::translate(_x, _y); + m_scroll_indicator_top->translate(_x, _y); + m_scroll_indicator_bottom->translate(_x, _y); + m_accept_btn->translate(_x, _y); + m_cancel_btn->translate(_x, _y); + } + + compiled_resource& list_view::get_compiled() + { + if (!is_compiled) + { + auto compiled = vertical_layout::get_compiled(); + compiled.add(m_highlight_box->get_compiled()); + compiled.add(m_scroll_indicator_top->get_compiled()); + compiled.add(m_scroll_indicator_bottom->get_compiled()); + compiled.add(m_cancel_btn->get_compiled()); + + if (!m_cancel_only) + compiled.add(m_accept_btn->get_compiled()); + + compiled_resources = compiled; + } + + return compiled_resources; + } + } // namespace overlays +} // namespace rsx diff --git a/rpcs3/Emu/RSX/Overlays/overlay_message_dialog.cpp b/rpcs3/Emu/RSX/Overlays/overlay_message_dialog.cpp new file mode 100644 index 0000000000..39d1855fc4 --- /dev/null +++ b/rpcs3/Emu/RSX/Overlays/overlay_message_dialog.cpp @@ -0,0 +1,299 @@ +#include "stdafx.h" +#include "overlays.h" + +namespace rsx +{ + namespace overlays + { + message_dialog::message_dialog(bool use_custom_background) + { + background.set_size(1280, 720); + background.back_color.a = 0.85f; + + text_display.set_size(1100, 40); + text_display.set_pos(90, 364); + text_display.set_font("Arial", 16); + text_display.align_text(overlay_element::text_align::center); + text_display.set_wrap_text(true); + text_display.back_color.a = 0.f; + + bottom_bar.back_color = color4f(1.f, 1.f, 1.f, 1.f); + bottom_bar.set_size(1200, 2); + bottom_bar.set_pos(40, 400); + + progress_1.set_size(800, 4); + progress_2.set_size(800, 4); + progress_1.back_color = color4f(0.25f, 0.f, 0.f, 0.85f); + progress_2.back_color = color4f(0.25f, 0.f, 0.f, 0.85f); + + btn_ok.set_text("Yes"); + btn_ok.set_size(140, 30); + btn_ok.set_pos(545, 420); + btn_ok.set_font("Arial", 16); + + btn_cancel.set_text("No"); + btn_cancel.set_size(140, 30); + btn_cancel.set_pos(685, 420); + btn_cancel.set_font("Arial", 16); + + if (g_cfg.sys.enter_button_assignment == enter_button_assign::circle) + { + btn_ok.set_image_resource(resource_config::standard_image_resource::circle); + btn_cancel.set_image_resource(resource_config::standard_image_resource::cross); + } + else + { + btn_ok.set_image_resource(resource_config::standard_image_resource::cross); + btn_cancel.set_image_resource(resource_config::standard_image_resource::circle); + } + + if (use_custom_background) + { + auto icon_path = Emu.GetSfoDir() + "/PIC1.PNG"; + if (!fs::exists(icon_path)) + { + // Fallback path + icon_path = Emu.GetSfoDir() + "/ICON0.PNG"; + } + + if (fs::exists(icon_path)) + { + background_image = std::make_unique(icon_path.c_str()); + if (background_image->data) + { + f32 color = (100 - g_cfg.video.shader_preloading_dialog.darkening_strength) / 100.f; + background_poster.fore_color = color4f(color, color, color, 1.); + background.back_color.a = 0.f; + + background_poster.set_size(1280, 720); + background_poster.set_raw_image(background_image.get()); + background_poster.set_blur_strength((u8)g_cfg.video.shader_preloading_dialog.blur_strength); + } + } + } + + return_code = CELL_MSGDIALOG_BUTTON_NONE; + } + + compiled_resource message_dialog::get_compiled() + { + compiled_resource result; + + if (background_image && background_image->data) + { + result.add(background_poster.get_compiled()); + } + + result.add(background.get_compiled()); + result.add(text_display.get_compiled()); + + if (num_progress_bars > 0) + { + result.add(progress_1.get_compiled()); + } + + if (num_progress_bars > 1) + { + result.add(progress_2.get_compiled()); + } + + if (interactive) + { + if (!num_progress_bars) + result.add(bottom_bar.get_compiled()); + + if (!cancel_only) + result.add(btn_ok.get_compiled()); + + if (!ok_only) + result.add(btn_cancel.get_compiled()); + } + + return result; + } + + void message_dialog::on_button_pressed(pad_button button_press) + { + switch (button_press) + { + case pad_button::cross: + { + if (ok_only) + { + return_code = CELL_MSGDIALOG_BUTTON_OK; + } + else if (cancel_only) + { + // Do not accept for cancel-only dialogs + return; + } + else + { + return_code = CELL_MSGDIALOG_BUTTON_YES; + } + + break; + } + case pad_button::circle: + { + if (ok_only) + { + // Ignore cancel operation for Ok-only + return; + } + else if (cancel_only) + { + return_code = CELL_MSGDIALOG_BUTTON_ESCAPE; + } + else + { + return_code = CELL_MSGDIALOG_BUTTON_NO; + } + + break; + } + default: return; + } + + close(); + } + + error_code message_dialog::show(const std::string& text, const MsgDialogType& type, std::function on_close) + { + num_progress_bars = type.progress_bar_count; + if (num_progress_bars) + { + u16 offset = 58; + progress_1.set_pos(240, 412); + + if (num_progress_bars > 1) + { + progress_2.set_pos(240, 462); + offset = 98; + } + + // Push the other stuff down + bottom_bar.translate(0, offset); + btn_ok.translate(0, offset); + btn_cancel.translate(0, offset); + } + + text_display.set_text(utf8_to_ascii8(text)); + + u16 text_w, text_h; + text_display.measure_text(text_w, text_h); + text_display.translate(0, -(text_h - 16)); + + switch (type.button_type.unshifted()) + { + case CELL_MSGDIALOG_TYPE_BUTTON_TYPE_NONE: + interactive = !type.disable_cancel; + if (interactive) + { + btn_cancel.set_pos(585, btn_cancel.y); + btn_cancel.set_text("Cancel"); + cancel_only = true; + } + break; + case CELL_MSGDIALOG_TYPE_BUTTON_TYPE_OK: + btn_ok.set_pos(600, btn_ok.y); + btn_ok.set_text("OK"); + interactive = true; + ok_only = true; + break; + case CELL_MSGDIALOG_TYPE_BUTTON_TYPE_YESNO: interactive = true; break; + } + + this->on_close = on_close; + if (interactive) + { + thread_ctrl::spawn("dialog input thread", [&] { + if (auto error = run_input_loop()) + { + LOG_ERROR(RSX, "Dialog input loop exited with error code=%d", error); + } + }); + } + + return CELL_OK; + } + + u32 message_dialog::progress_bar_count() + { + return num_progress_bars; + } + + void message_dialog::progress_bar_set_taskbar_index(s32 index) + { + taskbar_index = index; + } + + error_code message_dialog::progress_bar_set_message(u32 index, const std::string& msg) + { + if (index >= num_progress_bars) + return CELL_MSGDIALOG_ERROR_PARAM; + + if (index == 0) + progress_1.set_text(msg); + else + progress_2.set_text(msg); + + return CELL_OK; + } + + error_code message_dialog::progress_bar_increment(u32 index, f32 value) + { + if (index >= num_progress_bars) + return CELL_MSGDIALOG_ERROR_PARAM; + + if (index == 0) + progress_1.inc(value); + else + progress_2.inc(value); + + if (index == taskbar_index || taskbar_index == -1) + Emu.GetCallbacks().handle_taskbar_progress(1, (s32)value); + + return CELL_OK; + } + + error_code message_dialog::progress_bar_reset(u32 index) + { + if (index >= num_progress_bars) + return CELL_MSGDIALOG_ERROR_PARAM; + + if (index == 0) + progress_1.set_value(0.f); + else + progress_2.set_value(0.f); + + Emu.GetCallbacks().handle_taskbar_progress(0, 0); + + return CELL_OK; + } + + error_code message_dialog::progress_bar_set_limit(u32 index, u32 limit) + { + if (index >= num_progress_bars) + return CELL_MSGDIALOG_ERROR_PARAM; + + if (index == 0) + progress_1.set_limit((float)limit); + else + progress_2.set_limit((float)limit); + + if (index == taskbar_index) + { + taskbar_limit = limit; + Emu.GetCallbacks().handle_taskbar_progress(2, taskbar_limit); + } + else if (taskbar_index == -1) + { + taskbar_limit += limit; + Emu.GetCallbacks().handle_taskbar_progress(2, taskbar_limit); + } + + return CELL_OK; + } + } // namespace overlays +} // namespace rsx diff --git a/rpcs3/Emu/RSX/Overlays/overlay_progress_bar.cpp b/rpcs3/Emu/RSX/Overlays/overlay_progress_bar.cpp new file mode 100644 index 0000000000..8c90858b16 --- /dev/null +++ b/rpcs3/Emu/RSX/Overlays/overlay_progress_bar.cpp @@ -0,0 +1,102 @@ +#include "stdafx.h" +#include "overlay_controls.h" + +namespace rsx +{ + namespace overlays + { + progress_bar::progress_bar() + { + text_view.back_color = {0.f, 0.f, 0.f, 0.f}; + } + + void progress_bar::inc(f32 value) + { + set_value(m_value + value); + } + + void progress_bar::dec(f32 value) + { + set_value(m_value - value); + } + + void progress_bar::set_limit(f32 limit) + { + m_limit = limit; + is_compiled = false; + } + + void progress_bar::set_value(f32 value) + { + m_value = std::clamp(value, 0.f, m_limit); + + f32 indicator_width = (w * m_value) / m_limit; + indicator.set_size((u16)indicator_width, h); + is_compiled = false; + } + + void progress_bar::set_pos(u16 _x, u16 _y) + { + u16 text_w, text_h; + text_view.measure_text(text_w, text_h); + text_h += 13; + + overlay_element::set_pos(_x, _y + text_h); + indicator.set_pos(_x, _y + text_h); + text_view.set_pos(_x, _y); + } + + void progress_bar::set_size(u16 _w, u16 _h) + { + overlay_element::set_size(_w, _h); + text_view.set_size(_w, text_view.h); + set_value(m_value); + } + + void progress_bar::translate(s16 dx, s16 dy) + { + set_pos(x + dx, y + dy); + } + + void progress_bar::set_text(const char* str) + { + text_view.set_text(str); + text_view.align_text(text_align::center); + + u16 text_w, text_h; + text_view.measure_text(text_w, text_h); + text_view.set_size(w, text_h); + + set_pos(text_view.x, text_view.y); + is_compiled = false; + } + + void progress_bar::set_text(const std::string& str) + { + text_view.set_text(str); + text_view.align_text(text_align::center); + + u16 text_w, text_h; + text_view.measure_text(text_w, text_h); + text_view.set_size(w, text_h); + + set_pos(text_view.x, text_view.y); + is_compiled = false; + } + + compiled_resource& progress_bar::get_compiled() + { + if (!is_compiled) + { + auto& compiled = overlay_element::get_compiled(); + compiled.add(text_view.get_compiled()); + + indicator.back_color = fore_color; + indicator.refresh(); + compiled.add(indicator.get_compiled()); + } + + return compiled_resources; + } + } // namespace overlays +} // namespace rsx diff --git a/rpcs3/Emu/RSX/Overlays/overlay_save_dialog.cpp b/rpcs3/Emu/RSX/Overlays/overlay_save_dialog.cpp new file mode 100644 index 0000000000..ef4b89fbea --- /dev/null +++ b/rpcs3/Emu/RSX/Overlays/overlay_save_dialog.cpp @@ -0,0 +1,251 @@ +#include "stdafx.h" +#include "overlays.h" + +namespace rsx +{ + namespace overlays + { + save_dialog::save_dialog_entry::save_dialog_entry(const std::string& text1, const std::string& text2, const std::string& text3, u8 resource_id, const std::vector& icon_buf) + { + std::unique_ptr image = std::make_unique(); + image->set_size(160, 110); + image->set_padding(36, 36, 11, 11); // Square image, 88x88 + + if (resource_id != image_resource_id::raw_image) + { + static_cast(image.get())->set_image_resource(resource_id); + } + else if (icon_buf.size()) + { + image->set_padding(0, 0, 11, 11); // Half sized icon, 320x176->160x88 + icon_data = std::make_unique(icon_buf); + static_cast(image.get())->set_raw_image(icon_data.get()); + } + else + { + // Fallback + static_cast(image.get())->set_image_resource(resource_config::standard_image_resource::save); + } + + std::unique_ptr text_stack = std::make_unique(); + std::unique_ptr padding = std::make_unique(); + std::unique_ptr header_text = std::make_unique