diff options
Diffstat (limited to 'src/frontend/mame/ui/selmenu.cpp')
-rw-r--r-- | src/frontend/mame/ui/selmenu.cpp | 4077 |
1 files changed, 2741 insertions, 1336 deletions
diff --git a/src/frontend/mame/ui/selmenu.cpp b/src/frontend/mame/ui/selmenu.cpp index 236a33225bc..5655c226d3f 100644 --- a/src/frontend/mame/ui/selmenu.cpp +++ b/src/frontend/mame/ui/selmenu.cpp @@ -15,19 +15,16 @@ #include "ui/info.h" #include "ui/inifile.h" -// these hold static bitmap images -#include "ui/defimg.ipp" -#include "ui/starimg.ipp" -#include "ui/toolbar.ipp" - #include "audit.h" #include "cheat.h" +#include "infoxml.h" #include "mame.h" #include "mameopts.h" #include "corestr.h" #include "drivenum.h" #include "emuopts.h" +#include "fileio.h" #include "rendfont.h" #include "rendutil.h" #include "romload.h" @@ -36,16 +33,27 @@ #include "uiinput.h" #include "luaengine.h" +#include "util/nanosvg.h" +#include "util/path.h" + #include <algorithm> #include <cmath> #include <cstring> -#include <utility> + + +// these hold static bitmap images +#include "ui/defimg.ipp" +#include "ui/toolbar.ipp" namespace ui { namespace { +std::pair<char const *, char const *> RIGHT_PANEL_NAMES[RP_LAST + 1] = { + { "image", N_("Images") }, + { "info", N_("Info") } }; + enum { FIRST_VIEW = 0, @@ -69,49 +77,62 @@ enum LAST_VIEW = COVERS_VIEW }; -std::pair<char const *, char const *> const arts_info[] = -{ - { __("Snapshots"), OPTION_SNAPSHOT_DIRECTORY }, - { __("Cabinets"), OPTION_CABINETS_PATH }, - { __("Control Panels"), OPTION_CPANELS_PATH }, - { __("PCBs"), OPTION_PCBS_PATH }, - { __("Flyers"), OPTION_FLYERS_PATH }, - { __("Titles"), OPTION_TITLES_PATH }, - { __("Ends"), OPTION_ENDS_PATH }, - { __("Artwork Preview"), OPTION_ARTPREV_PATH }, - { __("Bosses"), OPTION_BOSSES_PATH }, - { __("Logos"), OPTION_LOGOS_PATH }, - { __("Versus"), OPTION_VERSUS_PATH }, - { __("Game Over"), OPTION_GAMEOVER_PATH }, - { __("HowTo"), OPTION_HOWTO_PATH }, - { __("Scores"), OPTION_SCORES_PATH }, - { __("Select"), OPTION_SELECT_PATH }, - { __("Marquees"), OPTION_MARQUEES_PATH }, - { __("Covers"), OPTION_COVER_PATH }, +std::tuple<char const *, char const *, char const *> const ARTS_INFO[] = +{ + { "snap", N_p("selmenu-artwork", "Snapshots"), OPTION_SNAPSHOT_DIRECTORY }, + { "cabinet", N_p("selmenu-artwork", "Cabinet"), OPTION_CABINETS_PATH }, + { "cpanel", N_p("selmenu-artwork", "Control Panel"), OPTION_CPANELS_PATH }, + { "pcb", N_p("selmenu-artwork", "PCB"), OPTION_PCBS_PATH }, + { "flyer", N_p("selmenu-artwork", "Flyer"), OPTION_FLYERS_PATH }, + { "title", N_p("selmenu-artwork", "Title Screen"), OPTION_TITLES_PATH }, + { "ending", N_p("selmenu-artwork", "Ending"), OPTION_ENDS_PATH }, + { "artpreview", N_p("selmenu-artwork", "Artwork Preview"), OPTION_ARTPREV_PATH }, + { "boss", N_p("selmenu-artwork", "Bosses"), OPTION_BOSSES_PATH }, + { "logo", N_p("selmenu-artwork", "Logo"), OPTION_LOGOS_PATH }, + { "versus", N_p("selmenu-artwork", "Versus"), OPTION_VERSUS_PATH }, + { "gameover", N_p("selmenu-artwork", "Game Over"), OPTION_GAMEOVER_PATH }, + { "howto", N_p("selmenu-artwork", "HowTo"), OPTION_HOWTO_PATH }, + { "scores", N_p("selmenu-artwork", "Scores"), OPTION_SCORES_PATH }, + { "select", N_p("selmenu-artwork", "Select"), OPTION_SELECT_PATH }, + { "marquee", N_p("selmenu-artwork", "Marquee"), OPTION_MARQUEES_PATH }, + { "cover", N_p("selmenu-artwork", "Covers"), OPTION_COVER_PATH }, }; char const *const hover_msg[] = { - __("Add or remove favorites"), - __("Export displayed list to file"), - __("Show DATs view"), + N_("Add or remove favorite"), + N_("Export displayed list to file"), + N_("Audit media"), + N_("Show DATs view"), +}; + +std::tuple<unsigned, int, bool> SYS_TOOLBAR_BITMAPS[] = { + { TOOLBAR_BITMAP_FAVORITE, IPT_UI_FAVORITES, true }, + { TOOLBAR_BITMAP_SAVE, IPT_UI_EXPORT, false }, + { TOOLBAR_BITMAP_AUDIT, IPT_UI_AUDIT, false }, + { TOOLBAR_BITMAP_INFO, IPT_UI_DATS, true } +}; + +std::tuple<unsigned, int, bool> SW_TOOLBAR_BITMAPS[] = { + { TOOLBAR_BITMAP_FAVORITE, IPT_UI_FAVORITES, true }, + { TOOLBAR_BITMAP_INFO, IPT_UI_DATS, true } }; void load_image(bitmap_argb32 &bitmap, emu_file &file, std::string const &base) { - if (file.open(base + ".png") == osd_file::error::NONE) + if (!file.open(base + ".png")) { render_load_png(bitmap, file); file.close(); } - if (!bitmap.valid() && (file.open(base + ".jpg") == osd_file::error::NONE)) + if (!bitmap.valid() && !file.open(base + ".jpg")) { render_load_jpeg(bitmap, file); file.close(); } - if (!bitmap.valid() && (file.open(base + ".bmp") == osd_file::error::NONE)) + if (!bitmap.valid() && !file.open(base + ".bmp")) { render_load_msdib(bitmap, file); file.close(); @@ -123,7 +144,7 @@ void load_driver_image(bitmap_argb32 &bitmap, emu_file &file, game_driver const { // try to load snapshot first from saved "0000.png" file std::string fullname = driver.name; - load_image(bitmap, file, fullname + PATH_SEPARATOR + "0000"); + load_image(bitmap, file, util::path_concat(fullname, "0000")); // if fail, attempt to load from standard file if (!bitmap.valid()) @@ -144,7 +165,7 @@ void load_driver_image(bitmap_argb32 &bitmap, emu_file &file, game_driver const if (isclone) { fullname = driver.parent; - load_image(bitmap, file, fullname + PATH_SEPARATOR + "0000"); + load_image(bitmap, file, util::path_concat(fullname, "0000")); if (!bitmap.valid()) load_image(bitmap, file, fullname); @@ -154,8 +175,6 @@ void load_driver_image(bitmap_argb32 &bitmap, emu_file &file, game_driver const } // anonymous namespace -constexpr std::size_t menu_select_launch::MAX_VISIBLE_SEARCH; // stupid non-inline semantics - class menu_select_launch::software_parts : public menu { @@ -163,12 +182,9 @@ public: software_parts(mame_ui_manager &mui, render_container &container, s_parts &&parts, ui_software_info const &ui_info); virtual ~software_parts() override; -protected: - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; - private: - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; + virtual bool handle(event const *ev) override; ui_software_info const &m_uiinfo; s_parts const m_parts; @@ -181,14 +197,11 @@ public: bios_selection(mame_ui_manager &mui, render_container &container, s_bios &&biosname, ui_software_info const &swinfo, bool inlist); virtual ~bios_selection() override; -protected: - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; - private: bios_selection(mame_ui_manager &mui, render_container &container, s_bios &&biosname, void const *driver, bool software, bool inlist); - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; + virtual bool handle(event const *ev) override; void const *m_driver; bool m_software, m_inlist; @@ -200,14 +213,11 @@ std::string menu_select_launch::reselect_last::s_software; std::string menu_select_launch::reselect_last::s_swlist; bool menu_select_launch::reselect_last::s_reselect = false; -std::mutex menu_select_launch::s_cache_guard; -menu_select_launch::cache_ptr_map menu_select_launch::s_caches; - // instantiate possible variants of these so derived classes don't get link errors template bool menu_select_launch::select_bios(game_driver const &, bool); template bool menu_select_launch::select_bios(ui_software_info const &, bool); -template float menu_select_launch::draw_left_panel<machine_filter>(machine_filter::type current, std::map<machine_filter::type, machine_filter::ptr> const &filters, float x1, float y1, float x2, float y2); -template float menu_select_launch::draw_left_panel<software_filter>(software_filter::type current, std::map<software_filter::type, software_filter::ptr> const &filters, float x1, float y1, float x2, float y2); +template void menu_select_launch::draw_left_panel<machine_filter>(u32 flags, machine_filter::type current, std::map<machine_filter::type, machine_filter::ptr> const &filters); +template void menu_select_launch::draw_left_panel<software_filter>(u32 flags, software_filter::type current, std::map<software_filter::type, software_filter::ptr> const &filters); menu_select_launch::system_flags::system_flags(machine_static_info const &info) @@ -262,6 +272,7 @@ menu_select_launch::software_parts::software_parts(mame_ui_manager &mui, render_ , m_uiinfo(ui_info) , m_parts(std::move(parts)) { + set_heading(_("Select Software Package Part")); } //------------------------------------------------- @@ -276,53 +287,38 @@ menu_select_launch::software_parts::~software_parts() // populate //------------------------------------------------- -void menu_select_launch::software_parts::populate(float &customtop, float &custombottom) +void menu_select_launch::software_parts::populate() { std::vector<s_parts::const_iterator> parts; parts.reserve(m_parts.size()); for (s_parts::const_iterator it = m_parts.begin(); m_parts.end() != it; ++it) parts.push_back(it); - std::sort(parts.begin(), parts.end(), [] (auto const &left, auto const &right) { return 0 > core_stricmp(left->first.c_str(), right->first.c_str()); }); + std::sort(parts.begin(), parts.end(), [] (auto const &left, auto const &right) { return 0 > core_stricmp(left->first, right->first); }); for (auto const &elem : parts) item_append(elem->first, elem->second, 0, (void *)&*elem); item_append(menu_item_type::SEPARATOR); - customtop = ui().get_line_height() + (3.0f * ui().box_tb_border()); } //------------------------------------------------- // handle //------------------------------------------------- -void menu_select_launch::software_parts::handle() +bool menu_select_launch::software_parts::handle(event const *ev) { - // process the menu - const event *menu_event = process(0); - if (menu_event && (menu_event->iptkey) == IPT_UI_SELECT && menu_event->itemref) + if (ev && (ev->iptkey == IPT_UI_SELECT) && ev->itemref) { for (auto const &elem : m_parts) { - if ((void*)&elem == menu_event->itemref) + if ((void*)&elem == ev->itemref) { launch_system(ui(), *m_uiinfo.driver, &m_uiinfo, &elem.first, nullptr); break; } } } -} -//------------------------------------------------- -// perform our special rendering -//------------------------------------------------- - -void menu_select_launch::software_parts::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) -{ - char const *const text[] = { _("Software part selection:") }; - draw_text_box( - std::begin(text), std::end(text), - origx1, origx2, origy1 - top, origy1 - ui().box_tb_border(), - ui::text_layout::CENTER, ui::text_layout::TRUNCATE, false, - ui().colors().text_color(), UI_GREEN_COLOR, 1.0f); + return false; } @@ -347,6 +343,7 @@ menu_select_launch::bios_selection::bios_selection(mame_ui_manager &mui, render_ , m_inlist(inlist) , m_bios(std::move(biosname)) { + set_heading(_("Select System BIOS")); } //------------------------------------------------- @@ -361,28 +358,25 @@ menu_select_launch::bios_selection::~bios_selection() // populate //------------------------------------------------- -void menu_select_launch::bios_selection::populate(float &customtop, float &custombottom) +void menu_select_launch::bios_selection::populate() { - for (auto & elem : m_bios) + for (auto &elem : m_bios) item_append(elem.first, 0, (void *)&elem.first); item_append(menu_item_type::SEPARATOR); - customtop = ui().get_line_height() + (3.0f * ui().box_tb_border()); } //------------------------------------------------- // handle //------------------------------------------------- -void menu_select_launch::bios_selection::handle() +bool menu_select_launch::bios_selection::handle(event const *ev) { - // process the menu - const event *menu_event = process(0); - if (menu_event && menu_event->iptkey == IPT_UI_SELECT && menu_event->itemref) + if (ev && (ev->iptkey == IPT_UI_SELECT) && ev->itemref) { for (auto & elem : m_bios) { - if ((void*)&elem.first == menu_event->itemref) + if ((void*)&elem.first == ev->itemref) { if (!m_software) { @@ -415,20 +409,8 @@ void menu_select_launch::bios_selection::handle() } } } -} -//------------------------------------------------- -// perform our special rendering -//------------------------------------------------- - -void menu_select_launch::bios_selection::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) -{ - char const *const text[] = { _("BIOS selection:") }; - draw_text_box( - std::begin(text), std::end(text), - origx1, origx2, origy1 - top, origy1 - ui().box_tb_border(), - ui::text_layout::CENTER, ui::text_layout::TRUNCATE, false, - ui().colors().text_color(), UI_GREEN_COLOR, 1.0f); + return false; } @@ -438,63 +420,86 @@ menu_select_launch::cache::cache(running_machine &machine) , m_snapx_driver(nullptr) , m_snapx_software(nullptr) , m_no_avail_bitmap(256, 256) - , m_star_bitmap(32, 32) - , m_star_texture(nullptr, machine.render()) - , m_toolbar_bitmap() - , m_sw_toolbar_bitmap() - , m_toolbar_texture() - , m_sw_toolbar_texture() + , m_toolbar_bitmaps() + , m_toolbar_textures() { render_manager &render(machine.render()); // create a texture for snapshot m_snapx_texture.reset(render.texture_alloc(render_texture::hq_scale)); - std::memcpy(&m_no_avail_bitmap.pix(0), no_avail_bmp, 256 * 256 * sizeof(uint32_t)); + std::memcpy(&m_no_avail_bitmap.pix(0), no_avail_bmp, 256 * 256 * sizeof(u32)); - std::memcpy(&m_star_bitmap.pix(0), favorite_star_bmp, 32 * 32 * sizeof(uint32_t)); - m_star_texture.reset(render.texture_alloc()); - m_star_texture->set_bitmap(m_star_bitmap, m_star_bitmap.cliprect(), TEXFORMAT_ARGB32); + m_toolbar_bitmaps.resize(UI_TOOLBAR_BUTTONS * 2); + m_toolbar_textures.reserve(UI_TOOLBAR_BUTTONS * 2); +} - m_toolbar_bitmap.reserve(UI_TOOLBAR_BUTTONS); - m_sw_toolbar_bitmap.reserve(UI_TOOLBAR_BUTTONS); - m_toolbar_texture.reserve(UI_TOOLBAR_BUTTONS); - m_sw_toolbar_texture.reserve(UI_TOOLBAR_BUTTONS); - for (std::size_t i = 0; i < UI_TOOLBAR_BUTTONS; ++i) - { - m_toolbar_bitmap.emplace_back(32, 32); - m_sw_toolbar_bitmap.emplace_back(32, 32); - m_toolbar_texture.emplace_back(render.texture_alloc(), render); - m_sw_toolbar_texture.emplace_back(render.texture_alloc(), render); +menu_select_launch::cache::~cache() +{ +} - std::memcpy(&m_toolbar_bitmap.back().pix(0), toolbar_bitmap_bmp[i], 32 * 32 * sizeof(uint32_t)); - if (m_toolbar_bitmap.back().valid()) - m_toolbar_texture.back()->set_bitmap(m_toolbar_bitmap.back(), m_toolbar_bitmap.back().cliprect(), TEXFORMAT_ARGB32); - else - m_toolbar_bitmap.back().reset(); - if ((i == 0U) || (i == 2U)) - { - std::memcpy(&m_sw_toolbar_bitmap.back().pix(0), toolbar_bitmap_bmp[i], 32 * 32 * sizeof(uint32_t)); - if (m_sw_toolbar_bitmap.back().valid()) - m_sw_toolbar_texture.back()->set_bitmap(m_sw_toolbar_bitmap.back(), m_sw_toolbar_bitmap.back().cliprect(), TEXFORMAT_ARGB32); - else - m_sw_toolbar_bitmap.back().reset(); - } - else +void menu_select_launch::cache::cache_toolbar(running_machine &machine, float width, float height) +{ + // not bothering to transform for non-square pixels greatly simplifies this + render_manager &render(machine.render()); + render_target const &target(render.ui_target()); + s32 const pix_size(std::ceil(std::max(width * target.width(), height * target.height()))); + if (m_toolbar_textures.empty() || (m_toolbar_bitmaps[0].width() != pix_size) || (m_toolbar_bitmaps[0].height() != pix_size)) + { + m_toolbar_textures.clear(); + util::nsvg_rasterizer_ptr rasterizer(nsvgCreateRasterizer()); + std::string xml; + for (unsigned i = 0; UI_TOOLBAR_BUTTONS > i; ++i) { - m_sw_toolbar_bitmap.back().reset(); + // parse SVG and calculate scale + xml = toolbar_icons_svg[i]; + util::nsvg_image_ptr svg(nsvgParse(xml.data(), "px", 72)); + float const xscale(float(pix_size) / svg->width); + float const yscale(float(pix_size) / svg->height); + float const drawscale((std::max)(xscale, yscale)); + + // rasterise the SVG and clear it out of memory + bitmap_argb32 &bitmap(m_toolbar_bitmaps[2 * i]); + bitmap.resize(pix_size, pix_size); + nsvgRasterize( + rasterizer.get(), + svg.get(), + 0, 0, drawscale, + reinterpret_cast<unsigned char *>(&bitmap.pix(0)), + pix_size, pix_size, + bitmap.rowbytes()); + svg.reset(); + + // correct colour format + bitmap_argb32 &disabled_bitmap(m_toolbar_bitmaps[(2 * i) + 1]); + disabled_bitmap.resize(pix_size, pix_size); + for (s32 y = 0; bitmap.height() > y; ++y) + { + u32 *cdst(&bitmap.pix(y)); + u32 *mdst(&disabled_bitmap.pix(y)); + for (s32 x = 0; bitmap.width() > x; ++x, ++cdst, ++mdst) + { + u8 const *const src(reinterpret_cast<u8 const *>(cdst)); + rgb_t const c(src[3], src[0], src[1], src[2]); + u8 const l(std::clamp(std::lround((0.2126 * src[0]) + (0.7152 * src[1]) + (0.0722 * src[2])), 0L, 255L)); + rgb_t const m(src[3], l, l, l); + *cdst = c; + *mdst = m; + } + } + + // make textures + render_texture &texture(*m_toolbar_textures.emplace_back(render.texture_alloc(), render)); + render_texture &disabled_texture(*m_toolbar_textures.emplace_back(render.texture_alloc(), render)); + texture.set_bitmap(bitmap, bitmap.cliprect(), TEXFORMAT_ARGB32); + disabled_texture.set_bitmap(disabled_bitmap, disabled_bitmap.cliprect(), TEXFORMAT_ARGB32); } } } -menu_select_launch::cache::~cache() -{ -} - - menu_select_launch::~menu_select_launch() { } @@ -502,6 +507,7 @@ menu_select_launch::~menu_select_launch() menu_select_launch::menu_select_launch(mame_ui_manager &mui, render_container &container, bool is_swlist) : menu(mui, container) + , m_skip_main_items(0) , m_prev_selected(nullptr) , m_total_lines(0) , m_topline_datsview(0) @@ -512,56 +518,159 @@ menu_select_launch::menu_select_launch(mame_ui_manager &mui, render_container &c , m_info_view(-1) , m_items_list() , m_info_buffer() - , m_cache() + , m_info_layout() + , m_icon_width(0) + , m_icon_height(0) + , m_divider_width(0.0F) + , m_divider_arrow_width(0.0F) + , m_divider_arrow_height(0.0F) + , m_info_line_height(0.0F) + , m_cache(mui.get_session_data<menu_select_launch, cache_wrapper>(machine())) , m_is_swlist(is_swlist) , m_focus(focused_menu::MAIN) - , m_pressed(false) - , m_repeat(0) + , m_pointer_action(pointer_action::NONE) + , m_scroll_repeat(std::chrono::steady_clock::time_point::min()) + , m_base_pointer(0.0F, 0.0F) + , m_last_pointer(0.0F, 0.0F) + , m_clicked_line(0) + , m_wheel_target(focused_menu::MAIN) + , m_wheel_movement(0) + , m_primary_vbounds(0.0F, -1.0F) + , m_primary_items_top(-1.0F) + , m_primary_items_hbounds(0.0F, -1.0F) + , m_primary_lines(0) + , m_left_panel_width(-1.0F) + , m_left_items_hbounds(0.0F, -1.0F) + , m_left_items_top(1.0F) + , m_left_item_count(0) + , m_left_visible_lines(0) + , m_left_visible_top(0) + , m_right_panel_width(-1.0F) + , m_right_tabs_bottom(-1.0F) + , m_right_heading_top(-1.0F) + , m_right_content_vbounds(0.0F, -1.0F) + , m_right_content_hbounds(0.0F, -1.0F) , m_right_visible_lines(0) + , m_toolbar_button_vbounds(0.0F, -1.0F) + , m_toolbar_button_width(-1.0) + , m_toolbar_button_spacing(-1.0) + , m_toolbar_backtrack_left(-1.0) + , m_toolbar_main_left(-1.0) + , m_panels_status(SHOW_PANELS) + , m_right_panel(RP_FIRST) , m_has_icons(false) , m_switch_image(false) - , m_default_image(true) , m_image_view(FIRST_VIEW) , m_flags(256) { - // set up persistent cache for machine run - { - std::lock_guard<std::mutex> guard(s_cache_guard); - auto const found(s_caches.find(&machine())); - if (found != s_caches.end()) - { - assert(found->second); - m_cache = found->second; - } - else - { - m_cache = std::make_shared<cache>(machine()); - s_caches.emplace(&machine(), m_cache); - add_cleanup_callback(&menu_select_launch::exit); - } - } + set_needs_prev_menu_item(false); + set_process_flags(PROCESS_LR_REPEAT); +} + + +std::pair<bool, bool> menu_select_launch::next_right_panel_view() +{ + if (right_panel() == RP_IMAGES) + return next_image_view(); + else if (right_panel() == RP_INFOS) + return next_info_view(); + else + return std::make_pair(false, false); +} + + +std::pair<bool, bool> menu_select_launch::previous_right_panel_view() +{ + if (right_panel() == RP_IMAGES) + return previous_image_view(); + else if (right_panel() == RP_INFOS) + return previous_info_view(); + else + return std::make_pair(false, false); } -void menu_select_launch::next_image_view() +std::pair<bool, bool> menu_select_launch::next_image_view() { if (LAST_VIEW > m_image_view) { ++m_image_view; set_switch_image(); - m_default_image = false; + return std::make_pair(true, (LAST_VIEW > m_image_view)); + } + else + { + return std::make_pair(false, false); } } -void menu_select_launch::previous_image_view() +std::pair<bool, bool> menu_select_launch::previous_image_view() { if (FIRST_VIEW < m_image_view) { --m_image_view; set_switch_image(); - m_default_image = false; + return std::make_pair(true, (FIRST_VIEW < m_image_view)); + } + else + { + return std::make_pair(false, false); + } +} + + +std::pair<bool, bool> menu_select_launch::next_info_view() +{ + ui_software_info const *software; + ui_system_info const *system; + get_selection(software, system); + if (software && !software->startempty) + { + if ((ui_globals::cur_sw_dats_total - 1) > ui_globals::cur_sw_dats_view) + { + ++ui_globals::cur_sw_dats_view; + m_topline_datsview = 0; + return std::make_pair(true, (ui_globals::cur_sw_dats_total - 1) > ui_globals::cur_sw_dats_view); + } + } + else if (system || (software && software->driver)) + { + if ((ui_globals::curdats_total - 1) > ui_globals::curdats_view) + { + ++ui_globals::curdats_view; + m_topline_datsview = 0; + return std::make_pair(true, (ui_globals::curdats_total - 1) > ui_globals::curdats_view); + } + } + return std::make_pair(false, false); +} + + +std::pair<bool, bool> menu_select_launch::previous_info_view() +{ + ui_software_info const *software; + ui_system_info const *system; + get_selection(software, system); + if (software && !software->startempty) + { + if (0 < ui_globals::cur_sw_dats_view) + { + --ui_globals::cur_sw_dats_view; + m_topline_datsview = 0; + return std::make_pair(true, 0 < ui_globals::cur_sw_dats_view); + } + } + else if (system || (software && software->driver)) + { + if (0 < ui_globals::curdats_view) + { + --ui_globals::curdats_view; + m_topline_datsview = 0; + return std::make_pair(true, 0 < ui_globals::curdats_view); + } } + return std::make_pair(false, false); } @@ -621,7 +730,7 @@ void menu_select_launch::launch_system(mame_ui_manager &mui, game_driver const & else moptions.set_value(OPTION_SOFTWARENAME, util::string_format("%s:%s", swinfo->listname, swinfo->shortname), OPTION_PRIORITY_CMDLINE); - moptions.set_value(OPTION_SNAPNAME, util::string_format("%s%s%s", swinfo->listname, PATH_SEPARATOR, swinfo->shortname), OPTION_PRIORITY_CMDLINE); + moptions.set_value(OPTION_SNAPNAME, util::path_concat(swinfo->listname, swinfo->shortname), OPTION_PRIORITY_CMDLINE); } reselect_last::set_software(driver, *swinfo); } @@ -635,7 +744,52 @@ void menu_select_launch::launch_system(mame_ui_manager &mui, game_driver const & mame_machine_manager::instance()->schedule_new_driver(driver); mui.machine().schedule_hard_reset(); - stack_reset(mui.machine()); + stack_reset(mui); +} + + +//------------------------------------------------- +// recompute metrics +//------------------------------------------------- + +void menu_select_launch::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + menu::recompute_metrics(width, height, aspect); + + // calculate icon size in pixels + render_target const &target(machine().render().ui_target()); + bool const rotated((target.orientation() & ORIENTATION_SWAP_XY) != 0); + m_icon_width = int((rotated ? height : width) * line_height() * aspect); + m_icon_height = int((rotated ? width : height) * line_height()); + + // force info text to be laid out again + m_info_layout = std::nullopt; + + // calculate size of dividers between panes + m_divider_width = 0.8F * line_height() * x_aspect(); + m_divider_arrow_width = 0.32F * line_height() * x_aspect(); + m_divider_arrow_height = 0.64F * line_height(); + + // calculate info text size + m_info_line_height = ui().get_line_height(ui().options().infos_size()); + + // invalidate panel metrics + m_primary_vbounds = std::make_pair(0.0F, -1.0F); + m_primary_items_hbounds = std::make_pair(0.0F, -1.0F); + m_left_panel_width = -1.0F; + m_left_items_hbounds = std::make_pair(0.0F, -1.0F); + m_right_panel_width = -1.0F; + m_right_heading_top = -1.0F; + m_right_content_vbounds = std::make_pair(0.0F, -1.0F); + m_right_content_hbounds = std::make_pair(0.0F, -1.0F); + m_toolbar_button_vbounds = std::make_pair(0.0F, -1.0F); + + // abandon pointer input + m_pointer_action = pointer_action::NONE; + + // force right panel images to be redrawn + m_cache.set_snapx_driver(nullptr); + m_cache.set_snapx_software(nullptr); } @@ -643,52 +797,52 @@ void menu_select_launch::launch_system(mame_ui_manager &mui, game_driver const & // perform our special rendering //------------------------------------------------- -void menu_select_launch::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_select_launch::custom_render(u32 flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { - std::string tempbuf[5]; + std::string tempbuf[4]; // determine the text for the header make_topbox_text(tempbuf[0], tempbuf[1], tempbuf[2]); - float const y1 = origy1 - 3.0f * ui().box_tb_border() - ui().get_line_height(); + float const y1 = origy1 - 3.0f * tb_border() - line_height(); draw_text_box( tempbuf, tempbuf + 3, origx1, origx2, origy1 - top, y1, - ui::text_layout::CENTER, ui::text_layout::NEVER, true, - ui().colors().text_color(), ui().colors().background_color(), 1.0f); + text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, true, + ui().colors().text_color(), ui().colors().background_color()); // draw toolbar - draw_toolbar(origx1, y1, origx2, origy1 - ui().box_tb_border()); + draw_toolbar(flags, origx1, y1, origx2, origy1 - tb_border()); // determine the text to render below ui_software_info const *swinfo; - game_driver const *driver; - get_selection(swinfo, driver); + ui_system_info const *system; + get_selection(swinfo, system); bool isstar = false; rgb_t color = ui().colors().background_color(); - if (swinfo && ((swinfo->startempty != 1) || !driver)) + if (swinfo && !swinfo->startempty) { isstar = mame_machine_manager::instance()->favorite().is_favorite_system_software(*swinfo); // first line is long name or system - tempbuf[0] = make_software_description(*swinfo); + tempbuf[0] = make_software_description(*swinfo, system); // next line is year, publisher - tempbuf[1] = string_format(_("%1$s, %2$-.100s"), swinfo->year, swinfo->publisher); + tempbuf[1] = string_format(_("%1$s, %2$s"), swinfo->year, swinfo->publisher); // next line is parent/clone if (!swinfo->parentname.empty()) - tempbuf[2] = string_format(_("Software is clone of: %1$-.100s"), !swinfo->parentlongname.empty() ? swinfo->parentlongname : swinfo->parentname); + tempbuf[2] = string_format(_("Software is clone of: %1$s"), !swinfo->parentlongname.empty() ? swinfo->parentlongname : swinfo->parentname); else tempbuf[2] = _("Software is parent"); // next line is supported status - if (swinfo->supported == SOFTWARE_SUPPORTED_NO) + if (swinfo->supported == software_support::UNSUPPORTED) { tempbuf[3] = _("Supported: No"); color = UI_RED_COLOR; } - else if (swinfo->supported == SOFTWARE_SUPPORTED_PARTIAL) + else if (swinfo->supported == software_support::PARTIALLY_SUPPORTED) { tempbuf[3] = _("Supported: Partial"); color = UI_YELLOW_COLOR; @@ -698,78 +852,102 @@ void menu_select_launch::custom_render(void *selectedref, float top, float botto tempbuf[3] = _("Supported: Yes"); color = UI_GREEN_COLOR; } - - // last line is romset name - tempbuf[4] = string_format(_("romset: %1$-.100s"), swinfo->shortname); } - else if (driver) + else if (system || (swinfo && swinfo->driver)) { - isstar = mame_machine_manager::instance()->favorite().is_favorite_system(*driver); - - // first line is game description/game name - tempbuf[0] = make_driver_description(*driver); + game_driver const &driver(system ? *system->driver : *swinfo->driver); + isstar = mame_machine_manager::instance()->favorite().is_favorite_system(driver); - // next line is year, manufacturer - tempbuf[1] = string_format(_("%1$s, %2$-.100s"), driver->year, driver->manufacturer); + // first line is year, manufacturer + tempbuf[0] = string_format(_("%1$s, %2$s"), driver.year, driver.manufacturer); // next line is clone/parent status - int cloneof = driver_list::non_bios_clone(*driver); + int cloneof = driver_list::non_bios_clone(driver); - if (cloneof != -1) - tempbuf[2] = string_format(_("Driver is clone of: %1$-.100s"), driver_list::driver(cloneof).type.fullname()); + if (0 > cloneof) + tempbuf[1] = _("System is parent"); + else if (system) + tempbuf[1] = string_format(_("System is clone of: %1$s"), system->parent); else - tempbuf[2] = _("Driver is parent"); + tempbuf[1] = string_format(_("System is clone of: %1$s"), driver_list::driver(cloneof).type.fullname()); // next line is overall driver status - system_flags const &flags(get_system_flags(*driver)); + system_flags const &flags(get_system_flags(driver)); if (flags.machine_flags() & machine_flags::NOT_WORKING) - tempbuf[3] = _("Overall: NOT WORKING"); + tempbuf[2] = _("Overall: NOT WORKING"); else if ((flags.unemulated_features() | flags.imperfect_features()) & device_t::feature::PROTECTION) - tempbuf[3] = _("Overall: Unemulated Protection"); + tempbuf[2] = _("Overall: Unemulated Protection"); else - tempbuf[3] = _("Overall: Working"); + tempbuf[2] = _("Overall: Working"); // next line is graphics, sound status if (flags.unemulated_features() & device_t::feature::GRAPHICS) - tempbuf[4] = _("Graphics: Unimplemented, "); + tempbuf[3] = _("Graphics: Unimplemented, "); else if ((flags.unemulated_features() | flags.imperfect_features()) & (device_t::feature::GRAPHICS | device_t::feature::PALETTE)) - tempbuf[4] = _("Graphics: Imperfect, "); + tempbuf[3] = _("Graphics: Imperfect, "); else - tempbuf[4] = _("Graphics: OK, "); + tempbuf[3] = _("Graphics: OK, "); - if (driver->flags & machine_flags::NO_SOUND_HW) - tempbuf[4].append(_("Sound: None")); + if (driver.flags & machine_flags::NO_SOUND_HW) + tempbuf[3].append(_("Sound: None")); else if (flags.unemulated_features() & device_t::feature::SOUND) - tempbuf[4].append(_("Sound: Unimplemented")); + tempbuf[3].append(_("Sound: Unimplemented")); else if (flags.imperfect_features() & device_t::feature::SOUND) - tempbuf[4].append(_("Sound: Imperfect")); + tempbuf[3].append(_("Sound: Imperfect")); else - tempbuf[4].append(_("Sound: OK")); + tempbuf[3].append(_("Sound: OK")); color = flags.status_color(); } else { - std::string copyright(emulator_info::get_copyright()); - size_t found = copyright.find('\n'); + std::string_view copyright(emulator_info::get_copyright()); + unsigned line(0); - tempbuf[0].clear(); - tempbuf[1] = string_format(_("%1$s %2$s"), emulator_info::get_appname(), build_version); - tempbuf[2] = copyright.substr(0, found); - tempbuf[3] = copyright.substr(found + 1); - tempbuf[4].clear(); + // first line is version string + tempbuf[line++] = string_format("%s %s", emulator_info::get_appname(), build_version); + + // output message + while (line < std::size(tempbuf)) + { + auto const found = copyright.find('\n'); + if (std::string::npos != found) + { + tempbuf[line++] = copyright.substr(0, found); + copyright.remove_prefix(found + 1); + } + else + { + tempbuf[line++] = copyright; + copyright = std::string_view(); + } + } } // draw the footer draw_text_box( std::begin(tempbuf), std::end(tempbuf), - origx1, origx2, origy2 + ui().box_tb_border(), origy2 + bottom, - ui::text_layout::CENTER, ui::text_layout::NEVER, true, - ui().colors().text_color(), color, 1.0f); + origx1, origx2, origy2 + tb_border(), origy2 + bottom, + text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, true, + ui().colors().text_color(), color); // is favorite? draw the star if (isstar) - draw_star(origx1 + ui().box_lr_border() * machine().render().ui_aspect(&container()), origy2 + (2.0f * ui().box_tb_border())); + draw_star(origx1 + lr_border(), origy2 + (2.0f * tb_border())); +} + + +void menu_select_launch::menu_activated() +{ + m_panels_status = ui().options().hide_panels(); + m_wheel_target = focused_menu::MAIN; + m_wheel_movement = 0; +} + + +void menu_select_launch::menu_deactivated() +{ + ui().options().set_value(OPTION_HIDE_PANELS, m_panels_status, OPTION_PRIORITY_CMDLINE); } @@ -778,34 +956,52 @@ void menu_select_launch::rotate_focus(int dir) switch (get_focus()) { case focused_menu::MAIN: - if (selected_index() <= m_available_items) - { - m_prev_selected = get_selection_ref(); - if ((0 < dir) || (ui_globals::panels_status == HIDE_BOTH)) - set_selected_index(m_available_items + 1); - else if (ui_globals::panels_status == HIDE_RIGHT_PANEL) - set_focus(focused_menu::LEFT); - else - set_focus(focused_menu::RIGHTBOTTOM); - } - else + if (selected_index() >= m_available_items) { - if ((0 > dir) || (ui_globals::panels_status == HIDE_BOTH)) + if ((0 > dir) || (m_panels_status == HIDE_BOTH)) select_prev(); - else if (ui_globals::panels_status == HIDE_LEFT_PANEL) + else if (m_panels_status == HIDE_LEFT_PANEL) set_focus(focused_menu::RIGHTTOP); else set_focus(focused_menu::LEFT); } + else if (m_skip_main_items || (m_panels_status != HIDE_BOTH)) + { + m_prev_selected = get_selection_ref(); + if (0 < dir) + { + if (m_skip_main_items) + set_selected_index(m_available_items + 1); + else if (m_panels_status == HIDE_LEFT_PANEL) + set_focus(focused_menu::RIGHTTOP); + else + set_focus(focused_menu::LEFT); + } + else if (m_panels_status == HIDE_RIGHT_PANEL) + { + set_focus(focused_menu::LEFT); + } + else if (m_panels_status != HIDE_BOTH) + { + set_focus(focused_menu::RIGHTBOTTOM); + } + else + { + set_selected_index(m_available_items + 1); + } + } break; case focused_menu::LEFT: if (0 > dir) { set_focus(focused_menu::MAIN); - set_selected_index(m_available_items + 1); + if (m_skip_main_items) + set_selected_index(m_available_items + 1); + else + select_prev(); } - else if (ui_globals::panels_status != HIDE_RIGHT_PANEL) + else if (m_panels_status != HIDE_RIGHT_PANEL) { set_focus(focused_menu::RIGHTTOP); } @@ -821,14 +1017,17 @@ void menu_select_launch::rotate_focus(int dir) { set_focus(focused_menu::RIGHTBOTTOM); } - else if (ui_globals::panels_status != HIDE_LEFT_PANEL) + else if (m_panels_status != HIDE_LEFT_PANEL) { set_focus(focused_menu::LEFT); } else { set_focus(focused_menu::MAIN); - set_selected_index(m_available_items + 1); + if (m_skip_main_items) + set_selected_index(m_available_items + 1); + else + select_prev(); } break; @@ -850,223 +1049,221 @@ void menu_select_launch::rotate_focus(int dir) void menu_select_launch::inkey_dats() { ui_software_info const *software; - game_driver const *driver; - get_selection(software, driver); - if (software) - { - if (software->startempty && mame_machine_manager::instance()->lua()->call_plugin_check<const char *>("data_list", software->driver->name, true)) - menu::stack_push<menu_dats_view>(ui(), container(), software->driver); - else if (mame_machine_manager::instance()->lua()->call_plugin_check<const char *>("data_list", std::string(software->shortname).append(1, ',').append(software->listname).c_str()) || !software->usage.empty()) - menu::stack_push<menu_dats_view>(ui(), container(), software); - } - else if (driver) - { - if (mame_machine_manager::instance()->lua()->call_plugin_check<const char *>("data_list", driver->name, true)) - menu::stack_push<menu_dats_view>(ui(), container(), driver); - } + ui_system_info const *system; + get_selection(software, system); + if (software && !software->startempty) + menu::stack_push<menu_dats_view>(ui(), container(), *software); + else if (system) + menu::stack_push<menu_dats_view>(ui(), container(), system); } //------------------------------------------------- -// draw common arrows +// draw info arrow //------------------------------------------------- -void menu_select_launch::draw_common_arrow(float origx1, float origy1, float origx2, float origy2, int current, int dmin, int dmax, float title_size) +void menu_select_launch::draw_info_arrow(u32 flags, int line) { - auto line_height = ui().get_line_height(); - auto lr_arrow_width = 0.4f * line_height * machine().render().ui_aspect(&container()); - auto gutter_width = lr_arrow_width * 1.3f; + float const linetop(m_right_content_vbounds.first + (float(line) * m_info_line_height)); + float const linebottom(m_right_content_vbounds.first + (float(line + 1) * m_info_line_height)); + bool const hovered(pointer_in_rect(m_right_content_hbounds.first, linetop, m_right_content_hbounds.second, linebottom)); + bool const clicked((pointer_action::RIGHT_TRACK_LINE == m_pointer_action) && (line == m_clicked_line)); - // set left-right arrows dimension - float const ar_x0 = 0.5f * (origx2 + origx1) + 0.5f * title_size + gutter_width - lr_arrow_width; - float const ar_y0 = origy1 + 0.1f * line_height; - float const ar_x1 = 0.5f * (origx2 + origx1) + 0.5f * title_size + gutter_width; - float const ar_y1 = origy1 + 0.9f * line_height; - - float const al_x0 = 0.5f * (origx2 + origx1) - 0.5f * title_size - gutter_width; - float const al_y0 = origy1 + 0.1f * line_height; - float const al_x1 = 0.5f * (origx2 + origx1) - 0.5f * title_size - gutter_width + lr_arrow_width; - float const al_y1 = origy1 + 0.9f * line_height; - - rgb_t fgcolor_right, fgcolor_left; - fgcolor_right = fgcolor_left = ui().colors().text_color(); - - // set hover - if (mouse_in_rect(ar_x0, ar_y0, ar_x1, ar_y1) && current != dmax) + rgb_t bgcolor = ui().colors().text_bg_color(); + rgb_t fgcolor = ui().colors().text_color(); + if (clicked && hovered) { - ui().draw_textured_box(container(), ar_x0 + 0.01f, ar_y0, ar_x1 - 0.01f, ar_y1, ui().colors().mouseover_bg_color(), rgb_t(43, 43, 43), - hilight_main_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); - set_hover(HOVER_UI_RIGHT); - fgcolor_right = ui().colors().mouseover_color(); + // draw selected highlight for tracked item + bgcolor = ui().colors().selected_bg_color(); + fgcolor = ui().colors().selected_color(); + highlight(m_right_content_hbounds.first, linetop, m_right_content_hbounds.second, linebottom, bgcolor); } - else if (mouse_in_rect(al_x0, al_y0, al_x1, al_y1) && current != dmin) + else if (clicked || (!m_ui_error && !(flags & PROCESS_NOINPUT) && hovered && pointer_idle())) { - ui().draw_textured_box(container(), al_x0 + 0.01f, al_y0, al_x1 - 0.01f, al_y1, ui().colors().mouseover_bg_color(), rgb_t(43, 43, 43), - hilight_main_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); - set_hover(HOVER_UI_LEFT); - fgcolor_left = ui().colors().mouseover_color(); + // draw hover highlight when hovered over or dragged off + bgcolor = ui().colors().mouseover_bg_color(); + fgcolor = ui().colors().mouseover_color(); + highlight(m_right_content_hbounds.first, linetop, m_right_content_hbounds.second, linebottom, bgcolor); } - // apply arrow - if (dmax == dmin) - return; - else if (current == dmin) - draw_arrow(ar_x0, ar_y0, ar_x1, ar_y1, fgcolor_right, ROT90); - else if (current == dmax) - draw_arrow(al_x0, al_y0, al_x1, al_y1, fgcolor_left, ROT90 ^ ORIENTATION_FLIP_X); - else - { - draw_arrow(ar_x0, ar_y0, ar_x1, ar_y1, fgcolor_right, ROT90); - draw_arrow(al_x0, al_y0, al_x1, al_y1, fgcolor_left, ROT90 ^ ORIENTATION_FLIP_X); - } + draw_arrow( + 0.5F * (m_right_content_hbounds.first + m_right_content_hbounds.second - (x_aspect() * m_info_line_height)), + linetop + (0.25F * m_info_line_height), + 0.5F * (m_right_content_hbounds.first + m_right_content_hbounds.second + (x_aspect() * m_info_line_height)), + linetop + (0.75F * m_info_line_height), + fgcolor, + line ? (ROT0 ^ ORIENTATION_FLIP_Y) : ROT0); } //------------------------------------------------- -// draw info arrow +// draw vertical divider //------------------------------------------------- -void menu_select_launch::draw_info_arrow(int ub, float origx1, float origx2, float oy1, float line_height, float text_size, float ud_arrow_width) +void menu_select_launch::draw_divider(u32 flags, float x1, bool right) { + // work out colours rgb_t fgcolor = ui().colors().text_color(); - uint32_t orientation = (!ub) ? ROT0 : ROT0 ^ ORIENTATION_FLIP_Y; - - if (mouse_in_rect(origx1, oy1, origx2, oy1 + (line_height * text_size))) + rgb_t bgcolor(0xef, 0x12, 0x47, 0x7b); // FIXME: magic numbers in colour? + bool const hovered(pointer_in_rect(x1, m_primary_vbounds.first, x1 + m_divider_width, m_primary_vbounds.second)); + bool const clicked((pointer_action::DIVIDER_TRACK == m_pointer_action) && ((right ? 1 : 0) == m_clicked_line)); + if (hovered && clicked) + { + fgcolor = ui().colors().selected_color(); + bgcolor = ui().colors().selected_bg_color(); + } + else if (clicked || (!m_ui_error && !(flags & PROCESS_NOINPUT) && hovered)) { - ui().draw_textured_box(container(), origx1 + 0.01f, oy1, origx2 - 0.01f, oy1 + (line_height * text_size), ui().colors().mouseover_bg_color(), - rgb_t(43, 43, 43), hilight_main_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); - set_hover((!ub) ? HOVER_DAT_UP : HOVER_DAT_DOWN); fgcolor = ui().colors().mouseover_color(); } - draw_arrow(0.5f * (origx1 + origx2) - 0.5f * (ud_arrow_width * text_size), oy1 + 0.25f * (line_height * text_size), - 0.5f * (origx1 + origx2) + 0.5f * (ud_arrow_width * text_size), oy1 + 0.75f * (line_height * text_size), fgcolor, orientation); + // draw the divider pane + ui().draw_outlined_box(container(), x1, m_primary_vbounds.first, x1 + m_divider_width, m_primary_vbounds.second, bgcolor); + + // draw the arrow + uint32_t orientation(ROT90); + if (right ? !show_right_panel() : show_left_panel()) + orientation ^= ORIENTATION_FLIP_X; + float const ar_x0 = x1 + (0.5F * (m_divider_width - m_divider_arrow_width)); + float const ar_y0 = 0.5F * (m_primary_vbounds.first + m_primary_vbounds.second - m_divider_arrow_height); + draw_arrow(ar_x0, ar_y0, ar_x0 + m_divider_arrow_width, ar_y0 + m_divider_arrow_height, fgcolor, orientation); } -bool menu_select_launch::draw_error_text() -{ - if (m_ui_error) - ui().draw_text_box(container(), m_error_text, ui::text_layout::CENTER, 0.5f, 0.5f, UI_RED_COLOR); - return m_ui_error; -} +//------------------------------------------------- +// draw left panel (filter list) +//------------------------------------------------- template <typename Filter> -float menu_select_launch::draw_left_panel( - typename Filter::type current, - std::map<typename Filter::type, typename Filter::ptr> const &filters, - float x1, float y1, float x2, float y2) +void menu_select_launch::draw_left_panel(u32 flags, typename Filter::type current, std::map<typename Filter::type, typename Filter::ptr> const &filters) { - if ((ui_globals::panels_status != SHOW_PANELS) && (ui_globals::panels_status != HIDE_RIGHT_PANEL)) - return draw_collapsed_left_panel(x1, y1, x2, y2); - - // calculate line height - float const line_height(ui().get_line_height()); - float const text_size(ui().options().infos_size()); - float const sc(y2 - y1 - (2.0f * ui().box_tb_border())); - float line_height_max(line_height * text_size); - if ((Filter::COUNT * line_height_max) > sc) + if (!show_left_panel()) { - float const lm(sc / Filter::COUNT); - line_height_max = line_height * (lm / line_height); + // left panel hidden, but no need to recompute metrics if target isn't resized + m_left_panel_width = 0.0F; + + draw_divider(flags, lr_border(), false); + return; } - // calculate horizontal offset for unadorned names - std::string tmp(convert_command_glyph("_# ")); - float const text_sign = ui().get_string_width(tmp, text_size); + // get the width of the selection indicator glyphs + float const checkmark_width = ui().get_string_width(convert_command_glyph("_# "), m_info_line_height); - // get the maximum width of a filter name - float left_width(0.0f); - for (typename Filter::type x = Filter::FIRST; Filter::COUNT > x; ++x) - left_width = std::max(ui().get_string_width(Filter::display_name(x), text_size) + text_sign, left_width); + if (m_left_items_hbounds.first >= m_left_items_hbounds.second) + { + // calculate number of lines that will fit - centre vertically if we need scroll arrows + float const height(m_primary_vbounds.second - m_primary_vbounds.first); + int const lines((height - (tb_border() * 2.0F)) / m_info_line_height); + if (Filter::COUNT <= lines) + m_left_items_top = m_primary_vbounds.first + tb_border(); + else + m_left_items_top = m_primary_vbounds.first + ((height - (float(lines) * m_info_line_height)) * 0.5F); + float const pixelheight(target_size().second); + m_left_items_top = std::round(m_left_items_top * pixelheight) / pixelheight; + m_left_item_count = Filter::COUNT; + m_left_visible_lines = std::min<int>(Filter::COUNT, lines); + + // get the maximum filter name width, restricted to a quarter of the target width + float line_width(0.0F); + for (typename Filter::type x = Filter::FIRST; Filter::COUNT > x; ++x) + line_width = std::max(ui().get_string_width(Filter::display_name(x), m_info_line_height) + checkmark_width, line_width); + line_width = std::min(line_width + (lr_border() * 2.0F), 0.25F); + m_left_items_hbounds = std::make_pair(2.0F * lr_border(), (2.0F * lr_border()) + line_width); + + // make sure the scroll position makes sense + m_left_visible_top = (std::min)(m_left_visible_top, m_left_item_count - m_left_visible_lines); + } + m_left_panel_width = (m_left_items_hbounds.second - m_left_items_hbounds.first) + (lr_border() * 2.0F); + + // ensure the highlighted item is visible + if ((m_filter_highlight - Filter::FIRST) < (m_left_visible_top + 1)) + m_left_visible_top = (Filter::FIRST == m_filter_highlight) ? 0 : (m_filter_highlight - 1); + else if ((m_filter_highlight - Filter::FIRST) > (m_left_visible_top + m_left_visible_lines - 2)) + m_left_visible_top = (std::min)(m_filter_highlight - Filter::FIRST + 2 - m_left_visible_lines, m_left_item_count - m_left_visible_lines); // outline the box and inset by the border width - float const origy1(y1); - float const origy2(y2); - float const aspect(machine().render().ui_aspect(&container())); - float const lr_border(ui().box_lr_border() * aspect); - x2 = x1 + left_width + 2.0f * lr_border; - ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color()); - x1 += lr_border; - x2 -= lr_border; - y1 += ui().box_tb_border(); - y2 -= ui().box_tb_border(); + ui().draw_outlined_box( + container(), + lr_border(), m_primary_vbounds.first, lr_border() + m_left_panel_width, m_primary_vbounds.second, + ui().colors().background_color()); // now draw the rows + typename Filter::type filter(Filter::FIRST); + for (int i = 0; i < m_left_visible_top; ++i) + ++filter; auto const active_filter(filters.find(current)); - for (typename Filter::type filter = Filter::FIRST; Filter::COUNT > filter; ++filter) + std::string str; + bool const atbottom((m_left_visible_top + m_left_visible_lines) == m_left_item_count); + for (int line = 0; line < m_left_visible_lines; ++line, ++filter) { - std::string str; - if (filters.end() != active_filter) - { - str = active_filter->second->adorned_display_name(filter); - } - else - { - if (current == filter) - str = convert_command_glyph("_> "); - str.append(Filter::display_name(filter)); - } + float const line_top(m_left_items_top + (float(line) * m_info_line_height)); + bool const uparrow(!line && m_left_visible_top); + bool const downarrow(!atbottom && ((m_left_visible_lines - 1) == line)); - // handle mouse hover in passing + // work out the colours for this item rgb_t bgcolor = ui().colors().text_bg_color(); rgb_t fgcolor = ui().colors().text_color(); - if (mouse_in_rect(x1, y1, x2, y1 + line_height_max)) + bool const hovered(pointer_in_rect(m_left_items_hbounds.first, line_top, m_left_items_hbounds.second, line_top + m_info_line_height)); + bool const pointerline((pointer_action::LEFT_TRACK_LINE == m_pointer_action) && (line == m_clicked_line)); + if (pointerline && hovered) { - bgcolor = ui().colors().mouseover_bg_color(); - fgcolor = ui().colors().mouseover_color(); - set_hover(HOVER_FILTER_FIRST + filter); - highlight(x1, y1, x2, y1 + line_height_max, bgcolor); + // draw selected highlight for tracked item + bgcolor = ui().colors().selected_bg_color(); + fgcolor = ui().colors().selected_color(); + highlight(m_left_items_hbounds.first, line_top, m_left_items_hbounds.second, line_top + m_info_line_height, bgcolor); } - - // draw primary highlight if keyboard focus is here - if ((m_filter_highlight == filter) && (get_focus() == focused_menu::LEFT)) + else if ((m_filter_highlight == filter) && (get_focus() == focused_menu::LEFT)) { + // draw primary highlight if keyboard focus is here fgcolor = rgb_t(0xff, 0xff, 0xff, 0x00); bgcolor = rgb_t(0xff, 0xff, 0xff, 0xff); ui().draw_textured_box( container(), - x1, y1, x2, y1 + line_height_max, + m_left_items_hbounds.first, line_top, m_left_items_hbounds.second, line_top + m_info_line_height, bgcolor, rgb_t(255, 43, 43, 43), hilight_main_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); } + else if (pointerline || (!m_ui_error && !(flags & PROCESS_NOINPUT) && hovered && pointer_idle())) + { + // draw hover highlight when hovered over or dragged off + bgcolor = ui().colors().mouseover_bg_color(); + fgcolor = ui().colors().mouseover_color(); + highlight(m_left_items_hbounds.first, line_top, m_left_items_hbounds.second, line_top + m_info_line_height, bgcolor); + } - // finally draw the text itself and move to the next line - float const x1t = x1 + ((str == Filter::display_name(filter)) ? text_sign : 0.0f); - ui().draw_text_full( - container(), str, - x1t, y1, x2 - x1, - ui::text_layout::LEFT, ui::text_layout::NEVER, - mame_ui_manager::NORMAL, fgcolor, bgcolor, - nullptr, nullptr, text_size); - y1 += line_height_max; - } - - x1 = x2 + lr_border; - x2 = x1 + 2.0f * lr_border; - y1 = origy1; - y2 = origy2; - float const space = x2 - x1; - float const lr_arrow_width = 0.4f * space * aspect; - - // set left-right arrows dimension - float const ar_x0 = 0.5f * (x2 + x1) - 0.5f * lr_arrow_width; - float const ar_y0 = 0.5f * (y2 + y1) + 0.1f * space; - float const ar_x1 = ar_x0 + lr_arrow_width; - float const ar_y1 = 0.5f * (y2 + y1) + 0.9f * space; - - ui().draw_outlined_box(container(), x1, y1, x2, y2, rgb_t(0xef, 0x12, 0x47, 0x7b)); - - rgb_t fgcolor = ui().colors().text_color(); - if (mouse_in_rect(x1, y1, x2, y2)) - { - fgcolor = ui().colors().mouseover_color(); - set_hover(HOVER_LPANEL_ARROW); + // finally draw the text itself + if (uparrow || downarrow) + { + draw_arrow( + 0.5F * (m_left_items_hbounds.first + m_left_items_hbounds.second + (x_aspect() * m_info_line_height)), + line_top + (0.25F * m_info_line_height), + 0.5F * (m_left_items_hbounds.first + m_left_items_hbounds.second - (x_aspect() * m_info_line_height)), + line_top + (0.75F * m_info_line_height), + fgcolor, + downarrow ? (ROT0 ^ ORIENTATION_FLIP_Y) : ROT0); + } + else + { + if (filters.end() != active_filter) + str = active_filter->second->adorned_display_name(filter); + else if (current == filter) + (str = convert_command_glyph("_> ")).append(Filter::display_name(filter)); + else + str = Filter::display_name(filter); + float const x1t = m_left_items_hbounds.first + lr_border() + ((str == Filter::display_name(filter)) ? checkmark_width : 0.0F); + ui().draw_text_full( + container(), str, + x1t, line_top, m_left_items_hbounds.second - x1t - lr_border() + (1.0F / float(target_size().second)), + text_layout::text_justify::LEFT, text_layout::word_wrapping::TRUNCATE, + mame_ui_manager::NORMAL, fgcolor, bgcolor, + nullptr, nullptr, + m_info_line_height); + } } - draw_arrow(ar_x0, ar_y0, ar_x1, ar_y1, fgcolor, ROT90 ^ ORIENTATION_FLIP_X); - return x2 + lr_border; + // draw the divider + draw_divider(flags, lr_border() + m_left_panel_width, false); } @@ -1087,11 +1284,7 @@ void menu_select_launch::check_for_icons(char const *listname) { // if we're doing a software list, append it to the configured path if (listname) - { - if (!current.empty() && !util::is_directory_separator(current.back())) - current.append(PATH_SEPARATOR); - current.append(listname); - } + util::path_append(current, listname); osd_printf_verbose("Checking for icons in directory %s\n", current); // open and walk the directory @@ -1136,11 +1329,7 @@ std::string menu_select_launch::make_icon_paths(char const *listname) const { // if we're doing a software list, append it to the configured path if (listname) - { - if (!current.empty() && !util::is_directory_separator(current.back())) - current.append(PATH_SEPARATOR); - current.append(listname); - } + util::path_append(current, listname); // append the configured path if (!result.empty()) @@ -1172,24 +1361,14 @@ bool menu_select_launch::scale_icon(bitmap_argb32 &&src, texture_and_bitmap &dst assert(dst.texture); if (src.valid()) { - // calculate available space for the icon in pixels - float const height(ui().get_line_height()); - float const width(height * container().manager().ui_aspect(&container())); - render_target const &target(machine().render().ui_target()); - uint32_t const dst_height(target.height()); - uint32_t const dst_width(target.width()); - bool const rotated((target.orientation() & ORIENTATION_SWAP_XY) != 0); - int const max_height(int((rotated ? dst_width : dst_height) * height)); - int const max_width(int((rotated ? dst_height : dst_width) * width)); - // reduce the source bitmap if it's too big bitmap_argb32 tmp; - float const ratio((std::min)({ float(max_height) / src.height(), float(max_width) / src.width(), 1.0F })); + float const ratio((std::min)({ float(m_icon_height) / src.height(), float(m_icon_width) / src.width(), 1.0F })); if (1.0F > ratio) { - float const pix_height(src.height() * ratio); - float const pix_width(src.width() * ratio); - tmp.allocate(int32_t(pix_width), int32_t(pix_height)); + float const pix_height(std::ceil(src.height() * ratio)); + float const pix_width(std::ceil(src.width() * ratio)); + tmp.allocate(s32(pix_width), s32(pix_height)); render_resample_argb_bitmap_hq(tmp, src, render_color{ 1.0F, 1.0F, 1.0F, 1.0F }, true); } else @@ -1198,7 +1377,7 @@ bool menu_select_launch::scale_icon(bitmap_argb32 &&src, texture_and_bitmap &dst } // copy into the destination - dst.bitmap.allocate(max_width, max_height); + dst.bitmap.allocate(m_icon_width, m_icon_height); for (int y = 0; tmp.height() > y; ++y) for (int x = 0; tmp.width() > x; ++x) dst.bitmap.pix(y, x) = tmp.pix(y, x); @@ -1254,46 +1433,87 @@ bool menu_select_launch::select_part(mame_ui_manager &mui, render_container &con // draw toolbar //------------------------------------------------- -void menu_select_launch::draw_toolbar(float x1, float y1, float x2, float y2) +void menu_select_launch::draw_toolbar(u32 flags, float x1, float y1, float x2, float y2) { - // draw a box - ui().draw_outlined_box(container(), x1, y1, x2, y2, rgb_t(0xEF, 0x12, 0x47, 0x7B)); + // work out which buttons we're going to draw + bool const have_parent(m_is_swlist || !stack_has_special_main_menu()); + auto const *const toolbar_bitmaps(m_is_swlist ? SW_TOOLBAR_BITMAPS : SYS_TOOLBAR_BITMAPS); + unsigned const toolbar_count(m_is_swlist ? std::size(SW_TOOLBAR_BITMAPS) : std::size(SYS_TOOLBAR_BITMAPS)); - // take off the borders - float const aspect(machine().render().ui_aspect(&container())); - float const lr_border(ui().box_lr_border() * aspect); - x1 += lr_border; - x2 -= lr_border; - y1 += ui().box_tb_border(); - y2 -= ui().box_tb_border(); + // draw a box + ui().draw_outlined_box(container(), x1, y1, x2, y2, rgb_t(0xef, 0x12, 0x47, 0x7b)); - texture_ptr_vector const &t_texture(m_is_swlist ? m_cache->sw_toolbar_texture() : m_cache->toolbar_texture()); - bitmap_vector const &t_bitmap(m_is_swlist ? m_cache->sw_toolbar_bitmap() : m_cache->toolbar_bitmap()); + // cache metrics and bitmaps if necessary + if (m_toolbar_button_vbounds.first >= m_toolbar_button_vbounds.second) + { + m_toolbar_button_vbounds.first = y1 + tb_border(); + m_toolbar_button_vbounds.second = y2 - tb_border(); + float const button_height(m_toolbar_button_vbounds.second - m_toolbar_button_vbounds.first); + m_toolbar_button_width = button_height * float(x_aspect()); + m_toolbar_button_spacing = m_toolbar_button_width * 1.5F; + float const total_width((float(toolbar_count) + (float(toolbar_count - 1) * 0.5F)) * m_toolbar_button_width); + m_toolbar_backtrack_left = x2 - lr_border() - m_toolbar_button_width; + m_toolbar_main_left = (std::min)(m_toolbar_backtrack_left - (float(toolbar_count) * m_toolbar_button_spacing), (x1 + x2 - total_width) * 0.5F); + m_cache.cache_toolbar(machine(), m_toolbar_button_width, button_height); + } - auto const num_valid(std::count_if(std::begin(t_bitmap), std::end(t_bitmap), [](bitmap_argb32 const &e) { return e.valid(); })); + // tooltip needs to be above for pen/touch to avoid being obscured + float tooltip_pos; + switch (pointer_type()) + { + case ui_event::pointer::PEN: + case ui_event::pointer::TOUCH: + tooltip_pos = m_toolbar_button_vbounds.first - line_height() - (2.0F * tb_border()); + break; + default: + tooltip_pos = m_toolbar_button_vbounds.second + line_height() + tb_border(); + break; + } - float const space_x = (y2 - y1) * aspect; - float const total = (float(num_valid) * space_x) + (float(num_valid - 1) * 0.001f); - x1 += (x2 - x1) * 0.5f - total * 0.5f; - x2 = x1 + space_x; - for (int z = 0; z < UI_TOOLBAR_BUTTONS; ++z) { - if (t_bitmap[z].valid()) + // add backtrack button + bool const hovered(pointer_in_rect(m_toolbar_backtrack_left, m_toolbar_button_vbounds.first, m_toolbar_backtrack_left + m_toolbar_button_width, m_toolbar_button_vbounds.second)); + bool const tracked((pointer_action::TOOLBAR_TRACK == m_pointer_action) && (0 > m_clicked_line)); + rgb_t const color((hovered && tracked) ? rgb_t::white() : rgb_t(0xffcccccc)); + if (tracked || (hovered && !(flags & PROCESS_NOINPUT) && pointer_idle())) { - rgb_t color(0xEFEFEFEF); - if (mouse_in_rect(x1, y1, x2, y2)) - { - set_hover(HOVER_B_FAV + z); - color = rgb_t::white(); - float ypos = y2 + ui().get_line_height() + 2.0f * ui().box_tb_border(); - ui().draw_text_box(container(), _(hover_msg[z]), ui::text_layout::CENTER, 0.5f, ypos, ui().colors().background_color()); - } + ui().draw_text_box( + container(), + have_parent ? _("Return to Previous Menu") : _("Exit"), + text_layout::text_justify::RIGHT, 1.0F - lr_border(), tooltip_pos, + ui().colors().background_color()); + } + container().add_quad( + m_toolbar_backtrack_left, m_toolbar_button_vbounds.first, m_toolbar_backtrack_left + m_toolbar_button_width, m_toolbar_button_vbounds.second, + color, + m_cache.toolbar_textures()[2 * (have_parent ? TOOLBAR_BITMAP_PREVMENU : TOOLBAR_BITMAP_EXIT)].get(), + PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + } - container().add_quad(x1, y1, x2, y2, color, t_texture[z].get(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); - x1 += space_x + ((z < UI_TOOLBAR_BUTTONS - 1) ? 0.001f : 0.0f); - x2 = x1 + space_x; + // now add the other buttons + for (int z = 0; toolbar_count > z; ++z) + { + auto const [bitmap, action, need_selection] = toolbar_bitmaps[z]; + float const button_left(m_toolbar_main_left + (float(z) * m_toolbar_button_spacing)); + float const button_right(button_left + m_toolbar_button_width); + bool const enabled(!need_selection || get_selection_ptr()); + bool const hovered(pointer_in_rect(button_left, m_toolbar_button_vbounds.first, button_right, m_toolbar_button_vbounds.second)); + bool const tracked((pointer_action::TOOLBAR_TRACK == m_pointer_action) && (z == m_clicked_line)); + rgb_t color((hovered && tracked && enabled) ? rgb_t::white() : rgb_t(0xffcccccc)); + if (tracked || (hovered && !(flags & PROCESS_NOINPUT) && pointer_idle())) + { + ui().draw_text_box( + container(), + _(hover_msg[bitmap]), + text_layout::text_justify::CENTER, (button_left + button_right) * 0.5F, tooltip_pos, + ui().colors().background_color()); } + container().add_quad( + button_left, m_toolbar_button_vbounds.first, button_right, m_toolbar_button_vbounds.second, + color, + m_cache.toolbar_textures()[(2 * bitmap) + (enabled ? 0 : 1)].get(), + PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } } @@ -1304,16 +1524,16 @@ void menu_select_launch::draw_toolbar(float x1, float y1, float x2, float y2) void menu_select_launch::draw_star(float x0, float y0) { - float y1 = y0 + ui().get_line_height(); - float x1 = x0 + ui().get_line_height() * container().manager().ui_aspect(&container()); - container().add_quad(x0, y0, x1, y1, rgb_t::white(), m_cache->star_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_PACKABLE); -} - - -void menu_select_launch::set_pressed() -{ - (m_repeat == 0) ? m_repeat = osd_ticks() + osd_ticks_per_second() / 2 : m_repeat = osd_ticks() + osd_ticks_per_second() / 4; - m_pressed = true; + if (TOOLBAR_BITMAP_FAVORITE < m_cache.toolbar_textures().size()) + { + float const y1 = y0 + line_height(); + float const x1 = x0 + line_height() * container().manager().ui_aspect(&container()); + container().add_quad( + x0, y0, x1, y1, + rgb_t::white(), + m_cache.toolbar_textures()[TOOLBAR_BITMAP_FAVORITE].get(), + PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_PACKABLE); + } } @@ -1326,9 +1546,8 @@ void menu_select_launch::draw_icon(int linenum, void *selectedref, float x0, flo render_texture *const icon(get_icon_texture(linenum, selectedref)); if (icon) { - float const ud_arrow_width = ui().get_line_height() * container().manager().ui_aspect(&container()); - float const x1 = x0 + ud_arrow_width; - float const y1 = y0 + ui().get_line_height(); + float const x1 = x0 + ud_arrow_width(); + float const y1 = y0 + line_height(); container().add_quad(x0, y0, x1, y1, rgb_t::white(), icon, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); }; } @@ -1338,24 +1557,23 @@ void menu_select_launch::draw_icon(int linenum, void *selectedref, float x0, flo // get title and search path for right panel //------------------------------------------------- -void menu_select_launch::get_title_search(std::string &snaptext, std::string &searchstr) +std::string menu_select_launch::get_arts_searchpath() { - // get arts title text - snaptext.assign(_(arts_info[m_image_view].first)); + std::string searchstr; // get search path std::string addpath; if (m_image_view == SNAPSHOT_VIEW) { emu_options moptions; - searchstr = machine().options().value(arts_info[m_image_view].second); - addpath = moptions.value(arts_info[m_image_view].second); + searchstr = machine().options().value(std::get<2>(ARTS_INFO[m_image_view])); + addpath = moptions.value(std::get<2>(ARTS_INFO[m_image_view])); } else { ui_options moptions; - searchstr = ui().options().value(arts_info[m_image_view].second); - addpath = moptions.value(arts_info[m_image_view].second); + searchstr = ui().options().value(std::get<2>(ARTS_INFO[m_image_view])); + addpath = moptions.value(std::get<2>(ARTS_INFO[m_image_view])); } std::string tmp(searchstr); @@ -1370,20 +1588,304 @@ void menu_select_launch::get_title_search(std::string &snaptext, std::string &se while (path_iter.next(c_path)) searchstr.append(";").append(curpath).append(PATH_SEPARATOR).append(c_path); } + + return searchstr; } //------------------------------------------------- -// handle keys for main menu +// handle UI input events for main menu //------------------------------------------------- -void menu_select_launch::handle_keys(uint32_t flags, int &iptkey) +bool menu_select_launch::handle_events(u32 flags, event &ev) { - bool const ignorepause = stack_has_special_main_menu(); + // loop while we have interesting events + bool stop(false), need_update(false), search_changed(false); + ui_event local_menu_event; + while (!stop && machine().ui_input().pop_event(&local_menu_event)) + { + switch (local_menu_event.event_type) + { + // deal with pointer-like input (mouse, pen, touch, etc.) + case ui_event::type::POINTER_UPDATE: + { + auto const [key, redraw] = handle_pointer_update(flags, local_menu_event); + need_update = need_update || redraw; + if (IPT_INVALID != key) + { + ev.iptkey = key; + stop = true; + } + } + break; + + // pointer left the normal way, possibly releasing buttons + case ui_event::type::POINTER_LEAVE: + { + auto const [key, redraw] = handle_pointer_leave(flags, local_menu_event); + need_update = need_update || redraw; + if (IPT_INVALID != key) + { + ev.iptkey = key; + stop = true; + } + } + break; + + // pointer left in some abnormal way - cancel any associated actions + case ui_event::type::POINTER_ABORT: + { + auto const [key, redraw] = handle_pointer_abort(flags, local_menu_event); + need_update = need_update || redraw; + if (IPT_INVALID != key) + { + ev.iptkey = key; + stop = true; + } + } + break; + // caught scroll event + case ui_event::type::MOUSE_WHEEL: + if ((&machine().render().ui_target() == local_menu_event.target) && pointer_idle() && !m_ui_error) + { + // check whether it's over something scrollable + float x, y; + bool const hit(local_menu_event.target->map_point_container(local_menu_event.mouse_x, local_menu_event.mouse_y, container(), x, y)); + if (!hit) + { + m_wheel_movement = 0; + break; + } + focused_menu hover; + if ((x >= m_primary_items_hbounds.first) && (x < m_primary_items_hbounds.second) && (y >= m_primary_items_top) && (y < (m_primary_items_top + (float(m_primary_lines) * line_height())))) + { + hover = focused_menu::MAIN; + } + else if (show_left_panel() && (x >= m_left_items_hbounds.first) && (x < m_left_items_hbounds.second) && (y >= m_left_items_top) && (y < (m_left_items_top + (float(m_left_visible_lines) * m_info_line_height)))) + { + hover = focused_menu::LEFT; + } + else if (show_right_panel() && (x >= m_right_content_hbounds.first) && (x < m_right_content_hbounds.second) && (y >= m_right_content_vbounds.first) && (y < m_right_content_vbounds.second)) + { + hover = focused_menu::RIGHTBOTTOM; + } + else + { + m_wheel_movement = 0; + break; + } + + // clear out leftovers if it isn't the last thing to be scrolled + if (hover != m_wheel_target) + m_wheel_movement = 0; + m_wheel_target = hover; + + // the value is scaled to 120 units per "click" + m_wheel_movement += local_menu_event.zdelta * local_menu_event.num_lines; + int const lines((m_wheel_movement + ((0 < local_menu_event.zdelta) ? 36 : -36)) / 120); + if (!lines) + break; + m_wheel_movement -= lines * 120; + + switch (hover) + { + case focused_menu::MAIN: + if (lines > 0) + { + if ((selected_index() >= m_available_items) || is_first_selected()) + break; + stop = true; + ev.iptkey = IPT_CUSTOM; // stop processing events so info can be rebuilt + set_selected_index(selected_index() - lines); + if (selected_index() < top_line + (top_line != 0)) + top_line -= lines; + } + else + { + if (selected_index() >= (m_available_items - 1)) + break; + stop = true; + ev.iptkey = IPT_CUSTOM; // stop processing events so info can be rebuilt + set_selected_index(std::min(selected_index() - lines, m_available_items - 1)); + if (selected_index() >= top_line + m_visible_items + (top_line != 0)) + top_line -= lines; + } + break; + case focused_menu::LEFT: + { + m_left_visible_top = std::clamp(m_left_visible_top - lines, 0, m_left_item_count - m_left_visible_lines); + int const first(left_at_top() ? 0 : (m_left_visible_top + 1)); + int const last(m_left_visible_top + m_left_visible_lines - (left_at_bottom() ? 1 : 2)); + m_filter_highlight = std::clamp(m_filter_highlight, first, last); + m_filter_highlight = std::clamp(m_filter_highlight - lines, 0, m_left_item_count - 1); + } + break; + case focused_menu::RIGHTBOTTOM: + if (RP_INFOS == m_right_panel) + m_topline_datsview -= lines; + break; + case focused_menu::RIGHTTOP: + break; // never gets here + } + } + break; + + // text input goes to the search field unless there's an error message displayed + case ui_event::type::IME_CHAR: + if (!pointer_idle()) + break; + + if (exclusive_input_pressed(ev.iptkey, IPT_UI_FOCUS_NEXT, 0) || exclusive_input_pressed(ev.iptkey, IPT_UI_FOCUS_PREV, 0)) + { + stop = true; + } + else if (m_ui_error) + { + ev.iptkey = IPT_CUSTOM; + stop = true; + } + else if (accept_search()) + { + if (input_character(m_search, local_menu_event.ch, uchar_is_printable)) + search_changed = true; + } + break; + + // ignore everything else + default: + break; + } + + // need to update search before processing certain kinds of events, but others don't matter + if (search_changed) + { + switch (machine().ui_input().peek_event_type()) + { + case ui_event::type::MOUSE_WHEEL: + case ui_event::type::POINTER_UPDATE: + case ui_event::type::POINTER_LEAVE: + case ui_event::type::POINTER_ABORT: + stop = true; + break; + case ui_event::type::NONE: + case ui_event::type::WINDOW_FOCUS: + case ui_event::type::WINDOW_DEFOCUS: + case ui_event::type::IME_CHAR: + break; + } + } + } + + // deal with repeating main scroll arrows + if ((pointer_action::MAIN_TRACK_LINE == m_pointer_action) && (is_main_up_arrow(m_clicked_line) || (is_main_down_arrow(m_clicked_line)))) + { + if (check_scroll_repeat(m_primary_items_top, m_primary_items_hbounds, line_height())) + { + if (!m_clicked_line) + { + // scroll up + --top_line; + if (main_at_top()) + m_pointer_action = pointer_action::NONE; + } + else + { + // scroll down + ++top_line; + if (main_at_bottom()) + m_pointer_action = pointer_action::NONE; + } + if (main_force_visible_selection()) + { + if (IPT_INVALID == ev.iptkey) + ev.iptkey = IPT_CUSTOM; // stop processing events so the info pane can be rebuilt + } + need_update = true; + } + } + + // deal with repeating info view arrows + if (pointer_action::RIGHT_TRACK_ARROW == m_pointer_action) + { + float const left(m_clicked_line ? (m_right_content_hbounds.second - lr_border() - lr_arrow_width()) : (m_right_content_hbounds.first + lr_border())); + float const right(m_clicked_line ? (m_right_content_hbounds.second - lr_border()) : (m_right_content_hbounds.first + lr_border() + lr_arrow_width())); + if (pointer_in_rect(left, right_arrows_top(), right, right_arrows_bottom())) + { + if (std::chrono::steady_clock::now() >= m_scroll_repeat) + { + m_scroll_repeat += std::chrono::milliseconds(200); + if (!(m_clicked_line ? next_right_panel_view() : previous_right_panel_view()).second) + m_pointer_action = pointer_action::NONE; + need_update = true; + } + } + } + + // deal with repeating filter scroll arrows + if ((pointer_action::LEFT_TRACK_LINE == m_pointer_action) && (is_left_up_arrow(m_clicked_line) || (is_left_down_arrow(m_clicked_line)))) + { + if (check_scroll_repeat(m_left_items_top, m_left_items_hbounds, m_info_line_height)) + { + if (!m_clicked_line) + { + // scroll up + --m_left_visible_top; + m_filter_highlight = std::min(m_left_visible_top + m_left_visible_lines - 2, m_filter_highlight); + if (left_at_top()) + m_pointer_action = pointer_action::NONE; + } + else + { + // scroll down + ++m_left_visible_top; + m_filter_highlight = std::max(m_left_visible_top + 1, m_filter_highlight); + if (left_at_bottom()) + m_pointer_action = pointer_action::NONE; + } + need_update = true; + } + } + + // deal with repeating filter scroll arrows + if (pointer_action::RIGHT_TRACK_LINE == m_pointer_action) + { + if (check_scroll_repeat(m_right_content_vbounds.first, m_right_content_hbounds, m_info_line_height)) + { + if (!m_clicked_line) + { + // scroll up + --m_topline_datsview; + if (info_at_top()) + m_pointer_action = pointer_action::NONE; + } + else + { + // scroll down + ++m_topline_datsview; + if (info_at_bottom()) + m_pointer_action = pointer_action::NONE; + } + need_update = true; + } + } + + if (search_changed) + reset(reset_options::SELECT_FIRST); + + return need_update; +} + + +//------------------------------------------------- +// handle keys for main menu +//------------------------------------------------- + +bool menu_select_launch::handle_keys(u32 flags, int &iptkey) +{ // bail if no items if (item_count() == 0) - return; + return false; // if we hit select, return true or pop the stack, depending on the item if (exclusive_input_pressed(iptkey, IPT_UI_SELECT, 0)) @@ -1394,119 +1896,198 @@ void menu_select_launch::handle_keys(uint32_t flags, int &iptkey) } else if (m_focus == focused_menu::LEFT) { - m_prev_selected = nullptr; - filter_selected(); + filter_selected(m_filter_highlight); } - if (is_last_selected() && (m_focus == focused_menu::MAIN)) - { - iptkey = IPT_UI_CANCEL; - stack_pop(); - } - return; + return false; } - if (exclusive_input_pressed(iptkey, IPT_UI_CANCEL, 0)) + if (exclusive_input_pressed(iptkey, IPT_UI_BACK, 0)) { if (m_ui_error) { // dismiss error + return false; + } + else if (!is_special_main_menu() && m_search.empty()) + { + // pop the stack if this isn't the root session menu + stack_pop(); + return false; } - else if (menu_has_search_active()) + } + + if (exclusive_input_pressed(iptkey, IPT_UI_CANCEL, 0)) + { + if (!m_search.empty()) { // escape pressed with non-empty search text clears it m_search.clear(); - reset(reset_options::SELECT_FIRST); + reset(reset_options::REMEMBER_REF); } - else + else if (is_special_main_menu()) { - // otherwise pop the stack + // this is the root session menu, exit stack_pop(); + machine().schedule_exit(); } - return; + return false; } // validate the current selection validate_selection(1); - - // swallow left/right keys if they are not appropriate - bool const ignoreleft = ((selected_item().flags & FLAG_LEFT_ARROW) == 0); - bool const ignoreright = ((selected_item().flags & FLAG_RIGHT_ARROW) == 0); - bool const leftclose = (ui_globals::panels_status == HIDE_BOTH || ui_globals::panels_status == HIDE_LEFT_PANEL); - bool const rightclose = (ui_globals::panels_status == HIDE_BOTH || ui_globals::panels_status == HIDE_RIGHT_PANEL); + bool updated(false); // accept left/right keys as-is with repeat - if (!ignoreleft && exclusive_input_pressed(iptkey, IPT_UI_LEFT, (flags & PROCESS_LR_REPEAT) ? 6 : 0)) + if (exclusive_input_pressed(iptkey, IPT_UI_LEFT, (flags & PROCESS_LR_REPEAT) ? 6 : 0)) { - // Swap the right panel if (m_focus == focused_menu::RIGHTTOP) - ui_globals::rpanel = RP_IMAGES; - return; + { + // Swap the right panel and swallow it + iptkey = IPT_INVALID; + if (right_panel() != RP_IMAGES) + { + m_right_panel = RP_IMAGES; + updated = true; + } + } + else if (show_right_panel()) + { + // Swap the right panel page and swallow it + if (right_panel() == RP_IMAGES) + { + iptkey = IPT_INVALID; + if (previous_image_view().first) + updated = true; + } + else if (right_panel() == RP_INFOS) + { + iptkey = IPT_INVALID; + if (previous_info_view().first) + updated = true; + } + } } - if (!ignoreright && exclusive_input_pressed(iptkey, IPT_UI_RIGHT, (flags & PROCESS_LR_REPEAT) ? 6 : 0)) + // swallow left/right keys if they are not appropriate + if (exclusive_input_pressed(iptkey, IPT_UI_RIGHT, (flags & PROCESS_LR_REPEAT) ? 6 : 0)) { - // Swap the right panel if (m_focus == focused_menu::RIGHTTOP) - ui_globals::rpanel = RP_INFOS; - return; + { + // Swap the right panel and swallow it + iptkey = IPT_INVALID; + if (right_panel() != RP_INFOS) + { + m_right_panel = RP_INFOS; + updated = true; + } + } + else if (show_right_panel()) + { + // Swap the right panel page and swallow it + if (right_panel() == RP_IMAGES) + { + iptkey = IPT_INVALID; + if (next_image_view().first) + updated = true; + } + else if (right_panel() == RP_INFOS) + { + iptkey = IPT_INVALID; + if (next_info_view().first) + updated = true; + } + } } // up backs up by one item if (exclusive_input_pressed(iptkey, IPT_UI_UP, 6)) { - if (!leftclose && m_focus == focused_menu::LEFT) + if (m_focus == focused_menu::LEFT) { - return; + // swallow it + iptkey = IPT_INVALID; + if (m_filter_highlight) + { + --m_filter_highlight; + updated = true; + } } - else if (!rightclose && m_focus == focused_menu::RIGHTBOTTOM) + else if ((m_focus == focused_menu::RIGHTTOP) || (m_focus == focused_menu::RIGHTBOTTOM)) { - m_topline_datsview--; - return; + // swallow it + iptkey = IPT_INVALID; + if (m_topline_datsview) + { + m_topline_datsview--; + updated = true; + } } else if (selected_index() == m_available_items + 1 || is_first_selected() || m_ui_error) { - return; + return updated; + } + else + { + set_selected_index(selected_index() - 1); + if (selected_index() == top_line && top_line != 0) + top_line--; } - - set_selected_index(selected_index() - 1); - - if (selected_index() == top_line && top_line != 0) - top_line--; } // down advances by one item if (exclusive_input_pressed(iptkey, IPT_UI_DOWN, 6)) { - if (!leftclose && m_focus == focused_menu::LEFT) + if (m_focus == focused_menu::LEFT) { - return; + // swallow it + iptkey = IPT_INVALID; + if ((m_left_item_count - 1) > m_filter_highlight) + { + ++m_filter_highlight; + updated = true; + } } - else if (!rightclose && m_focus == focused_menu::RIGHTBOTTOM) + else if ((m_focus == focused_menu::RIGHTTOP) || (m_focus == focused_menu::RIGHTBOTTOM)) { + // swallow it + iptkey = IPT_INVALID; + updated = true; m_topline_datsview++; - return; } else if (is_last_selected() || selected_index() == m_available_items - 1 || m_ui_error) { - return; + return updated; + } + else + { + set_selected_index(selected_index() + 1); + if (selected_index() == top_line + m_visible_items + (top_line != 0)) + top_line++; } - - set_selected_index(selected_index() + 1); - if (selected_index() == top_line + m_visible_items + (top_line != 0)) - top_line++; } // page up backs up by m_visible_items if (exclusive_input_pressed(iptkey, IPT_UI_PAGE_UP, 6)) { - // Infos - if (!rightclose && m_focus == focused_menu::RIGHTBOTTOM) + if (m_focus == focused_menu::LEFT) { - m_topline_datsview -= m_right_visible_lines - 1; - return; + // Filters - swallow it + iptkey = IPT_INVALID; + if (!left_at_top()) + { + updated = true; + m_left_visible_top -= std::min(std::max(m_left_visible_lines - 3, 1), m_left_visible_top); + m_filter_highlight = std::min(m_left_visible_top + m_left_visible_lines - 2, m_filter_highlight); + } } - - if (selected_index() < m_available_items && !m_ui_error) + else if ((m_focus == focused_menu::RIGHTTOP) || (m_focus == focused_menu::RIGHTBOTTOM)) + { + // Infos - swallow it + iptkey = IPT_INVALID; + updated = true; + m_topline_datsview -= m_right_visible_lines - 3; + } + else if (selected_index() < m_available_items && !m_ui_error) { set_selected_index(std::max(selected_index() - m_visible_items, 0)); @@ -1517,14 +2098,25 @@ void menu_select_launch::handle_keys(uint32_t flags, int &iptkey) // page down advances by m_visible_items if (exclusive_input_pressed(iptkey, IPT_UI_PAGE_DOWN, 6)) { - // Infos - if (!rightclose && m_focus == focused_menu::RIGHTBOTTOM) + if (m_focus == focused_menu::LEFT) { - m_topline_datsview += m_right_visible_lines - 1; - return; + // Filters - swallow it + iptkey = IPT_INVALID; + if (!left_at_bottom()) + { + updated = true; + m_left_visible_top += std::min(std::max(m_left_visible_lines - 3, 1), m_left_item_count - m_left_visible_lines - m_left_visible_top); + m_filter_highlight = std::max(m_left_visible_top + 1, m_filter_highlight); + } } - - if (selected_index() < m_available_items && !m_ui_error) + else if ((m_focus == focused_menu::RIGHTTOP) || (m_focus == focused_menu::RIGHTBOTTOM)) + { + // Infos - swallow it + iptkey = IPT_INVALID; + updated = true; + m_topline_datsview += m_right_visible_lines - 3; + } + else if (selected_index() < m_available_items && !m_ui_error) { set_selected_index(std::min(selected_index() + m_visible_lines - 2 + (selected_index() == 0), m_available_items - 1)); @@ -1535,63 +2127,92 @@ void menu_select_launch::handle_keys(uint32_t flags, int &iptkey) // home goes to the start if (exclusive_input_pressed(iptkey, IPT_UI_HOME, 0)) { - if (!leftclose && m_focus == focused_menu::LEFT) + if (m_focus == focused_menu::LEFT) { - return; + // Filters - swallow it + iptkey = IPT_INVALID; + if (m_filter_highlight) + { + updated = true; + m_left_visible_top = 0; + m_filter_highlight = 0; + } } - else if (!rightclose && m_focus == focused_menu::RIGHTBOTTOM) + else if ((m_focus == focused_menu::RIGHTTOP) || (m_focus == focused_menu::RIGHTBOTTOM)) { + // Infos - swallow it + iptkey = IPT_INVALID; + if (m_topline_datsview) + updated = true; m_topline_datsview = 0; - return; } - - if (selected_index() < m_available_items && !m_ui_error) + else if (selected_index() < m_available_items && !m_ui_error) + { select_first_item(); + } } // end goes to the last if (exclusive_input_pressed(iptkey, IPT_UI_END, 0)) { - if (!leftclose && m_focus == focused_menu::LEFT) + if (m_focus == focused_menu::LEFT) { - return; + // Filters - swallow it + iptkey = IPT_INVALID; + if ((m_left_item_count - 1) != m_filter_highlight) + { + updated = true; + if (!left_at_bottom()) + m_left_visible_top = m_left_item_count - m_left_visible_lines; + m_filter_highlight = m_left_item_count - 1; + } } - else if (!rightclose && m_focus == focused_menu::RIGHTBOTTOM) + else if ((m_focus == focused_menu::RIGHTTOP) || (m_focus == focused_menu::RIGHTBOTTOM)) { + // Infos - swallow it + iptkey = IPT_INVALID; + updated = true; m_topline_datsview = m_total_lines; - return; } - - if (selected_index() < m_available_items && !m_ui_error) + else if (selected_index() < m_available_items && !m_ui_error) + { set_selected_index(top_line = m_available_items - 1); + } } // focus next rotates throw targets forward if (exclusive_input_pressed(iptkey, IPT_UI_FOCUS_NEXT, 12)) { if (!m_ui_error) + { rotate_focus(1); + updated = true; + } } // focus next rotates throw targets forward if (exclusive_input_pressed(iptkey, IPT_UI_FOCUS_PREV, 12)) { if (!m_ui_error) + { rotate_focus(-1); - } - - // pause enables/disables pause - if (!m_ui_error && !ignorepause && exclusive_input_pressed(iptkey, IPT_UI_PAUSE, 0)) - { - if (machine().paused()) - machine().resume(); - else - machine().pause(); + updated = true; + } } // handle a toggle cheats request if (!m_ui_error && machine().ui_input().pressed_repeat(IPT_UI_TOGGLE_CHEAT, 0)) - mame_machine_manager::instance()->cheat().set_enable(!mame_machine_manager::instance()->cheat().enabled()); + mame_machine_manager::instance()->cheat().set_enable(!mame_machine_manager::instance()->cheat().enabled(), true); + + // handle pasting text into the search + if (exclusive_input_pressed(iptkey, IPT_UI_PASTE, 0)) + { + if (!m_ui_error && accept_search()) + { + if (paste_text(m_search, uchar_is_printable)) + reset(reset_options::SELECT_FIRST); + } + } // see if any other UI keys are pressed if (iptkey == IPT_INVALID) @@ -1605,366 +2226,1034 @@ void menu_select_launch::handle_keys(uint32_t flags, int &iptkey) { case IPT_UI_FOCUS_NEXT: case IPT_UI_FOCUS_PREV: - continue; - case IPT_UI_LEFT: - if (ignoreleft) - continue; - break; - case IPT_UI_RIGHT: - if (ignoreright) - continue; - break; case IPT_UI_PAUSE: - if (ignorepause) - continue; - break; + continue; } if (exclusive_input_pressed(iptkey, code, 0)) break; } } + return updated; } //------------------------------------------------- -// handle input events for main menu +// handle pointer input for main menu //------------------------------------------------- -void menu_select_launch::handle_events(uint32_t flags, event &ev) +std::tuple<int, bool, bool> menu_select_launch::custom_pointer_updated(bool changed, ui_event const &uievt) { - if (m_pressed) + if (ui_event::type::POINTER_ABORT == uievt.event_type) + return std::make_tuple(IPT_INVALID, false, false); + + // if nothing's happening, check for clicks + if (pointer_idle()) { - bool const pressed = mouse_pressed(); - int32_t target_x, target_y; - bool button; - render_target *const mouse_target = machine().ui_input().find_mouse(&target_x, &target_y, &button); - if (mouse_target && button && (hover() == HOVER_ARROW_DOWN || hover() == HOVER_ARROW_UP)) - { - if (pressed) - machine().ui_input().push_mouse_down_event(mouse_target, target_x, target_y); - } + if ((uievt.pointer_pressed & 0x01) && !(uievt.pointer_buttons & ~u32(0x01))) + return handle_primary_down(changed, uievt); + else if ((uievt.pointer_pressed & 0x02) && !(uievt.pointer_buttons & ~u32(0x02))) + return handle_right_down(changed, uievt); + else if ((uievt.pointer_pressed & 0x04) && !(uievt.pointer_buttons & ~u32(0x04))) + return handle_middle_down(changed, uievt); else + return std::make_tuple(IPT_INVALID, false, false); + } + + // handle in-progress actions + switch (m_pointer_action) + { + case pointer_action::NONE: + break; + case pointer_action::MAIN_TRACK_LINE: + return update_main_track_line(changed, uievt); + case pointer_action::MAIN_TRACK_RBUTTON: + return update_main_track_rbutton(changed, uievt); + case pointer_action::MAIN_DRAG: + return update_main_drag(changed, uievt); + case pointer_action::LEFT_TRACK_LINE: + return update_left_track_line(changed, uievt); + case pointer_action::LEFT_DRAG: + return update_left_drag(changed, uievt); + case pointer_action::RIGHT_TRACK_TAB: + return update_right_track_tab(changed, uievt); + case pointer_action::RIGHT_TRACK_ARROW: + return update_right_track_arrow(changed, uievt); + case pointer_action::RIGHT_TRACK_LINE: + return update_right_track_line(changed, uievt); + case pointer_action::RIGHT_SWITCH: + return update_right_switch(changed, uievt); + case pointer_action::RIGHT_DRAG: + return update_right_drag(changed, uievt); + case pointer_action::TOOLBAR_TRACK: + return update_toolbar_track(changed, uievt); + case pointer_action::DIVIDER_TRACK: + return update_divider_track(changed, uievt); + } + return std::make_tuple(IPT_INVALID, false, false); +} + + +//------------------------------------------------- +// handle primary click +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::handle_primary_down(bool changed, ui_event const &uievt) +{ + if (m_ui_error) + { + m_ui_error = false; + m_error_text.clear(); + return std::make_tuple(IPT_INVALID, true, true); + } + + auto const [x, y] = pointer_location(); + + // check main item list + if ((x >= m_primary_items_hbounds.first) && (x < m_primary_items_hbounds.second) && (y > m_primary_items_top)) + { + int const line((y - m_primary_items_top) / line_height()); + if (line < (m_primary_lines + m_skip_main_items)) { - reset_pressed(); + int key(IPT_INVALID); + m_pointer_action = pointer_action::MAIN_TRACK_LINE; + m_base_pointer = m_last_pointer = std::make_pair(x, y); + m_clicked_line = line; + if (is_main_up_arrow(line)) + { + // top line is a scroll arrow + --top_line; + if (main_force_visible_selection()) + key = IPT_CUSTOM; // stop processing events so the info pane can be rebuilt + if (!main_at_top()) + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(300); + else + m_pointer_action = pointer_action::NONE; + } + else if (is_main_down_arrow(line)) + { + // bottom line is a scroll arrow + ++top_line; + if (main_force_visible_selection()) + key = IPT_CUSTOM; // stop processing events so the info pane can be rebuilt + if (!main_at_bottom()) + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(300); + else + m_pointer_action = pointer_action::NONE; + } + return std::make_tuple(key, true, true); } } - // loop while we have interesting events - bool stop(false), search_changed(false); - ui_event local_menu_event; - while (!stop && machine().ui_input().pop_event(&local_menu_event)) + // check filter list + if (show_left_panel() && (x >= m_left_items_hbounds.first) && (x < m_left_items_hbounds.second) && (y >= m_left_items_top)) { - switch (local_menu_event.event_type) + int const line((y - m_left_items_top) / m_info_line_height); + if (line < m_left_visible_lines) { - // if we are hovering over a valid item, select it with a single click - case ui_event::type::MOUSE_DOWN: - if (m_ui_error) + m_pointer_action = pointer_action::LEFT_TRACK_LINE; + m_base_pointer = m_last_pointer = std::make_pair(x, y); + m_clicked_line = line; + if (is_left_up_arrow(line)) { - ev.iptkey = IPT_OTHER; - stop = true; + // top line is a scroll arrow + --m_left_visible_top; + m_filter_highlight = std::min(m_left_visible_top + m_left_visible_lines - 2, m_filter_highlight); + if (!left_at_top()) + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(300); + else + m_pointer_action = pointer_action::NONE; + } + else if (is_left_down_arrow(line)) + { + // bottom line is a scroll arrow + ++m_left_visible_top; + m_filter_highlight = std::max(m_left_visible_top + 1, m_filter_highlight); + if (!left_at_bottom()) + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(300); + else + m_pointer_action = pointer_action::NONE; } else { - if (hover() >= 0 && hover() < item_count()) - { - if (hover() >= m_available_items - 1 && selected_index() < m_available_items) - m_prev_selected = get_selection_ref(); - set_selected_index(hover()); - m_focus = focused_menu::MAIN; - } - else if (hover() == HOVER_ARROW_UP) - { - set_selected_index(std::max(selected_index() - m_visible_items, 0)); - top_line -= m_visible_items - (top_line + m_visible_lines == m_available_items); - set_pressed(); - } - else if (hover() == HOVER_ARROW_DOWN) - { - set_selected_index(std::min(selected_index() + m_visible_lines - 2 + (selected_index() == 0), m_available_items - 1)); - top_line += m_visible_lines - 2; - set_pressed(); - } - else if (hover() == HOVER_UI_RIGHT) - ev.iptkey = IPT_UI_RIGHT; - else if (hover() == HOVER_UI_LEFT) - ev.iptkey = IPT_UI_LEFT; - else if (hover() == HOVER_DAT_DOWN) - m_topline_datsview += m_right_visible_lines - 1; - else if (hover() == HOVER_DAT_UP) - m_topline_datsview -= m_right_visible_lines - 1; - else if (hover() == HOVER_LPANEL_ARROW) + // ignore multi-click actions - someone hammered on the scroll arrow and hit the end + if (1 < uievt.pointer_clicks) + m_pointer_action = pointer_action::NONE; + } + return std::make_tuple(IPT_INVALID, true, true); + } + } + + // check right panel content + if (show_right_panel()) + { + // check right tabs + if ((x >= right_panel_left()) && (x < right_panel_right()) && (y >= m_primary_vbounds.first) && (y < m_right_tabs_bottom)) + { + int const tab((x - right_panel_left()) / right_tab_width()); + if (tab != m_right_panel) + { + m_pointer_action = pointer_action::RIGHT_TRACK_TAB; + m_clicked_line = tab; + } + return std::make_tuple(IPT_INVALID, true, pointer_action::NONE != m_pointer_action); + } + + if ((x >= m_right_content_hbounds.first) && (x < m_right_content_hbounds.second)) + { + // check right panel heading arrows + if ((ui_event::pointer::TOUCH != uievt.pointer_type) && (y >= right_arrows_top()) && (y < right_arrows_bottom())) + { + if ((x >= (m_right_content_hbounds.first + lr_border())) && (x < (m_right_content_hbounds.first + lr_border() + lr_arrow_width()))) { - if (get_focus() == focused_menu::LEFT) + auto const [updated, notend] = previous_right_panel_view(); + if (notend) { - set_focus(focused_menu::MAIN); - select_prev(); + m_pointer_action = pointer_action::RIGHT_TRACK_ARROW; + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(600); + m_base_pointer = m_last_pointer = std::make_pair(x, y); + m_clicked_line = 0; } - - if (ui_globals::panels_status == HIDE_LEFT_PANEL) - ui_globals::panels_status = SHOW_PANELS; - else if (ui_globals::panels_status == HIDE_BOTH) - ui_globals::panels_status = HIDE_RIGHT_PANEL; - else if (ui_globals::panels_status == SHOW_PANELS) - ui_globals::panels_status = HIDE_LEFT_PANEL; - else if (ui_globals::panels_status == HIDE_RIGHT_PANEL) - ui_globals::panels_status = HIDE_BOTH; + return std::make_tuple(IPT_INVALID, true, updated); } - else if (hover() == HOVER_RPANEL_ARROW) + else if ((x >= (m_right_content_hbounds.second - lr_border() - lr_arrow_width())) && (x < (m_right_content_hbounds.second - lr_border()))) { - if ((get_focus() == focused_menu::RIGHTTOP) || (get_focus() == focused_menu::RIGHTBOTTOM)) + auto const [updated, notend] = next_right_panel_view(); + if (notend) { - set_focus(focused_menu::MAIN); - select_prev(); + m_pointer_action = pointer_action::RIGHT_TRACK_ARROW; + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(600); + m_base_pointer = m_last_pointer = std::make_pair(x, y); + m_clicked_line = 1; } + return std::make_tuple(IPT_INVALID, true, updated); + } + } - if (ui_globals::panels_status == HIDE_RIGHT_PANEL) - ui_globals::panels_status = SHOW_PANELS; - else if (ui_globals::panels_status == HIDE_BOTH) - ui_globals::panels_status = HIDE_LEFT_PANEL; - else if (ui_globals::panels_status == SHOW_PANELS) - ui_globals::panels_status = HIDE_RIGHT_PANEL; - else if (ui_globals::panels_status == HIDE_LEFT_PANEL) - ui_globals::panels_status = HIDE_BOTH; + // check right panel heading touch swipe + if ((ui_event::pointer::TOUCH == uievt.pointer_type) && (y >= m_right_heading_top) && (y < (m_right_heading_top + line_height()))) + { + m_pointer_action = pointer_action::RIGHT_SWITCH; + m_base_pointer = m_last_pointer = std::make_pair(x, y); + m_clicked_line = 0; + if (right_panel() == RP_IMAGES) + { + m_clicked_line = m_image_view; } - else if (hover() == HOVER_B_FAV) + else if (right_panel() == RP_INFOS) { - ev.iptkey = IPT_UI_FAVORITES; - stop = true; + ui_software_info const *software; + ui_system_info const *system; + get_selection(software, system); + if (software && !software->startempty) + m_clicked_line = ui_globals::cur_sw_dats_view; + else if (system || (software && software->driver)) + m_clicked_line = ui_globals::curdats_view; } - else if (hover() == HOVER_B_EXPORT) + return std::make_tuple(IPT_INVALID, true, true); + } + + // check info scroll + if ((RP_INFOS == m_right_panel) && (y >= m_right_content_vbounds.first) && (y < (m_right_content_vbounds.first + (float(m_right_visible_lines) * m_info_line_height)))) + { + int const line((y - m_right_content_vbounds.first) / m_info_line_height); + if (line < m_right_visible_lines) { - inkey_export(); - stop = true; + bool redraw(false); + m_base_pointer = m_last_pointer = std::make_pair(x, y); + m_clicked_line = line; + if (!line && !info_at_top()) + { + redraw = true; + --m_topline_datsview; + if (!info_at_top()) + { + m_pointer_action = pointer_action::RIGHT_TRACK_LINE; + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(300); + } + } + else if ((line == (m_right_visible_lines - 1)) && !info_at_bottom()) + { + redraw = true; + ++m_topline_datsview; + if (!info_at_bottom()) + { + m_pointer_action = pointer_action::RIGHT_TRACK_LINE; + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(300); + } + } + else if (ui_event::pointer::TOUCH == uievt.pointer_type) + { + m_pointer_action = pointer_action::RIGHT_DRAG; + m_clicked_line = m_topline_datsview; + } + return std::make_tuple(IPT_INVALID, true, redraw); } - else if (hover() == HOVER_B_DATS) + } + } + } + + // check toolbar + if ((y >= m_toolbar_button_vbounds.first) && (y < m_toolbar_button_vbounds.second)) + { + if ((x >= m_toolbar_backtrack_left) && (x < (m_toolbar_backtrack_left + m_toolbar_button_width))) + { + m_pointer_action = pointer_action::TOOLBAR_TRACK; + m_clicked_line = -1; + return std::make_tuple(IPT_INVALID, true, true); + } + else + { + unsigned const toolbar_count(m_is_swlist ? std::size(SW_TOOLBAR_BITMAPS) : std::size(SYS_TOOLBAR_BITMAPS)); + float const button(std::floor((x - m_toolbar_main_left) / m_toolbar_button_spacing)); + int const n(button); + if ((n >= 0) && (n < toolbar_count) && (x < (m_toolbar_main_left + (button * m_toolbar_button_spacing) + m_toolbar_button_width))) + { + m_pointer_action = pointer_action::TOOLBAR_TRACK; + m_clicked_line = n; + return std::make_tuple(IPT_INVALID, true, true); + } + } + } + + // check dividers + if ((y >= m_primary_vbounds.first) && (y < m_primary_vbounds.second)) + { + if ((x >= left_divider_left()) && (x < left_divider_right())) + { + m_pointer_action = pointer_action::DIVIDER_TRACK; + m_clicked_line = 0; + return std::make_tuple(IPT_INVALID, true, true); + } + else if ((x >= right_divider_left()) && (x < right_divider_right())) + { + m_pointer_action = pointer_action::DIVIDER_TRACK; + m_clicked_line = 1; + return std::make_tuple(IPT_INVALID, true, true); + } + } + + return std::make_tuple(IPT_INVALID, false, false); +} + + +//------------------------------------------------- +// handle right click +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::handle_right_down(bool changed, ui_event const &uievt) +{ + if (m_ui_error) + return std::make_tuple(IPT_INVALID, false, false); + + // check main item list + auto const [x, y] = pointer_location(); + if ((x >= m_primary_items_hbounds.first) && (x < m_primary_items_hbounds.second) && (y > m_primary_items_top)) + { + int const line((y - m_primary_items_top) / line_height()); + if ((line < m_primary_lines) && !is_main_up_arrow(line) && (!is_main_down_arrow(line))) + { + m_pointer_action = pointer_action::MAIN_TRACK_RBUTTON; + m_base_pointer = m_last_pointer = std::make_pair(x, y); + m_clicked_line = line; + return std::make_tuple(IPT_INVALID, true, true); + } + } + + return std::make_tuple(IPT_INVALID, false, false); +} + + +//------------------------------------------------- +// handle middle click +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::handle_middle_down(bool changed, ui_event const &uievt) +{ + if (m_ui_error) + return std::make_tuple(IPT_INVALID, false, false); + + auto const [x, y] = pointer_location(); + if ((y >= m_primary_vbounds.first) && (y < m_primary_vbounds.second)) + { + if ((x >= left_divider_right()) && (x < right_divider_left())) + { + // main list + if (m_skip_main_items && (y >= (m_primary_items_top + ((float(m_primary_lines) + ((item(m_available_items).type() == menu_item_type::SEPARATOR) ? 0.5F : 0.0F)) * line_height())))) + { + if (selected_index() < m_available_items) { - inkey_dats(); - stop = true; + m_prev_selected = get_selection_ref(); + set_selected_index(m_available_items + 1); } - else if (hover() >= HOVER_RP_FIRST && hover() <= HOVER_RP_LAST) + } + else + { + if ((get_focus() != focused_menu::MAIN) || (selected_index() > m_available_items)) + select_prev(); + } + set_focus(focused_menu::MAIN); + } + else if ((x >= left_panel_left()) && (x < left_panel_right())) + { + // left panel + assert(show_left_panel()); + if ((get_focus() == focused_menu::MAIN) && (selected_index() <= m_available_items)) + m_prev_selected = get_selection_ref(); + set_focus(focused_menu::LEFT); + return std::make_tuple(IPT_INVALID, true, true); + } + else if ((x >= right_panel_left()) && (x < right_panel_right())) + { + // right panel + assert(show_right_panel()); + if ((get_focus() == focused_menu::MAIN) && (selected_index() <= m_available_items)) + m_prev_selected = get_selection_ref(); + set_focus((y < m_right_tabs_bottom) ? focused_menu::RIGHTTOP : focused_menu::RIGHTBOTTOM); + return std::make_tuple(IPT_INVALID, true, true); + } + } + + return std::make_tuple(IPT_INVALID, false, false); +} + + +//------------------------------------------------- +// track click on main item +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::update_main_track_line(bool changed, ui_event const &uievt) +{ + auto const [x, y] = pointer_location(); + float const itemtop(m_primary_items_top + (float(m_clicked_line) * line_height())); + float const itembottom(m_primary_items_top + (float(m_clicked_line + 1) * line_height())); + int key(IPT_INVALID); + bool redraw(false); + + if (is_main_up_arrow(m_clicked_line) || is_main_down_arrow(m_clicked_line)) + { + // top or bottom line is a scroll arrow + bool const reentered(reentered_rect(m_last_pointer.first, m_last_pointer.second, x, y, m_primary_items_hbounds.first, itemtop, m_primary_items_hbounds.second, itembottom)); + if (reentered) + { + auto const now(std::chrono::steady_clock::now()); + if (now >= m_scroll_repeat) + { + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(100); + if (!m_clicked_line) { - ui_globals::rpanel = (HOVER_RP_FIRST - hover()) * (-1); - stop = true; + --top_line; + if (main_at_top()) + m_pointer_action = pointer_action::NONE; } - else if (hover() >= HOVER_FILTER_FIRST && hover() <= HOVER_FILTER_LAST) + else { - m_prev_selected = nullptr; - m_filter_highlight = hover() - HOVER_FILTER_FIRST; - filter_selected(); - stop = true; + ++top_line; + if (main_at_bottom()) + m_pointer_action = pointer_action::NONE; } + if (main_force_visible_selection()) + key = IPT_CUSTOM; // stop processing events so the info pane can be rebuilt + redraw = true; } - break; - - // if we are hovering over a valid item, fake a UI_SELECT with a double-click - case ui_event::type::MOUSE_DOUBLE_CLICK: - if (hover() >= 0 && hover() < item_count()) + } + } + else + { + // check for conversion to touch scroll + if (ui_event::pointer::TOUCH == uievt.pointer_type) + { + auto const [h, v] = check_drag_conversion(x, y, m_base_pointer.first, m_base_pointer.second, line_height()); + if (h || (v && (m_clicked_line >= m_primary_lines))) + { + m_pointer_action = pointer_action::NONE; + return std::make_tuple(IPT_INVALID, false, true); + } + else if (v) { - set_selected_index(hover()); - ev.iptkey = IPT_UI_SELECT; + m_pointer_action = pointer_action::MAIN_DRAG; + m_last_pointer = m_base_pointer; + m_clicked_line = top_line; + return update_main_drag(changed, uievt); } + } - if (is_last_selected()) + // check to see if they released over the item + if (uievt.pointer_released & 0x01) + { + m_pointer_action = pointer_action::NONE; + if ((0 < uievt.pointer_clicks) || ((x >= m_primary_items_hbounds.first) && (x < m_primary_items_hbounds.second) && (y >= itemtop) && (y < itembottom))) { - ev.iptkey = IPT_UI_CANCEL; - stack_pop(); + if (m_clicked_line < m_visible_lines) + { + // systems or software items are always selectable + if (2 == uievt.pointer_clicks) + key = IPT_UI_SELECT; + else if (selected_index() != (m_clicked_line + top_line)) + key = IPT_CUSTOM; // stop processing events so the info pane can be rebuilt + set_selected_index(m_clicked_line + top_line); + set_focus(focused_menu::MAIN); + } + else if ((m_clicked_line >= m_primary_lines) && (m_clicked_line < (m_primary_lines + m_skip_main_items))) + { + // need to ensure this is a selectable item + int const itemnum(m_available_items + m_clicked_line - m_primary_lines); + if (is_selectable(item(itemnum))) + { + if (selected_index() < m_available_items) + m_prev_selected = get_selection_ref(); + set_selected_index(itemnum); + set_focus(focused_menu::MAIN); + if (2 == uievt.pointer_clicks) + key = IPT_UI_SELECT; + } + } } - stop = true; - break; + return std::make_tuple(key, false, true); + } + } - // caught scroll event - case ui_event::type::MOUSE_WHEEL: - if (hover() >= 0 && hover() < item_count() - skip_main_items - 1) + // stop tracking if the primary button is released or another button is pressed + if ((uievt.pointer_released & 0x01) || (uievt.pointer_pressed & ~u32(0x01))) + m_pointer_action = pointer_action::NONE; + if (pointer_action::NONE != m_pointer_action) + m_last_pointer = std::make_pair(x, y); + return std::make_tuple(key, pointer_action::NONE != m_pointer_action, redraw); +} + + +//------------------------------------------------- +// track right click +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::update_main_track_rbutton(bool changed, ui_event const &uievt) +{ + // see if it was released inside the line + if (uievt.pointer_released & 0x02) + { + m_pointer_action = pointer_action::NONE; + auto const [x, y] = pointer_location(); + float const linetop(m_primary_items_top + (float(m_clicked_line) * line_height())); + float const linebottom(m_primary_items_top + (float(m_clicked_line + 1) * line_height())); + if ((x >= m_primary_items_hbounds.first) && (x < m_primary_items_hbounds.second) && (y >= linetop) && (y < linebottom)) + { + show_config_menu(m_clicked_line + top_line); + return std::make_tuple(IPT_CUSTOM, false, false); // return IPT_CUSTOM to stop processing events + } + } + + // stop tracking if another button is pressed + if (uievt.pointer_pressed & ~u32(0x02)) + m_pointer_action = pointer_action::NONE; + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, pointer_action::DIVIDER_TRACK != m_pointer_action); +} + + +//------------------------------------------------- +// track main touch drag scroll +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::update_main_drag(bool changed, ui_event const &uievt) +{ + auto const newtop(drag_scroll( + pointer_location().second, m_base_pointer.second, m_last_pointer.second, -line_height(), + m_clicked_line, 0, m_available_items - m_primary_lines)); + bool const moved(newtop != top_line); + int key(IPT_INVALID); + if (moved) + { + // scroll and move the selection if necessary to keep it in the visible range + top_line = newtop; + if (main_force_visible_selection()) + key = IPT_CUSTOM; // stop processing events so the info pane can be rebuilt + } + + // stop tracking if the primary button is released or another button is pressed + if ((uievt.pointer_released & 0x01) || (uievt.pointer_pressed & ~u32(0x01))) + m_pointer_action = pointer_action::NONE; + return std::make_tuple(key, pointer_action::NONE != m_pointer_action, moved); +} + + +//------------------------------------------------- +// track click on left panel item +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::update_left_track_line(bool changed, ui_event const &uievt) +{ + auto const [x, y] = pointer_location(); + float const itemtop(m_left_items_top + (float(m_clicked_line) * m_info_line_height)); + float const itembottom(m_left_items_top + (float(m_clicked_line + 1) * m_info_line_height)); + bool redraw(false); + + if (is_left_up_arrow(m_clicked_line) || is_left_down_arrow(m_clicked_line)) + { + // top or bottom line is a scroll arrow + bool const reentered(reentered_rect(m_last_pointer.first, m_last_pointer.second, x, y, m_left_items_hbounds.first, itemtop, m_left_items_hbounds.second, itembottom)); + if (reentered) + { + auto const now(std::chrono::steady_clock::now()); + if (now >= m_scroll_repeat) { - if (local_menu_event.zdelta > 0) + m_scroll_repeat = now + std::chrono::milliseconds(100); + if (!m_clicked_line) { - if (selected_index() >= m_available_items || is_first_selected() || m_ui_error) - break; - set_selected_index(selected_index() - local_menu_event.num_lines); - if (selected_index() < top_line + (top_line != 0)) - top_line -= local_menu_event.num_lines; + --m_left_visible_top; + m_filter_highlight = std::min(m_left_visible_top + m_left_visible_lines - 2, m_filter_highlight); + if (left_at_top()) + m_pointer_action = pointer_action::NONE; } else { - if (selected_index() >= m_available_items - 1 || m_ui_error) - break; - set_selected_index(std::min(selected_index() + local_menu_event.num_lines, m_available_items - 1)); - if (selected_index() >= top_line + m_visible_items + (top_line != 0)) - top_line += local_menu_event.num_lines; + ++m_left_visible_top; + m_filter_highlight = std::max(m_left_visible_top + 1, m_filter_highlight); + if (left_at_bottom()) + m_pointer_action = pointer_action::NONE; } + redraw = true; } - else if (hover() == HOVER_INFO_TEXT) + } + } + else + { + // check for conversion to touch scroll + if (ui_event::pointer::TOUCH == uievt.pointer_type) + { + auto const [h, v] = check_drag_conversion(x, y, m_base_pointer.first, m_base_pointer.second, m_info_line_height); + if (h) { - if (local_menu_event.zdelta > 0) - m_topline_datsview -= local_menu_event.num_lines; - else - m_topline_datsview += local_menu_event.num_lines; + m_pointer_action = pointer_action::NONE; + return std::make_tuple(IPT_INVALID, false, true); } - break; - - // translate CHAR events into specials - case ui_event::type::IME_CHAR: - if (exclusive_input_pressed(ev.iptkey, IPT_UI_FOCUS_NEXT, 0) || exclusive_input_pressed(ev.iptkey, IPT_UI_FOCUS_PREV, 0)) + else if (v) { - stop = true; + m_pointer_action = pointer_action::LEFT_DRAG; + m_last_pointer = m_base_pointer; + m_clicked_line = m_left_visible_top; + return update_left_drag(changed, uievt); } - else if (m_ui_error) + } + + // this is a filter - check to see if they released over the item + if ((uievt.pointer_released & 0x01) && (x >= m_left_items_hbounds.first) && (x < m_left_items_hbounds.second) && (y >= itemtop) && (y < itembottom)) + { + m_pointer_action = pointer_action::NONE; + filter_selected(m_left_visible_top + m_clicked_line); + return std::make_tuple(IPT_CUSTOM, false, true); // return IPT_CUSTOM to stop processing events + } + } + + // stop tracking if the primary button is released or another button is pressed + if ((uievt.pointer_released & 0x01) || (uievt.pointer_pressed & ~u32(0x01))) + m_pointer_action = pointer_action::NONE; + if (pointer_action::NONE != m_pointer_action) + m_last_pointer = std::make_pair(x, y); + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, redraw); +} + + +//------------------------------------------------- +// track left panel touch drag scroll +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::update_left_drag(bool changed, ui_event const &uievt) +{ + auto const newtop(drag_scroll( + pointer_location().second, m_base_pointer.second, m_last_pointer.second, -m_info_line_height, + m_clicked_line, 0, m_left_item_count - m_left_visible_lines)); + bool const moved(newtop != m_left_visible_top); + if (moved) + { + // scroll and move the selection if necessary to keep it in the visible range + m_left_visible_top = newtop; + int const first(left_at_top() ? 0 : (newtop + 1)); + int const last(newtop + m_left_visible_lines - (left_at_bottom() ? 1 : 2)); + m_filter_highlight = std::clamp(m_filter_highlight, first, last); + } + + // stop tracking if the primary button is released or another button is pressed + if ((uievt.pointer_released & 0x01) || (uievt.pointer_pressed & ~u32(0x01))) + m_pointer_action = pointer_action::NONE; + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, moved); +} + + +//------------------------------------------------- +// track click on right panel tab +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::update_right_track_tab(bool changed, ui_event const &uievt) +{ + // see if it was released inside the divider + if (uievt.pointer_released & 0x01) + { + m_pointer_action = pointer_action::NONE; + auto const [x, y] = pointer_location(); + float const left(right_panel_left() + (float(m_clicked_line) * right_tab_width())); + float const right(right_panel_left() + (float(m_clicked_line + 1) * right_tab_width())); + if ((x >= left) && (x < right) && (y >= m_primary_vbounds.first) && (y < m_right_tabs_bottom)) + { + m_right_panel = m_clicked_line; + return std::make_tuple(IPT_CUSTOM, false, true); // return IPT_CUSTOM to stop processing events + } + } + + // stop tracking if another button is pressed + if (uievt.pointer_pressed & ~u32(0x01)) + m_pointer_action = pointer_action::NONE; + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, pointer_action::DIVIDER_TRACK != m_pointer_action); +} + + +//------------------------------------------------- +// track right panel heading left/right arrows +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::update_right_track_arrow(bool changed, ui_event const &uievt) +{ + auto const [x, y] = pointer_location(); + float const left(m_clicked_line ? (m_right_content_hbounds.second - lr_border() - lr_arrow_width()) : (m_right_content_hbounds.first + lr_border())); + float const right(m_clicked_line ? (m_right_content_hbounds.second - lr_border()) : (m_right_content_hbounds.first + lr_border() + lr_arrow_width())); + + // check for reentry + bool redraw(false); + bool const reentered(reentered_rect(m_last_pointer.first, m_last_pointer.second, x, y, left, right_arrows_top(), right, right_arrows_bottom())); + if (reentered) + { + auto const now(std::chrono::steady_clock::now()); + if (now >= m_scroll_repeat) + { + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(200); + bool notend; + std::tie(redraw, notend) = m_clicked_line ? next_right_panel_view() : previous_right_panel_view(); + if (!notend) + m_pointer_action = pointer_action::NONE; + } + } + + // stop tracking if the primary button is released or another button is pressed + if ((uievt.pointer_released & 0x01) || (uievt.pointer_pressed & ~u32(0x01))) + m_pointer_action = pointer_action::NONE; + if (pointer_action::NONE != m_pointer_action) + m_last_pointer = std::make_pair(x, y); + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, redraw); +} + + +//------------------------------------------------- +// track right scroll arrows +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::update_right_track_line(bool changed, ui_event const &uievt) +{ + auto const [x, y] = pointer_location(); + float const itemtop(m_right_content_vbounds.first + (float(m_clicked_line) * m_info_line_height)); + float const itembottom(m_right_content_vbounds.first + (float(m_clicked_line + 1) * m_info_line_height)); + bool redraw(false); + + bool const reentered(reentered_rect(m_last_pointer.first, m_last_pointer.second, x, y, m_right_content_hbounds.first, itemtop, m_right_content_hbounds.second, itembottom)); + if (reentered) + { + auto const now(std::chrono::steady_clock::now()); + if (now >= m_scroll_repeat) + { + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(100); + if (!m_clicked_line) { - ev.iptkey = IPT_SPECIAL; - stop = true; + --m_topline_datsview; + if (info_at_top()) + m_pointer_action = pointer_action::NONE; } - else if (accept_search()) + else { - if (input_character(m_search, local_menu_event.ch, uchar_is_printable)) - search_changed = true; + ++m_topline_datsview; + if (info_at_bottom()) + m_pointer_action = pointer_action::NONE; } - break; + redraw = true; + } + } + + // stop tracking if the primary button is released or another button is pressed + if ((uievt.pointer_released & 0x01) || (uievt.pointer_pressed & ~u32(0x01))) + m_pointer_action = pointer_action::NONE; + if (pointer_action::NONE != m_pointer_action) + m_last_pointer = std::make_pair(x, y); + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, redraw); +} + - case ui_event::type::MOUSE_RDOWN: - if (hover() >= 0 && hover() < item_count() - skip_main_items - 1) +//------------------------------------------------- +// track panel heading touch drag switch +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::update_right_switch(bool changed, ui_event const &uievt) +{ + // get new page + ui_software_info const *software; + ui_system_info const *system; + get_selection(software, system); + int const min((RP_IMAGES == m_right_panel) ? FIRST_VIEW : 0); + int const max( + (RP_IMAGES == m_right_panel) ? LAST_VIEW : + (software && !software->startempty) ? (ui_globals::cur_sw_dats_total - 1) : + (system || (software && software->driver)) ? (ui_globals::curdats_total - 1) : + 0); + auto const newpage(drag_scroll( + pointer_location().first, m_base_pointer.first, m_last_pointer.first, 0.125F * (m_right_content_hbounds.first - m_right_content_hbounds.second), + m_clicked_line, min, max)); + + // switch page + u8 dummy(newpage); + u8 ¤t( + (RP_IMAGES == m_right_panel) ? m_image_view : + (software && !software->startempty) ? ui_globals::cur_sw_dats_view : + (system || (software && software->driver)) ? ui_globals::curdats_view : + dummy); + bool const redraw(newpage != current); + if (redraw) + { + current = newpage; + if (RP_IMAGES == m_right_panel) + set_switch_image(); + else + m_topline_datsview = 0; + } + + // stop tracking if the primary button is released or another button is pressed + if ((uievt.pointer_released & 0x01) || (uievt.pointer_pressed & ~u32(0x01))) + m_pointer_action = pointer_action::NONE; + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, redraw); +} + + +//------------------------------------------------- +// track right panel touch drag scroll +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::update_right_drag(bool changed, ui_event const &uievt) +{ + auto const newtop(drag_scroll( + pointer_location().second, m_base_pointer.second, m_last_pointer.second, -m_info_line_height, + m_clicked_line, 0, m_total_lines - m_right_visible_lines)); + bool const moved(newtop != m_topline_datsview); + m_topline_datsview = newtop; + + // stop tracking if the primary button is released or another button is pressed + if ((uievt.pointer_released & 0x01) || (uievt.pointer_pressed & ~u32(0x01))) + m_pointer_action = pointer_action::NONE; + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, moved); +} + + +//------------------------------------------------- +// track click on toolbar button +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::update_toolbar_track(bool changed, ui_event const &uievt) +{ + // see if it was released inside the button + if (uievt.pointer_released & 0x01) + { + m_pointer_action = pointer_action::NONE; + auto const [x, y] = pointer_location(); + float const left((0 > m_clicked_line) ? m_toolbar_backtrack_left : (m_toolbar_main_left + (float(m_clicked_line) * m_toolbar_button_spacing))); + if ((x >= left) && (x < (left + m_toolbar_button_width)) && (y >= m_toolbar_button_vbounds.first) && (y < m_toolbar_button_vbounds.second)) + { + if (0 > m_clicked_line) { - set_selected_index(hover()); - m_prev_selected = get_selection_ref(); - m_focus = focused_menu::MAIN; - ev.iptkey = IPT_CUSTOM; - ev.mouse.x0 = local_menu_event.mouse_x; - ev.mouse.y0 = local_menu_event.mouse_y; - stop = true; + // backtrack button + stack_pop(); + if (is_special_main_menu()) + machine().schedule_exit(); + return std::make_tuple(IPT_UI_BACK, false, true); + } + else + { + // main buttons + auto const *const toolbar_bitmaps(m_is_swlist ? SW_TOOLBAR_BITMAPS : SYS_TOOLBAR_BITMAPS); + auto const [bitmap, action, need_selection] = toolbar_bitmaps[m_clicked_line]; + switch (action) + { + case IPT_UI_EXPORT: + inkey_export(); + break; + case IPT_UI_DATS: + inkey_dats(); + break; + default: + return std::make_tuple(action, false, true); + } + return std::make_tuple(IPT_CUSTOM, false, true); // return IPT_CUSTOM to stop processing events } - break; - - // ignore everything else - default: - break; } + } - // need to update search before processing certain kinds of events, but others don't matter - if (search_changed) + // stop tracking if another button is pressed + if (uievt.pointer_pressed & ~u32(0x01)) + m_pointer_action = pointer_action::NONE; + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, pointer_action::TOOLBAR_TRACK != m_pointer_action); +} + + +//------------------------------------------------- +// track click on divider +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_select_launch::update_divider_track(bool changed, ui_event const &uievt) +{ + // see if it was released inside the divider + if (uievt.pointer_released & 0x01) + { + m_pointer_action = pointer_action::NONE; + auto const [x, y] = pointer_location(); + float const left(m_clicked_line ? right_divider_left() : left_divider_left()); + float const right(m_clicked_line ? right_divider_right() : left_divider_right()); + if ((x >= left) && (x < right) && (y >= m_primary_vbounds.first) && (y < m_primary_vbounds.second)) { - switch (machine().ui_input().peek_event_type()) + if (m_clicked_line) { - case ui_event::type::MOUSE_DOWN: - case ui_event::type::MOUSE_RDOWN: - case ui_event::type::MOUSE_DOUBLE_CLICK: - case ui_event::type::MOUSE_WHEEL: - stop = true; - break; - case ui_event::type::NONE: - case ui_event::type::WINDOW_FOCUS: - case ui_event::type::WINDOW_DEFOCUS: - case ui_event::type::MOUSE_MOVE: - case ui_event::type::MOUSE_LEAVE: - case ui_event::type::MOUSE_UP: - case ui_event::type::MOUSE_RUP: - case ui_event::type::IME_CHAR: - break; + if ((get_focus() == focused_menu::RIGHTTOP) || (get_focus() == focused_menu::RIGHTBOTTOM)) + { + set_focus(focused_menu::MAIN); + select_prev(); + } + m_panels_status ^= HIDE_RIGHT_PANEL; } + else + { + if (get_focus() == focused_menu::LEFT) + { + set_focus(focused_menu::MAIN); + select_prev(); + } + m_panels_status ^= HIDE_LEFT_PANEL; + } + return std::make_tuple(IPT_CUSTOM, false, true); // return IPT_CUSTOM to stop processing events } } - if (search_changed) - reset(reset_options::SELECT_FIRST); + + // stop tracking if another button is pressed + if (uievt.pointer_pressed & ~u32(0x01)) + m_pointer_action = pointer_action::NONE; + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, pointer_action::DIVIDER_TRACK != m_pointer_action); } //------------------------------------------------- -// draw main menu +// move selection to visible range //------------------------------------------------- -void menu_select_launch::draw(uint32_t flags) +bool menu_select_launch::main_force_visible_selection() { - bool noinput = (flags & PROCESS_NOINPUT); - float const aspect = machine().render().ui_aspect(&container()); - float const lr_border = ui().box_lr_border() * aspect; - float line_height = ui().get_line_height(); - float const ud_arrow_width = line_height * aspect; - float const gutter_width = 0.52f * ud_arrow_width; - float const icon_offset = m_has_icons ? (1.5f * ud_arrow_width) : 0.0f; - float right_panel_size = (ui_globals::panels_status == HIDE_BOTH || ui_globals::panels_status == HIDE_RIGHT_PANEL) ? 2.0f * lr_border : 0.3f; - float visible_width = 1.0f - 4.0f * lr_border; - float primary_left = (1.0f - visible_width) * 0.5f; - float primary_width = visible_width; + int const first(main_at_top() ? 0 : (top_line + 1)); + int const last(top_line + m_primary_lines - (main_at_bottom() ? 1 : 2)); + if (selected_index() < m_available_items) + { + int const restricted(std::clamp(selected_index(), first, last)); + if (selected_index() != restricted) + { + set_selected_index(restricted); + return true; + } + } + else + { + int selection(0); + while ((m_available_items > selection) && (item(selection).ref() != m_prev_selected)) + ++selection; + auto const ref(item(std::clamp(selection, first, last)).ref()); + if (ref != m_prev_selected) + { + m_prev_selected = ref; + return true; + } + } + return false; +} - draw_background(); - clear_hover(); - m_available_items = (m_is_swlist) ? item_count() - 2 : item_count() - 2 - skip_main_items; - float extra_height = (m_is_swlist) ? 2.0f * line_height : (2.0f + skip_main_items) * line_height; - float visible_extra_menu_height = get_customtop() + get_custombottom() + extra_height; +//------------------------------------------------- +// draw main menu +//------------------------------------------------- - // locate mouse - if (noinput) - ignore_mouse(); - else - map_mouse(); +void menu_select_launch::draw(u32 flags) +{ - // account for extra space at the top and bottom - float visible_main_menu_height = 1.0f - 2.0f * ui().box_tb_border() - visible_extra_menu_height; - m_visible_lines = int(std::trunc(visible_main_menu_height / line_height)); - visible_main_menu_height = float(m_visible_lines) * line_height; + // recompute height of primary menu area if necessary + if (m_primary_vbounds.first >= m_primary_vbounds.second) + { + float const pixelheight(target_size().second); + float const lines(std::floor((1.0F - (4.0F * tb_border()) - get_customtop() - get_custombottom()) / line_height())); + float const itemsheight(line_height() * lines); + float const space(1.0F - itemsheight - get_customtop() - get_custombottom()); + m_primary_items_top = std::round((get_customtop() + (0.5F * space)) * pixelheight) / pixelheight; + m_primary_vbounds = std::make_pair(m_primary_items_top - tb_border(), m_primary_items_top + itemsheight + tb_border()); + m_primary_lines = int(lines) - m_skip_main_items; + } - if (!m_is_swlist) - ui_globals::visible_main_lines = m_visible_lines; + // ensure the selection is visible + m_available_items = item_count() - m_skip_main_items; + m_visible_lines = (std::min)(m_primary_lines, m_available_items); + int selection; + if (selected_index() < m_available_items) + { + selection = selected_index(); + } else - ui_globals::visible_sw_lines = m_visible_lines; - - // compute top/left of inner menu area by centering - float visible_left = primary_left; - float visible_top = (1.0f - (visible_main_menu_height + visible_extra_menu_height)) * 0.5f; - - // if the menu is at the bottom of the extra, adjust - visible_top += get_customtop(); - - // compute left box size - float x1 = visible_left - lr_border; - float y1 = visible_top - ui().box_tb_border(); - float x2 = x1 + 2.0f * lr_border; - float y2 = visible_top + visible_main_menu_height + ui().box_tb_border() + extra_height; - - // add left box - visible_left = draw_left_panel(x1, y1, x2, y2); - visible_width -= right_panel_size + visible_left - 2.0f * lr_border; - - // compute and add main box - x1 = visible_left - lr_border; - x2 = visible_left + visible_width + lr_border; - float line = visible_top + (float(m_visible_lines) * line_height); - ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color()); - - if (m_available_items < m_visible_lines) - m_visible_lines = m_available_items; - if (top_line < 0 || is_first_selected()) + { + selection = 0; + while ((m_available_items > selection) && (item(selection).ref() != m_prev_selected)) + ++selection; + } + if (top_line < 0 || !selection) + { top_line = 0; - if (selected_index() < m_available_items && top_line + m_visible_lines >= m_available_items) - top_line = m_available_items - m_visible_lines; - - // determine effective positions taking into account the hilighting arrows - float effective_width = visible_width - 2.0f * gutter_width; - float effective_left = visible_left + gutter_width; - + } + else if (selection < m_available_items) + { + if ((selection >= (top_line + m_visible_lines)) || (selection <= top_line)) + top_line = (std::max)(selection - (m_visible_lines / 2), 0); + if ((top_line + m_visible_lines) >= m_available_items) + top_line = m_available_items - m_visible_lines; + else if (selection >= (top_line + m_visible_lines - 2)) + top_line = selection - m_visible_lines + ((selection == (m_available_items - 1)) ? 1: 2); + } if ((m_focus == focused_menu::MAIN) && (selected_index() < m_available_items)) m_prev_selected = nullptr; - int const n_loop = (std::min)(m_visible_lines, m_available_items); - for (int linenum = 0; linenum < n_loop; linenum++) + // draw background, left and right panels, and outline of main box + draw_background(); + draw_left_panel(flags); + draw_right_panel(flags); + ui().draw_outlined_box( + container(), + left_divider_right(), m_primary_vbounds.first, right_divider_left(), m_primary_vbounds.second, + ui().colors().background_color()); + + // calculate horizontal geometry of main item list + m_primary_items_hbounds = std::make_pair(left_divider_right() + lr_border(), right_divider_left() - lr_border()); + float const item_text_left(m_primary_items_hbounds.first + gutter_width()); + float const item_text_width(m_primary_items_hbounds.second - m_primary_items_hbounds.first - (2.0F * gutter_width())); + float const icon_offset(m_has_icons ? (1.5F * ud_arrow_width()) : 0.0F); + + // draw main scrolling items + for (int linenum = 0; linenum < m_visible_lines; linenum++) { - float line_y = visible_top + (float)linenum * line_height; - int itemnum = top_line + linenum; - const menu_item &pitem = item(itemnum); - const std::string_view itemtext = pitem.text; + int const itemnum(top_line + linenum); + menu_item const &pitem(item(itemnum)); + std::string_view const itemtext(pitem.text()); + float const linetop(m_primary_items_top + (float(linenum) * line_height())); + float const linebottom(linetop + line_height()); + + // work out colours rgb_t fgcolor = ui().colors().text_color(); rgb_t bgcolor = ui().colors().text_bg_color(); rgb_t fgcolor3 = ui().colors().clone_color(); - float line_x0 = x1 + 0.5f * UI_LINE_WIDTH; - float line_y0 = line_y; - float line_x1 = x2 - 0.5f * UI_LINE_WIDTH; - float line_y1 = line_y + line_height; - - // set the hover if this is our item - if (mouse_in_rect(line_x0, line_y0, line_x1, line_y1) && is_selectable(pitem)) - set_hover(itemnum); - - if (is_selected(itemnum) && m_focus == focused_menu::MAIN) + bool const hovered(is_selectable(pitem) && pointer_in_rect(m_primary_items_hbounds.first, linetop, m_primary_items_hbounds.second, linebottom)); + bool const pointerline((pointer_action::MAIN_TRACK_LINE == m_pointer_action) && (linenum == m_clicked_line)); + bool const rclickline((pointer_action::MAIN_TRACK_RBUTTON == m_pointer_action) && (linenum == m_clicked_line)); + if (!rclickline && is_selected(itemnum) && (get_focus() == focused_menu::MAIN)) { // if we're selected, draw with a different background fgcolor = rgb_t(0xff, 0xff, 0x00); @@ -1972,167 +3261,177 @@ void menu_select_launch::draw(uint32_t flags) fgcolor3 = rgb_t(0xcc, 0xcc, 0x00); ui().draw_textured_box( container(), - line_x0 + 0.01f, line_y0, line_x1 - 0.01f, line_y1, + m_primary_items_hbounds.first, linetop, m_primary_items_hbounds.second, linebottom, bgcolor, rgb_t(43, 43, 43), hilight_main_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); } - else if (itemnum == hover()) + else if ((pointerline || rclickline) && hovered) { - // else if the mouse is over this item, draw with a different background - fgcolor = fgcolor3 = ui().options().mouseover_color(); + // draw selected highlight for tracked item + fgcolor = fgcolor3 = ui().colors().selected_color(); + bgcolor = ui().colors().selected_bg_color(); + highlight(m_primary_items_hbounds.first, linetop, m_primary_items_hbounds.second, linebottom, bgcolor); + } + else if (pointerline || rclickline || (!m_ui_error && !(flags & PROCESS_NOINPUT) && hovered && pointer_idle())) + { + // draw hover highlight when hovered over or dragged off + fgcolor = fgcolor3 = ui().colors().mouseover_color(); bgcolor = ui().colors().mouseover_bg_color(); - highlight(line_x0, line_y0, line_x1, line_y1, bgcolor); + highlight(m_primary_items_hbounds.first, linetop, m_primary_items_hbounds.second, linebottom, bgcolor); } - else if (pitem.ref == m_prev_selected) + else if (pitem.ref() == m_prev_selected) { - fgcolor = fgcolor3 = ui().options().mouseover_color(); + fgcolor = fgcolor3 = ui().colors().mouseover_color(); bgcolor = ui().colors().mouseover_bg_color(); - ui().draw_textured_box(container(), line_x0 + 0.01f, line_y0, line_x1 - 0.01f, line_y1, bgcolor, rgb_t(43, 43, 43), + ui().draw_textured_box( + container(), + m_primary_items_hbounds.first, linetop, m_primary_items_hbounds.second, linebottom, + bgcolor, rgb_t(43, 43, 43), hilight_main_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); } - if (linenum == 0 && top_line != 0) + if ((!linenum && top_line) || ((linenum == (m_visible_lines - 1)) && (itemnum != m_available_items - 1))) { - // if we're on the top line, display the up arrow - draw_arrow(0.5f * (x1 + x2) - 0.5f * ud_arrow_width, line_y + 0.25f * line_height, - 0.5f * (x1 + x2) + 0.5f * ud_arrow_width, line_y + 0.75f * line_height, fgcolor, ROT0); - - if (hover() == itemnum) - set_hover(HOVER_ARROW_UP); - } - else if (linenum == m_visible_lines - 1 && itemnum != m_available_items - 1) - { - // if we're on the bottom line, display the down arrow - draw_arrow(0.5f * (x1 + x2) - 0.5f * ud_arrow_width, line_y + 0.25f * line_height, - 0.5f * (x1 + x2) + 0.5f * ud_arrow_width, line_y + 0.75f * line_height, fgcolor, ROT0 ^ ORIENTATION_FLIP_Y); - - if (hover() == itemnum) - set_hover(HOVER_ARROW_DOWN); + // if we're on the top or bottom line, display the up or down arrow + draw_arrow( + 0.5F * (m_primary_items_hbounds.first + m_primary_items_hbounds.second - ud_arrow_width()), linetop + (0.25F * line_height()), + 0.5F * (m_primary_items_hbounds.first + m_primary_items_hbounds.second + ud_arrow_width()), linetop + (0.75F * line_height()), + fgcolor, + linenum ? (ROT0 ^ ORIENTATION_FLIP_Y) : ROT0); } - else if (pitem.type == menu_item_type::SEPARATOR) + else if (pitem.type() == menu_item_type::SEPARATOR) { // if we're just a divider, draw a line - container().add_line(visible_left, line_y + 0.5f * line_height, visible_left + visible_width, line_y + 0.5f * line_height, + container().add_line( + m_primary_items_hbounds.first, linetop + (0.5F * line_height()), + m_primary_items_hbounds.second, linetop + (0.5F * line_height()), UI_LINE_WIDTH, ui().colors().text_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } - else if (pitem.subtext.empty()) - { - // draw the item centered - int const item_invert = pitem.flags & FLAG_INVERT; - if (m_has_icons) - draw_icon(linenum, item(itemnum).ref, effective_left, line_y); - ui().draw_text_full( - container(), - itemtext, - effective_left + icon_offset, line_y, effective_width - icon_offset, - ui::text_layout::LEFT, ui::text_layout::TRUNCATE, - mame_ui_manager::NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, - nullptr, nullptr); - } else { - int const item_invert = pitem.flags & FLAG_INVERT; - std::string_view const subitem_text = pitem.subtext; - float item_width, subitem_width; - - // compute right space for subitem - ui().draw_text_full( - container(), - subitem_text, - effective_left + icon_offset, line_y, ui().get_string_width(pitem.subtext), - ui::text_layout::RIGHT, ui::text_layout::NEVER, - mame_ui_manager::NONE, item_invert ? fgcolor3 : fgcolor, bgcolor, - &subitem_width, nullptr); - subitem_width += gutter_width; - - // draw the item left-justified + bool const item_invert(pitem.flags() & FLAG_INVERT); if (m_has_icons) - draw_icon(linenum, item(itemnum).ref, effective_left, line_y); - ui().draw_text_full( - container(), - itemtext, - effective_left + icon_offset, line_y, effective_width - icon_offset - subitem_width, - ui::text_layout::LEFT, ui::text_layout::TRUNCATE, - mame_ui_manager::NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, - &item_width, nullptr); - - // draw the subitem right-justified - ui().draw_text_full( - container(), - subitem_text, - effective_left + icon_offset + item_width, line_y, effective_width - icon_offset - item_width, - ui::text_layout::RIGHT, ui::text_layout::NEVER, - mame_ui_manager::NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, - nullptr, nullptr); + draw_icon(linenum, pitem.ref(), item_text_left, linetop); + if (pitem.subtext().empty()) + { + // draw the item left-aligned + ui().draw_text_full( + container(), + itemtext, + item_text_left + icon_offset, linetop, item_text_width - icon_offset, + text_layout::text_justify::LEFT, text_layout::word_wrapping::TRUNCATE, + mame_ui_manager::NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, + nullptr, nullptr, + line_height()); + } + else + { + // compute right space for subitem + std::string_view const subitem_text(pitem.subtext()); + float const subitem_width(get_string_width(subitem_text) + gutter_width()); + + // draw the item left-aligned + float item_width; + ui().draw_text_full( + container(), + itemtext, + item_text_left + icon_offset, linetop, item_text_width - icon_offset - subitem_width, + text_layout::text_justify::LEFT, text_layout::word_wrapping::TRUNCATE, + mame_ui_manager::NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, + &item_width, nullptr, + line_height()); + + // draw the subitem right-aligned + ui().draw_text_full( + container(), + subitem_text, + item_text_left + icon_offset + item_width, linetop, item_text_width - icon_offset - item_width, + text_layout::text_justify::RIGHT, text_layout::word_wrapping::NEVER, + mame_ui_manager::NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, + nullptr, nullptr, + line_height()); + } } } - for (size_t count = m_available_items; count < item_count(); count++) + // draw extra fixed items + for (size_t linenum = 0; linenum < m_skip_main_items; linenum++) { - const menu_item &pitem = item(count); - const std::string_view itemtext = pitem.text; - float line_x0 = x1 + 0.5f * UI_LINE_WIDTH; - float line_y0 = line; - float line_x1 = x2 - 0.5f * UI_LINE_WIDTH; - float line_y1 = line + line_height; + int const itemnum(m_available_items + linenum); + menu_item const &pitem(item(itemnum)); + std::string_view const itemtext(pitem.text()); + float const linetop(m_primary_items_top + (float(m_primary_lines + linenum) * line_height())); + float const linebottom(linetop + line_height()); + + // work out colours rgb_t fgcolor = ui().colors().text_color(); rgb_t bgcolor = ui().colors().text_bg_color(); - - if (mouse_in_rect(line_x0, line_y0, line_x1, line_y1) && is_selectable(pitem)) - set_hover(count); - - // if we're selected, draw with a different background - if (is_selected(count) && m_focus == focused_menu::MAIN) + bool const hovered(is_selectable(pitem) && pointer_in_rect(m_primary_items_hbounds.first, linetop, m_primary_items_hbounds.second, linebottom)); + bool const pointerline((pointer_action::MAIN_TRACK_LINE == m_pointer_action) && ((m_primary_lines + linenum) == m_clicked_line)); + if (is_selected(itemnum) && (get_focus() == focused_menu::MAIN)) { + // if we're selected, draw with a different background fgcolor = rgb_t(0xff, 0xff, 0x00); bgcolor = rgb_t(0xff, 0xff, 0xff); - ui().draw_textured_box(container(), line_x0 + 0.01f, line_y0, line_x1 - 0.01f, line_y1, bgcolor, rgb_t(43, 43, 43), + ui().draw_textured_box( + container(), + m_primary_items_hbounds.first, linetop, m_primary_items_hbounds.second, linebottom, + bgcolor, rgb_t(43, 43, 43), hilight_main_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); } - // else if the mouse is over this item, draw with a different background - else if (count == hover()) + else if (pointerline && hovered) { - fgcolor = ui().options().mouseover_color(); + // draw selected highlight for tracked item + fgcolor = ui().colors().selected_color(); + bgcolor = ui().colors().selected_bg_color(); + highlight(m_primary_items_hbounds.first, linetop, m_primary_items_hbounds.second, linebottom, bgcolor); + } + else if (pointerline || (!m_ui_error && !(flags & PROCESS_NOINPUT) && hovered && pointer_idle())) + { + // draw hover highlight when hovered over or dragged off + fgcolor = ui().colors().mouseover_color(); bgcolor = ui().colors().mouseover_bg_color(); - highlight(line_x0, line_y0, line_x1, line_y1, bgcolor); + highlight(m_primary_items_hbounds.first, linetop, m_primary_items_hbounds.second, linebottom, bgcolor); } - if (pitem.type == menu_item_type::SEPARATOR) + if (pitem.type() == menu_item_type::SEPARATOR) { - container().add_line(visible_left, line + 0.5f * line_height, visible_left + visible_width, line + 0.5f * line_height, + // if we're just a divider, draw a line + container().add_line( + m_primary_items_hbounds.first, linetop + (0.5F * line_height()), + m_primary_items_hbounds.second, linetop + (0.5F * line_height()), UI_LINE_WIDTH, ui().colors().text_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } else { - ui().draw_text_full(container(), itemtext, effective_left, line, effective_width, ui::text_layout::CENTER, ui::text_layout::TRUNCATE, - mame_ui_manager::NORMAL, fgcolor, bgcolor, nullptr, nullptr); + // draw the item centred + ui().draw_text_full( + container(), + itemtext, + item_text_left, linetop, item_text_width, + text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, + mame_ui_manager::NORMAL, fgcolor, bgcolor, + nullptr, nullptr, + line_height()); } - line += line_height; } - x1 = x2; - x2 += right_panel_size; - - draw_right_panel(x1, y1, x2, y2); - - x1 = primary_left - lr_border; - x2 = primary_left + primary_width + lr_border; - // if there is something special to add, do it by calling the virtual method - custom_render(get_selection_ref(), get_customtop(), get_custombottom(), x1, y1, x2, y2); - - // return the number of visible lines, minus 1 for top arrow and 1 for bottom arrow - m_visible_items = m_visible_lines - (top_line != 0) - (top_line + m_visible_lines != m_available_items); + custom_render( + flags, + get_selection_ref(), + get_customtop(), get_custombottom(), + lr_border(), m_primary_vbounds.first, 1.0F - lr_border(), m_primary_vbounds.second); - // noinput - if (noinput) + // show error text if necessary + if (m_ui_error) { - int alpha = (1.0f - machine().options().pause_brightness()) * 255.0f; - if (alpha > 255) - alpha = 255; - if (alpha >= 0) - container().add_rect(0.0f, 0.0f, 1.0f, 1.0f, rgb_t(alpha, 0x00, 0x00, 0x00), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + container().add_rect(0.0F, 0.0F, 1.0F, 1.0F, rgb_t(114, 0, 0, 0), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + ui().draw_text_box(container(), m_error_text, text_layout::text_justify::CENTER, 0.5f, 0.5f, UI_RED_COLOR); } + + // return the number of visible lines, minus 1 for top arrow and 1 for bottom arrow + m_visible_items = m_visible_lines - (top_line != 0) - (top_line + m_visible_lines != m_available_items); } @@ -2140,115 +3439,197 @@ void menu_select_launch::draw(uint32_t flags) // draw right panel //------------------------------------------------- -void menu_select_launch::draw_right_panel(float origx1, float origy1, float origx2, float origy2) +void menu_select_launch::draw_right_panel(u32 flags) { - float const aspect(machine().render().ui_aspect(&container())); - bool const hide((ui_globals::panels_status == HIDE_RIGHT_PANEL) || (ui_globals::panels_status == HIDE_BOTH)); - float const x2(hide ? origx2 : (origx1 + 2.0f * ui().box_lr_border() * aspect)); - float const space(x2 - origx1); - float const lr_arrow_width(0.4f * space * aspect); + if (show_right_panel()) + { + m_right_panel_width = 0.3F - m_divider_width; - // set left-right arrows dimension - float const ar_x0(0.5f * (x2 + origx1) - 0.5f * lr_arrow_width); - float const ar_y0(0.5f * (origy2 + origy1) + 0.1f * space); - float const ar_x1(ar_x0 + lr_arrow_width); - float const ar_y1(0.5f * (origy2 + origy1) + 0.9f * space); + ui().draw_outlined_box( + container(), right_panel_left(), m_primary_vbounds.first, right_panel_right(), m_primary_vbounds.second, + ui().colors().background_color()); + draw_right_box_tabs(flags); - ui().draw_outlined_box(container(), origx1, origy1, origx2, origy2, rgb_t(0xEF, 0x12, 0x47, 0x7B)); + if (0.0F >= m_right_heading_top) + { + float const pixelheight(target_size().second); + m_right_heading_top = std::round((m_right_tabs_bottom + UI_LINE_WIDTH + tb_border()) * pixelheight) / pixelheight; + m_right_content_vbounds = std::make_pair( + std::round((m_right_heading_top + line_height() + tb_border()) * pixelheight) / pixelheight, + m_primary_vbounds.second - tb_border()); + m_right_content_hbounds = std::make_pair(right_panel_left() + lr_border(), right_panel_right() - lr_border()); + } - rgb_t fgcolor(ui().colors().text_color()); - if (mouse_in_rect(origx1, origy1, x2, origy2)) - { - fgcolor = ui().options().mouseover_color(); - set_hover(HOVER_RPANEL_ARROW); + if (m_right_panel == RP_IMAGES) + arts_render(flags); + else + infos_render(flags); } - - if (hide) + else { - draw_arrow(ar_x0, ar_y0, ar_x1, ar_y1, fgcolor, ROT90 ^ ORIENTATION_FLIP_X); - return; + m_right_panel_width = 0.0F; } - draw_arrow(ar_x0, ar_y0, ar_x1, ar_y1, fgcolor, ROT90); - origy1 = draw_right_box_title(x2, origy1, origx2, origy2); - - if (ui_globals::rpanel == RP_IMAGES) - arts_render(x2, origy1, origx2, origy2); - else - infos_render(x2, origy1, origx2, origy2); + draw_divider(flags, 1.0F - lr_border() - m_right_panel_width - m_divider_width, true); } //------------------------------------------------- -// draw right box title +// draw right box tabs //------------------------------------------------- -float menu_select_launch::draw_right_box_title(float x1, float y1, float x2, float y2) +void menu_select_launch::draw_right_box_tabs(u32 flags) { - auto line_height = ui().get_line_height(); - float const midl = (x2 - x1) * 0.5f; + m_right_tabs_bottom = m_primary_vbounds.first + line_height(); - // add outlined box for options - ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color()); + float const x1(1.0F - lr_border() - m_right_panel_width); + float const x2(1.0F - lr_border()); + float const tabwidth = right_tab_width(); - // add separator line - container().add_line(x1 + midl, y1, x1 + midl, y1 + line_height, UI_LINE_WIDTH, ui().colors().border_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); - - std::string buffer[RP_LAST + 1]; - buffer[RP_IMAGES] = _("Images"); - buffer[RP_INFOS] = _("Infos"); + std::string const buffer[RP_LAST + 1] = { _(RIGHT_PANEL_NAMES[0].second), _(RIGHT_PANEL_NAMES[1].second) }; // check size float text_size = 1.0f; for (auto & elem : buffer) { - auto textlen = ui().get_string_width(elem) + 0.01f; - float tmp_size = (textlen > midl) ? (midl / textlen) : 1.0f; + auto textlen = get_string_width(elem) + 0.01f; + float tmp_size = (textlen > tabwidth) ? (tabwidth / textlen) : 1.0f; text_size = std::min(text_size, tmp_size); } for (int cells = RP_FIRST; cells <= RP_LAST; ++cells) { - rgb_t bgcolor = ui().colors().text_bg_color(); - rgb_t fgcolor = ui().colors().text_color(); + float const tableft(x1 + (float(cells - RP_FIRST) * tabwidth)); - if (mouse_in_rect(x1, y1, x1 + midl, y1 + line_height)) + rgb_t fgcolor = ui().colors().text_color(); + rgb_t bgcolor = ui().colors().text_bg_color(); + if ((focused_menu::RIGHTTOP == m_focus) && (cells == m_right_panel)) + { + // draw primary highlight if keyboard focus is here + fgcolor = rgb_t(0xff, 0xff, 0x00); + bgcolor = rgb_t(0xff, 0xff, 0xff); + ui().draw_textured_box( + container(), + tableft + UI_LINE_WIDTH, m_primary_vbounds.first + UI_LINE_WIDTH, tableft + tabwidth - UI_LINE_WIDTH, m_right_tabs_bottom, + bgcolor, rgb_t(43, 43, 43), hilight_main_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); + } + else if (cells != m_right_panel) { - if (ui_globals::rpanel != cells) + bool const hovered(pointer_in_rect(tableft, m_primary_vbounds.first, tableft + tabwidth, m_right_tabs_bottom)); + bool const pointertab((pointer_action::RIGHT_TRACK_TAB == m_pointer_action) && (cells == m_clicked_line)); + if (pointertab && hovered) + { + // draw selected highlight for tracked item + fgcolor = ui().colors().selected_color(); + bgcolor = ui().colors().selected_bg_color(); + highlight(tableft + UI_LINE_WIDTH, m_primary_vbounds.first + UI_LINE_WIDTH, tableft + tabwidth - UI_LINE_WIDTH, m_right_tabs_bottom, bgcolor); + } + else if (pointertab || (!m_ui_error && !(flags & PROCESS_NOINPUT) && hovered && pointer_idle())) { + // draw hover highlight when hovered over or dragged off + fgcolor = ui().colors().mouseover_color(); bgcolor = ui().colors().mouseover_bg_color(); - fgcolor = ui().options().mouseover_color(); - set_hover(HOVER_RP_FIRST + cells); + highlight(tableft + UI_LINE_WIDTH, m_primary_vbounds.first + UI_LINE_WIDTH, tableft + tabwidth - UI_LINE_WIDTH, m_right_tabs_bottom, bgcolor); } - } - - if (ui_globals::rpanel != cells) - { - container().add_line(x1, y1 + line_height, x1 + midl, y1 + line_height, UI_LINE_WIDTH, - ui().colors().border_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); - if (fgcolor != ui().colors().mouseover_color()) + else + { + // dim unselected tab title fgcolor = ui().colors().clone_color(); + } } - if (m_focus == focused_menu::RIGHTTOP && ui_globals::rpanel == cells) - { - fgcolor = rgb_t(0xff, 0xff, 0x00); - bgcolor = rgb_t(0xff, 0xff, 0xff); - ui().draw_textured_box(container(), x1 + UI_LINE_WIDTH, y1 + UI_LINE_WIDTH, x1 + midl - UI_LINE_WIDTH, y1 + line_height, - bgcolor, rgb_t(43, 43, 43), hilight_main_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); - } - else if (bgcolor == ui().colors().mouseover_bg_color()) + ui().draw_text_full( + container(), + buffer[cells], + tableft + UI_LINE_WIDTH, m_primary_vbounds.first, tabwidth - UI_LINE_WIDTH, + text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, + mame_ui_manager::NORMAL, fgcolor, bgcolor, nullptr, nullptr, + line_height() * text_size); + + // add lines when appropriate + if (RP_FIRST < cells) { - container().add_rect(x1 + UI_LINE_WIDTH, y1 + UI_LINE_WIDTH, x1 + midl - UI_LINE_WIDTH, y1 + line_height, - bgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); + container().add_line( + tableft, m_primary_vbounds.first, tableft, m_right_tabs_bottom, + UI_LINE_WIDTH, ui().colors().border_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + if (m_right_panel == cells) + { + container().add_line( + x1, m_primary_vbounds.first + line_height(), tableft, m_right_tabs_bottom, + UI_LINE_WIDTH, ui().colors().border_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + } } + } + if (RP_LAST != m_right_panel) + { + container().add_line( + x1 + (float(m_right_panel + 1) * tabwidth), m_right_tabs_bottom, x2, m_right_tabs_bottom, + UI_LINE_WIDTH, ui().colors().border_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + } +} + + +//------------------------------------------------- +// draw right box heading +//------------------------------------------------- + +void menu_select_launch::draw_right_box_heading(u32 flags, bool larrow, bool rarrow, std::string_view text) +{ + float const text_left(m_right_content_hbounds.first + (2.0F * lr_border()) + lr_arrow_width()); + float const text_width(m_right_content_hbounds.second - m_right_content_hbounds.first - (4.0F * lr_border()) - (2.0F * lr_arrow_width())); + + rgb_t fgcolor(ui().colors().text_color()); + rgb_t bgcolor(ui().colors().text_bg_color()); + if (pointer_action::RIGHT_SWITCH == m_pointer_action) + { + // touch swipe to switch + fgcolor = ui().colors().selected_color(); + bgcolor = ui().colors().selected_bg_color(); + highlight(m_right_content_hbounds.first, m_right_heading_top, m_right_content_hbounds.second, m_right_heading_top + line_height(), bgcolor); + } + else if (focused_menu::RIGHTBOTTOM == m_focus) + { + // keyboard focus + fgcolor = rgb_t(0xff, 0xff, 0x00); + bgcolor = rgb_t(0xff, 0xff, 0xff); + ui().draw_textured_box( + container(), + m_right_content_hbounds.first, m_right_heading_top, m_right_content_hbounds.second, m_right_heading_top + line_height(), + bgcolor, rgb_t(43, 43, 43), + hilight_main_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); + } - ui().draw_text_full(container(), buffer[cells], x1 + UI_LINE_WIDTH, y1, midl - UI_LINE_WIDTH, - ui::text_layout::CENTER, ui::text_layout::NEVER, mame_ui_manager::NORMAL, fgcolor, bgcolor, nullptr, nullptr, text_size); - x1 += midl; + ui().draw_text_full(container(), + text, text_left, m_right_heading_top, text_width, + text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, mame_ui_manager::NORMAL, fgcolor, bgcolor, + nullptr, nullptr, + line_height()); + + if (larrow) + { + // not using the selected colour here because the background isn't changed + float const left(m_right_content_hbounds.first + lr_border()); + float const right(m_right_content_hbounds.first + lr_border() + lr_arrow_width()); + bool const hovered(pointer_in_rect(left, right_arrows_top(), right, right_arrows_bottom())); + bool const tracked((pointer_action::RIGHT_TRACK_ARROW == m_pointer_action) && !m_clicked_line); + rgb_t fg(fgcolor); + if ((focused_menu::RIGHTBOTTOM != m_focus) && (tracked || (!m_ui_error && !(flags & PROCESS_NOINPUT) && pointer_idle() && hovered))) + fg = ui().colors().mouseover_color(); + draw_arrow(left, right_arrows_top(), right, right_arrows_bottom(), fg, ROT90 ^ ORIENTATION_FLIP_X); } - return (y1 + line_height + UI_LINE_WIDTH); + if (rarrow) + { + // not using the selected colour here because the background isn't changed + float const left(m_right_content_hbounds.second - lr_border() - lr_arrow_width()); + float const right(m_right_content_hbounds.second - lr_border()); + bool const hovered(pointer_in_rect(left, right_arrows_top(), right, right_arrows_bottom())); + bool const tracked((pointer_action::RIGHT_TRACK_ARROW == m_pointer_action) && m_clicked_line); + rgb_t fg(fgcolor); + if ((focused_menu::RIGHTBOTTOM != m_focus) && (tracked || (!m_ui_error && !(flags & PROCESS_NOINPUT) && pointer_idle() && hovered))) + fg = ui().colors().mouseover_color(); + draw_arrow(left, right_arrows_top(), right, right_arrows_bottom(), fg, ROT90); + } } @@ -2256,26 +3637,23 @@ float menu_select_launch::draw_right_box_title(float x1, float y1, float x2, flo // perform our special rendering //------------------------------------------------- -void menu_select_launch::arts_render(float origx1, float origy1, float origx2, float origy2) +void menu_select_launch::arts_render(u32 flags) { + // draw the heading + draw_right_box_heading(flags, FIRST_VIEW < m_image_view, LAST_VIEW > m_image_view, _("selmenu-artwork", std::get<1>(ARTS_INFO[m_image_view]))); + ui_software_info const *software; - game_driver const *driver; - get_selection(software, driver); + ui_system_info const *system; + get_selection(software, system); - if (software && (!software->startempty || !driver)) + if (software && (!software->startempty || !system)) { - m_cache->set_snapx_driver(nullptr); - - if (m_default_image) - m_image_view = (software->startempty == 0) ? SNAPSHOT_VIEW : CABINETS_VIEW; - - // arts title and searchpath - std::string const searchstr = arts_render_common(origx1, origy1, origx2, origy2); + m_cache.set_snapx_driver(nullptr); // loads the image if necessary - if (!m_cache->snapx_software_is(software) || !snapx_valid() || m_switch_image) + if (!m_cache.snapx_software_is(software) || !snapx_valid() || m_switch_image) { - emu_file snapfile(searchstr.c_str(), OPEN_FLAG_READ); + emu_file snapfile(get_arts_searchpath(), OPEN_FLAG_READ); bitmap_argb32 tmp_bitmap; if (software->startempty == 1) @@ -2286,99 +3664,40 @@ void menu_select_launch::arts_render(float origx1, float origy1, float origx2, f else { // First attempt from name list - load_image(tmp_bitmap, snapfile, software->listname + PATH_SEPARATOR + software->shortname); + load_image(tmp_bitmap, snapfile, util::path_concat(software->listname, software->shortname)); // Second attempt from driver name + part name if (!tmp_bitmap.valid()) - load_image(tmp_bitmap, snapfile, software->driver->name + software->part + PATH_SEPARATOR + software->shortname); + load_image(tmp_bitmap, snapfile, util::path_concat(software->driver->name + software->part, software->shortname)); } - m_cache->set_snapx_software(software); + m_cache.set_snapx_software(software); m_switch_image = false; - arts_render_images(std::move(tmp_bitmap), origx1, origy1, origx2, origy2); + arts_render_images(std::move(tmp_bitmap)); } // if the image is available, loaded and valid, display it - draw_snapx(origx1, origy1, origx2, origy2); + draw_snapx(); } - else if (driver) + else if (system) { - m_cache->set_snapx_software(nullptr); - - if (m_default_image) - m_image_view = ((driver->flags & machine_flags::MASK_TYPE) != machine_flags::TYPE_ARCADE) ? CABINETS_VIEW : SNAPSHOT_VIEW; - - std::string const searchstr = arts_render_common(origx1, origy1, origx2, origy2); + m_cache.set_snapx_software(nullptr); // loads the image if necessary - if (!m_cache->snapx_driver_is(driver) || !snapx_valid() || m_switch_image) + if (!m_cache.snapx_driver_is(system->driver) || !snapx_valid() || m_switch_image) { - emu_file snapfile(searchstr, OPEN_FLAG_READ); + emu_file snapfile(get_arts_searchpath(), OPEN_FLAG_READ); bitmap_argb32 tmp_bitmap; - load_driver_image(tmp_bitmap, snapfile, *driver); + load_driver_image(tmp_bitmap, snapfile, *system->driver); - m_cache->set_snapx_driver(driver); + m_cache.set_snapx_driver(system->driver); m_switch_image = false; - arts_render_images(std::move(tmp_bitmap), origx1, origy1, origx2, origy2); + arts_render_images(std::move(tmp_bitmap)); } // if the image is available, loaded and valid, display it - draw_snapx(origx1, origy1, origx2, origy2); - } -} - - -//------------------------------------------------- -// common function for images render -//------------------------------------------------- - -std::string menu_select_launch::arts_render_common(float origx1, float origy1, float origx2, float origy2) -{ - float const line_height = ui().get_line_height(); - float const gutter_width = 0.4f * line_height * machine().render().ui_aspect(&container()) * 1.3f; - - std::string snaptext, searchstr; - get_title_search(snaptext, searchstr); - - // apply title to right panel - float title_size = 0.0f; - for (int x = FIRST_VIEW; x < LAST_VIEW; x++) - { - float text_length; - ui().draw_text_full(container(), - _(arts_info[x].first), origx1, origy1, origx2 - origx1, - ui::text_layout::CENTER, ui::text_layout::TRUNCATE, mame_ui_manager::NONE, rgb_t::white(), rgb_t::black(), - &text_length, nullptr); - title_size = (std::max)(text_length + 0.01f, title_size); + draw_snapx(); } - - rgb_t const fgcolor = (m_focus == focused_menu::RIGHTBOTTOM) ? rgb_t(0xff, 0xff, 0x00) : ui().colors().text_color(); - rgb_t const bgcolor = (m_focus == focused_menu::RIGHTBOTTOM) ? rgb_t(0xff, 0xff, 0xff) : ui().colors().text_bg_color(); - float const middle = origx2 - origx1; - - // check size - float const sc = title_size + 2.0f * gutter_width; - float const tmp_size = (sc > middle) ? ((middle - 2.0f * gutter_width) / sc) : 1.0f; - title_size *= tmp_size; - - if (bgcolor != ui().colors().text_bg_color()) - { - ui().draw_textured_box( - container(), - origx1 + ((middle - title_size) * 0.5f), origy1 + ui().box_tb_border(), - origx1 + ((middle + title_size) * 0.5f), origy1 + ui().box_tb_border() + line_height, - bgcolor, rgb_t(43, 43, 43), - hilight_main_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); - } - - ui().draw_text_full(container(), - snaptext, origx1, origy1 + ui().box_tb_border(), origx2 - origx1, - ui::text_layout::CENTER, ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, fgcolor, bgcolor, - nullptr, nullptr, tmp_size); - - draw_common_arrow(origx1, origy1 + ui().box_tb_border(), origx2, origy2, m_image_view, FIRST_VIEW, LAST_VIEW, title_size); - - return searchstr; } @@ -2386,91 +3705,87 @@ std::string menu_select_launch::arts_render_common(float origx1, float origy1, f // perform rendering of image //------------------------------------------------- -void menu_select_launch::arts_render_images(bitmap_argb32 &&tmp_bitmap, float origx1, float origy1, float origx2, float origy2) +void menu_select_launch::arts_render_images(bitmap_argb32 &&tmp_bitmap) { - bool no_available = false; - float line_height = ui().get_line_height(); - // if it fails, use the default image - if (!tmp_bitmap.valid()) + bool no_available(!tmp_bitmap.valid()); + if (no_available) { tmp_bitmap.allocate(256, 256); - const bitmap_argb32 &src(m_cache->no_avail_bitmap()); + const bitmap_argb32 &src(m_cache.no_avail_bitmap()); for (int x = 0; x < 256; x++) { for (int y = 0; y < 256; y++) tmp_bitmap.pix(y, x) = src.pix(y, x); } - no_available = true; } - bitmap_argb32 &snapx_bitmap(m_cache->snapx_bitmap()); - if (tmp_bitmap.valid()) + bitmap_argb32 &snapx_bitmap(m_cache.snapx_bitmap()); + if (!tmp_bitmap.valid()) { - float panel_width = origx2 - origx1 - 0.02f; - float panel_height = origy2 - origy1 - 0.02f - (3.0f * ui().box_tb_border()) - (2.0f * line_height); - int screen_width = machine().render().ui_target().width(); - int screen_height = machine().render().ui_target().height(); + snapx_bitmap.reset(); + return; + } - if (machine().render().ui_target().orientation() & ORIENTATION_SWAP_XY) - std::swap(screen_height, screen_width); + float const panel_width(m_right_content_hbounds.second - m_right_content_hbounds.first); + float const panel_height(m_right_content_vbounds.second - m_right_content_vbounds.first); + + auto [screen_width, screen_height] = target_size(); + if (machine().render().ui_target().orientation() & ORIENTATION_SWAP_XY) + { + using std::swap; + swap(screen_height, screen_width); + } - int panel_width_pixel = panel_width * screen_width; - int panel_height_pixel = panel_height * screen_height; + int const panel_width_pixel(panel_width * screen_width); + int const panel_height_pixel(panel_height * screen_height); - // Calculate resize ratios for resizing - auto ratioW = (float)panel_width_pixel / tmp_bitmap.width(); - auto ratioH = (float)panel_height_pixel / tmp_bitmap.height(); - auto ratioI = (float)tmp_bitmap.height() / tmp_bitmap.width(); - auto dest_xPixel = tmp_bitmap.width(); - auto dest_yPixel = tmp_bitmap.height(); + // Calculate resize ratios for resizing + auto const ratioW(float(panel_width_pixel) / float(tmp_bitmap.width())); + auto const ratioH(float(panel_height_pixel) / float(tmp_bitmap.height())); + auto const ratioI(float(tmp_bitmap.height()) / float(tmp_bitmap.width())); + auto dest_xPixel(tmp_bitmap.width()); + auto dest_yPixel(tmp_bitmap.height()); + if (ui().options().forced_4x3_snapshot() && (ratioI < 0.75F) && (m_image_view == SNAPSHOT_VIEW)) + { // force 4:3 ratio min - if (ui().options().forced_4x3_snapshot() && ratioI < 0.75f && m_image_view == SNAPSHOT_VIEW) - { - // smaller ratio will ensure that the image fits in the view - dest_yPixel = tmp_bitmap.width() * 0.75f; - ratioH = (float)panel_height_pixel / dest_yPixel; - float ratio = std::min(ratioW, ratioH); - dest_xPixel = tmp_bitmap.width() * ratio; - dest_yPixel *= ratio; - } + dest_yPixel = tmp_bitmap.width() * 0.75F; + float const ratio = std::min(ratioW, float(panel_height_pixel) / float(dest_yPixel)); + dest_xPixel = tmp_bitmap.width() * ratio; + dest_yPixel *= ratio; + } + else if ((ratioW < 1.0F) || (ratioH < 1.0F) || (ui().options().enlarge_snaps() && !no_available)) + { // resize the bitmap if necessary - else if (ratioW < 1 || ratioH < 1 || (ui().options().enlarge_snaps() && !no_available)) - { - // smaller ratio will ensure that the image fits in the view - float ratio = std::min(ratioW, ratioH); - dest_xPixel = tmp_bitmap.width() * ratio; - dest_yPixel = tmp_bitmap.height() * ratio; - } - - bitmap_argb32 dest_bitmap; - - // resample if necessary - if (dest_xPixel != tmp_bitmap.width() || dest_yPixel != tmp_bitmap.height()) - { - dest_bitmap.allocate(dest_xPixel, dest_yPixel); - render_color color = { 1.0f, 1.0f, 1.0f, 1.0f }; - render_resample_argb_bitmap_hq(dest_bitmap, tmp_bitmap, color, true); - } - else - dest_bitmap = std::move(tmp_bitmap); - - snapx_bitmap.allocate(panel_width_pixel, panel_height_pixel); - int x1 = (0.5f * panel_width_pixel) - (0.5f * dest_xPixel); - int y1 = (0.5f * panel_height_pixel) - (0.5f * dest_yPixel); + float const ratio(std::min(ratioW, ratioH)); + dest_xPixel = tmp_bitmap.width() * ratio; + dest_yPixel = tmp_bitmap.height() * ratio; + } - for (int x = 0; x < dest_xPixel; x++) - for (int y = 0; y < dest_yPixel; y++) - snapx_bitmap.pix(y + y1, x + x1) = dest_bitmap.pix(y, x); - // apply bitmap - m_cache->snapx_texture()->set_bitmap(snapx_bitmap, snapx_bitmap.cliprect(), TEXFORMAT_ARGB32); + // resample if necessary + bitmap_argb32 dest_bitmap; + if ((dest_xPixel != tmp_bitmap.width()) || (dest_yPixel != tmp_bitmap.height())) + { + dest_bitmap.allocate(dest_xPixel, dest_yPixel); + render_resample_argb_bitmap_hq(dest_bitmap, tmp_bitmap, render_color{ 1.0F, 1.0F, 1.0F, 1.0F }, true); } else { - snapx_bitmap.reset(); + dest_bitmap = std::move(tmp_bitmap); } + + snapx_bitmap.allocate(panel_width_pixel, panel_height_pixel); + int x1(0.5F * (float(panel_width_pixel) - float(dest_xPixel))); + int y1(0.5F * (float(panel_height_pixel) - float(dest_yPixel))); + + for (int x = 0; x < dest_xPixel; x++) + for (int y = 0; y < dest_yPixel; y++) + snapx_bitmap.pix(y + y1, x + x1) = dest_bitmap.pix(y, x); + + // apply bitmap + m_cache.snapx_texture()->set_bitmap(snapx_bitmap, snapx_bitmap.cliprect(), TEXFORMAT_ARGB32); } @@ -2478,22 +3793,66 @@ void menu_select_launch::arts_render_images(bitmap_argb32 &&tmp_bitmap, float or // draw snapshot //------------------------------------------------- -void menu_select_launch::draw_snapx(float origx1, float origy1, float origx2, float origy2) +void menu_select_launch::draw_snapx() { // if the image is available, loaded and valid, display it if (snapx_valid()) { - float const line_height = ui().get_line_height(); - float const x1 = origx1 + 0.01f; - float const x2 = origx2 - 0.01f; - float const y1 = origy1 + (2.0f * ui().box_tb_border()) + line_height; - float const y2 = origy2 - ui().box_tb_border() - line_height; + container().add_quad( + m_right_content_hbounds.first, m_right_content_vbounds.first, m_right_content_hbounds.second, m_right_content_vbounds.second, + rgb_t::white(), m_cache.snapx_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + } +} + + +char const *menu_select_launch::right_panel_config_string() const +{ + assert(std::size(RIGHT_PANEL_NAMES) > m_right_panel); + return RIGHT_PANEL_NAMES[m_right_panel].first; +} + +char const *menu_select_launch::right_image_config_string() const +{ + assert(std::size(ARTS_INFO) > m_image_view); + return std::get<0>(ARTS_INFO[m_image_view]); +} + +void menu_select_launch::set_right_panel(u8 index) +{ + assert(std::size(RIGHT_PANEL_NAMES) > index); + m_right_panel = index; +} - // apply texture - container().add_quad(x1, y1, x2, y2, rgb_t::white(), m_cache->snapx_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); +void menu_select_launch::set_right_image(u8 index) +{ + assert(std::size(ARTS_INFO) > index); + if (index != m_image_view) + { + m_image_view = index; + set_switch_image(); } } +void menu_select_launch::set_right_panel(std::string_view value) +{ + auto const found = std::find_if( + std::begin(RIGHT_PANEL_NAMES), + std::end(RIGHT_PANEL_NAMES), + [&value] (auto const &that) { return value == that.first; }); + if (std::end(RIGHT_PANEL_NAMES) != found) + m_right_panel = found - std::begin(RIGHT_PANEL_NAMES); +} + +void menu_select_launch::set_right_image(std::string_view value) +{ + auto const found = std::find_if( + std::begin(ARTS_INFO), + std::end(ARTS_INFO), + [&value] (auto const &that) { return value == std::get<0>(that); }); + if (std::end(ARTS_INFO) != found) + m_image_view = found - std::begin(ARTS_INFO); +} + std::string menu_select_launch::make_system_audit_fail_text(media_auditor const &auditor, media_auditor::summary summary) { @@ -2588,7 +3947,7 @@ bool menu_select_launch::has_multiple_bios(game_driver const &driver, s_bios &bi for (romload::system_bios const &bios : romload::entries(driver.rom).get_system_bioses()) { std::string name(bios.get_description()); - uint32_t const bios_flags(bios.get_value()); + u32 const bios_flags(bios.get_value()); if (default_name && !std::strcmp(bios.get_name(), default_name)) { @@ -2604,68 +3963,27 @@ bool menu_select_launch::has_multiple_bios(game_driver const &driver, s_bios &bi } -void menu_select_launch::exit(running_machine &machine) -{ - std::lock_guard<std::mutex> guard(s_cache_guard); - s_caches.erase(&machine); -} - - -//------------------------------------------------- -// draw collapsed left panel -//------------------------------------------------- - -float menu_select_launch::draw_collapsed_left_panel(float x1, float y1, float x2, float y2) -{ - float const aspect = machine().render().ui_aspect(&container()); - float const space = x2 - x1; - float const lr_arrow_width = 0.4f * space * aspect; - - // set left-right arrows dimension - float const ar_x0 = 0.5f * (x2 + x1) - (0.5f * lr_arrow_width); - float const ar_y0 = 0.5f * (y2 + y1) + (0.1f * space); - float const ar_x1 = ar_x0 + lr_arrow_width; - float const ar_y1 = 0.5f * (y2 + y1) + (0.9f * space); - - ui().draw_outlined_box(container(), x1, y1, x2, y2, rgb_t(0xef, 0x12, 0x47, 0x7b)); // FIXME: magic numbers in colour? - - rgb_t fgcolor = ui().colors().text_color(); - if (mouse_in_rect(x1, y1, x2, y2)) - { - fgcolor = ui().options().mouseover_color(); - set_hover(HOVER_LPANEL_ARROW); - } - - draw_arrow(ar_x0, ar_y0, ar_x1, ar_y1, fgcolor, ROT90); - - return x2 + ui().box_lr_border() * aspect; -} - - //------------------------------------------------- // draw infos //------------------------------------------------- -void menu_select_launch::infos_render(float origx1, float origy1, float origx2, float origy2) +void menu_select_launch::infos_render(u32 flags) { - float const line_height = ui().get_line_height(); - float text_size = ui().options().infos_size(); - std::vector<int> xstart; - std::vector<int> xend; - const char *first = ""; + std::string_view first; ui_software_info const *software; - game_driver const *driver; + ui_system_info const *system; int total; - get_selection(software, driver); + get_selection(software, system); - if (software && (!software->startempty || !driver)) + if (software && !software->startempty) { m_info_driver = nullptr; - first = __("Usage"); + first = _("Software List Info"); if ((m_info_software != software) || (m_info_view != ui_globals::cur_sw_dats_view)) { m_info_buffer.clear(); + m_info_layout = std::nullopt; if (software == m_info_software) { m_info_view = ui_globals::cur_sw_dats_view; @@ -2683,7 +4001,7 @@ void menu_select_launch::infos_render(float origx1, float origy1, float origx2, if (m_info_view == 0) { - m_info_buffer = software->usage; + m_info_buffer = software->infotext; } else { @@ -2693,34 +4011,38 @@ void menu_select_launch::infos_render(float origx1, float origy1, float origx2, } total = ui_globals::cur_sw_dats_total; } - else if (driver) + else if (system || (software && software->driver)) { + game_driver const &driver(system ? *system->driver : *software->driver); m_info_software = nullptr; - first = __("General Info"); + first = _("General Info"); - if (driver != m_info_driver || ui_globals::curdats_view != m_info_view) + if (&driver != m_info_driver || ui_globals::curdats_view != m_info_view) { m_info_buffer.clear(); - if (driver == m_info_driver) + m_info_layout = std::nullopt; + if (&driver == m_info_driver) { m_info_view = ui_globals::curdats_view; } else { - m_info_driver = driver; + m_info_driver = &driver; m_info_view = 0; ui_globals::curdats_view = 0; m_items_list.clear(); - mame_machine_manager::instance()->lua()->call_plugin("data_list", driver->name, m_items_list); + mame_machine_manager::instance()->lua()->call_plugin("data_list", driver.name, m_items_list); ui_globals::curdats_total = m_items_list.size() + 1; } if (m_info_view == 0) - general_info(driver, m_info_buffer); + { + general_info(system, driver, m_info_buffer); + } else { - m_info_buffer = ""; + m_info_buffer.clear(); mame_machine_manager::instance()->lua()->call_plugin("data", m_info_view - 1, m_info_buffer); } } @@ -2731,163 +4053,246 @@ void menu_select_launch::infos_render(float origx1, float origy1, float origx2, return; } - origy1 += ui().box_tb_border(); - float const aspect(machine().render().ui_aspect(&container())); - float const gutter_width = 0.4f * line_height * aspect * 1.3f; - float const ud_arrow_width = line_height * aspect; - float oy1 = origy1 + line_height; + // draw the heading + std::string_view const snaptext(m_info_view ? std::string_view(m_items_list[m_info_view - 1]) : first); + draw_right_box_heading(flags, 0 < m_info_view, (total - 1) > m_info_view, snaptext); - std::string_view const snaptext(m_info_view ? std::string_view(m_items_list[m_info_view - 1]) : std::string_view(_(first))); - - // get width of widest title - float title_size(0.0f); - for (std::size_t x = 0; total > x; ++x) + float const sc(m_right_panel_width - (2.0F * gutter_width())); + if (!m_info_layout || (m_info_layout->width() != sc)) { - std::string_view const name(x ? std::string_view(m_items_list[x - 1]) : std::string_view(_(first))); - float txt_length(0.0f); - ui().draw_text_full( - container(), name, - origx1, origy1, origx2 - origx1, - ui::text_layout::CENTER, ui::text_layout::NEVER, - mame_ui_manager::NONE, ui().colors().text_color(), ui().colors().text_bg_color(), - &txt_length, nullptr); - txt_length += 0.01f; - title_size = (std::max)(txt_length, title_size); + m_info_layout.emplace( + *ui().get_font(), + x_aspect() * m_info_line_height, m_info_line_height, + sc, + text_layout::text_justify::LEFT, text_layout::word_wrapping::WORD); + menu_dats_view::add_info_text(*m_info_layout, m_info_buffer, ui().colors().text_color()); + m_total_lines = m_info_layout->lines(); } - rgb_t fgcolor = ui().colors().text_color(); - rgb_t bgcolor = ui().colors().text_bg_color(); - if (get_focus() == focused_menu::RIGHTBOTTOM) - { - fgcolor = rgb_t(0xff, 0xff, 0xff, 0x00); - bgcolor = rgb_t(0xff, 0xff, 0xff, 0xff); - } + m_right_visible_lines = floor((m_right_content_vbounds.second - m_right_content_vbounds.first) / m_info_line_height); + if (m_total_lines < m_right_visible_lines) + m_right_visible_lines = m_total_lines; + if (m_topline_datsview < 0) + m_topline_datsview = 0; + if ((m_topline_datsview + m_right_visible_lines) >= m_total_lines) + m_topline_datsview = m_total_lines - m_right_visible_lines; + + // get the number of visible lines, minus 1 for top arrow and 1 for bottom arrow + bool const up_arrow(!info_at_top()); + bool const down_arrow(!info_at_bottom()); + int const r_visible_lines(m_right_visible_lines - (up_arrow ? 1 : 0) - (down_arrow ? 1 : 0)); + + if (up_arrow) + draw_info_arrow(flags, 0); + if (down_arrow) + draw_info_arrow(flags, m_right_visible_lines - 1); + + m_info_layout->emit( + container(), + m_topline_datsview ? (m_topline_datsview + 1) : 0, r_visible_lines, + right_panel_left() + gutter_width(), m_right_content_vbounds.first + (m_topline_datsview ? m_info_line_height : 0.0f)); +} - float middle = origx2 - origx1; - // check size - float sc = title_size + 2.0f * gutter_width; - float tmp_size = (sc > middle) ? ((middle - 2.0f * gutter_width) / sc) : 1.0f; - title_size *= tmp_size; +//------------------------------------------------- +// generate general info +//------------------------------------------------- - if (bgcolor != ui().colors().text_bg_color()) - { - ui().draw_textured_box(container(), origx1 + ((middle - title_size) * 0.5f), origy1, origx1 + ((middle + title_size) * 0.5f), - origy1 + line_height, bgcolor, rgb_t(255, 43, 43, 43), hilight_main_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); - } +void menu_select_launch::general_info(ui_system_info const *system, game_driver const &driver, std::string &buffer) +{ + system_flags const &flags(get_system_flags(driver)); + std::ostringstream str; - ui().draw_text_full(container(), snaptext, origx1, origy1, origx2 - origx1, ui::text_layout::CENTER, - ui::text_layout::NEVER, mame_ui_manager::NORMAL, fgcolor, bgcolor, nullptr, nullptr, tmp_size); + str << "#j2\n"; - char justify = 'l'; // left justify - if ((m_info_buffer.length() >= 3) && (m_info_buffer[0] == '#')) - { - if (m_info_buffer[1] == 'j') - justify = m_info_buffer[2]; - } + if (system) + str << system->description; + else + str << driver.type.fullname(); + str << "\t\n\n"; + + util::stream_format(str, _("Short Name\t%1$s\n"), driver.name); + util::stream_format(str, _("Year\t%1$s\n"), driver.year); + util::stream_format(str, _("Manufacturer\t%1$s\n"), driver.manufacturer); - draw_common_arrow(origx1, origy1, origx2, origy2, m_info_view, 0, total - 1, title_size); - if (justify == 'f') + int cloneof = driver_list::non_bios_clone(driver); + if (0 <= cloneof) { - m_total_lines = ui().wrap_text( - container(), m_info_buffer, - 0.0f, 0.0f, 1.0f - (2.0f * gutter_width), - xstart, xend, - text_size); + util::stream_format( + str, + _("System is Clone of\t%1$s\n"), + system ? std::string_view(system->parent) : std::string_view(driver_list::driver(cloneof).type.fullname())); } else { - m_total_lines = ui().wrap_text( - container(), m_info_buffer, - origx1, origy1, origx2 - origx1 - (2.0f * gutter_width), - xstart, xend, - text_size); + str << _("System is Parent\t\n"); } - int r_visible_lines = floor((origy2 - oy1) / (line_height * text_size)); - if (m_total_lines < r_visible_lines) - r_visible_lines = m_total_lines; - if (m_topline_datsview < 0) - m_topline_datsview = 0; - if (m_topline_datsview + r_visible_lines >= m_total_lines) - m_topline_datsview = m_total_lines - r_visible_lines; - - if (mouse_in_rect(origx1 + gutter_width, oy1, origx2 - gutter_width, origy2)) - set_hover(HOVER_INFO_TEXT); + if (flags.has_analog()) + str << _("Analog Controls\tYes\n"); + if (flags.has_keyboard()) + str << _("Keyboard Inputs\tYes\n"); - sc = origx2 - origx1 - (2.0f * gutter_width); - for (int r = 0; r < r_visible_lines; ++r) + if (flags.machine_flags() & machine_flags::NOT_WORKING) + str << _("Overall\tNOT WORKING\n"); + else if ((flags.unemulated_features() | flags.imperfect_features()) & device_t::feature::PROTECTION) + str << _("Overall\tUnemulated Protection\n"); + else + str << _("Overall\tWorking\n"); + + if (flags.unemulated_features() & device_t::feature::GRAPHICS) + str << _("Graphics\tUnimplemented\n"); + else if (flags.unemulated_features() & device_t::feature::PALETTE) + str << _("Graphics\tWrong Colors\n"); + else if (flags.imperfect_features() & device_t::feature::PALETTE) + str << _("Graphics\tImperfect Colors\n"); + else if (flags.imperfect_features() & device_t::feature::GRAPHICS) + str << _("Graphics\tImperfect\n"); + else + str << _("Graphics\tOK\n"); + + if (flags.machine_flags() & machine_flags::NO_SOUND_HW) + str << _("Sound\tNone\n"); + else if (flags.unemulated_features() & device_t::feature::SOUND) + str << _("Sound\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::SOUND) + str << _("Sound\tImperfect\n"); + else + str << _("Sound\tOK\n"); + + if (flags.unemulated_features() & device_t::feature::CAPTURE) + str << _("Capture\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::CAPTURE) + str << _("Capture\tImperfect\n"); + + if (flags.unemulated_features() & device_t::feature::CAMERA) + str << _("Camera\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::CAMERA) + str << _("Camera\tImperfect\n"); + + if (flags.unemulated_features() & device_t::feature::MICROPHONE) + str << _("Microphone\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::MICROPHONE) + str << _("Microphone\tImperfect\n"); + + if (flags.unemulated_features() & device_t::feature::CONTROLS) + str << _("Controls\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::CONTROLS) + str << _("Controls\tImperfect\n"); + + if (flags.unemulated_features() & device_t::feature::KEYBOARD) + str << _("Keyboard\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::KEYBOARD) + str << _("Keyboard\tImperfect\n"); + + if (flags.unemulated_features() & device_t::feature::MOUSE) + str << _("Mouse\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::MOUSE) + str << _("Mouse\tImperfect\n"); + + if (flags.unemulated_features() & device_t::feature::MEDIA) + str << _("Media\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::MEDIA) + str << _("Media\tImperfect\n"); + + if (flags.unemulated_features() & device_t::feature::DISK) + str << _("Disk\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::DISK) + str << _("Disk\tImperfect\n"); + + if (flags.unemulated_features() & device_t::feature::PRINTER) + str << _("Printer\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::PRINTER) + str << _("Printer\tImperfect\n"); + + if (flags.unemulated_features() & device_t::feature::TAPE) + str << _("Mag. Tape\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::TAPE) + str << _("Mag. Tape\tImperfect\n"); + + if (flags.unemulated_features() & device_t::feature::PUNCH) + str << _("Punch Tape\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::PUNCH) + str << _("Punch Tape\tImperfect\n"); + + if (flags.unemulated_features() & device_t::feature::DRUM) + str << _("Mag. Drum\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::DRUM) + str << _("Mag. Drum\tImperfect\n"); + + if (flags.unemulated_features() & device_t::feature::ROM) + str << _("(EP)ROM\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::ROM) + str << _("(EP)ROM\tImperfect\n"); + + if (flags.unemulated_features() & device_t::feature::COMMS) + str << _("Communications\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::COMMS) + str << _("Communications\tImperfect\n"); + + if (flags.unemulated_features() & device_t::feature::LAN) + str << _("LAN\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::LAN) + str << _("LAN\tImperfect\n"); + + if (flags.unemulated_features() & device_t::feature::WAN) + str << _("WAN\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::WAN) + str << _("WAN\tImperfect\n"); + + if (flags.unemulated_features() & device_t::feature::TIMING) + str << _("Timing\tUnimplemented\n"); + else if (flags.imperfect_features() & device_t::feature::TIMING) + str << _("Timing\tImperfect\n"); + + str << ((flags.machine_flags() & machine_flags::MECHANICAL) ? _("Mechanical System\tYes\n") : _("Mechanical System\tNo\n")); + str << ((flags.machine_flags() & machine_flags::REQUIRES_ARTWORK) ? _("Requires Artwork\tYes\n") : _("Requires Artwork\tNo\n")); + if (flags.machine_flags() & machine_flags::NO_COCKTAIL) + str << _("Support Cocktail\tNo\n"); + str << ((flags.machine_flags() & machine_flags::IS_BIOS_ROOT) ? _("System is BIOS\tYes\n") : _("System is BIOS\tNo\n")); + str << ((flags.machine_flags() & machine_flags::SUPPORTS_SAVE) ? _("Support Save\tYes\n") : _("Support Save\tNo\n")); + str << ((flags.machine_flags() & ORIENTATION_SWAP_XY) ? _("Screen Orientation\tVertical\n") : _("Screen Orientation\tHorizontal\n")); + bool found = false; + for (romload::region const ®ion : romload::entries(driver.rom).get_regions()) { - int itemline = r + m_topline_datsview; - std::string_view const tempbuf(std::string_view(m_info_buffer).substr(xstart[itemline], xend[itemline] - xstart[itemline])); - if (tempbuf[0] == '#') - continue; - - if (r == 0 && m_topline_datsview != 0) // up arrow + if (region.is_diskdata()) { - draw_info_arrow(0, origx1, origx2, oy1, line_height, text_size, ud_arrow_width); - } - else if (r == r_visible_lines - 1 && itemline != m_total_lines - 1) // bottom arrow - { - draw_info_arrow(1, origx1, origx2, oy1, line_height, text_size, ud_arrow_width); + found = true; + break; } - else if (justify == '2') // two-column layout - { - // split at first tab - std::string_view::size_type const splitpos(tempbuf.find('\t')); - std::string_view const leftcol(tempbuf.substr(0, (std::string_view::npos == splitpos) ? 0U : splitpos)); - std::string_view const rightcol(tempbuf.substr((std::string_view::npos == splitpos) ? 0U : (splitpos + 1U))); - - // measure space needed, condense if necessary - float const leftlen(ui().get_string_width(leftcol, text_size)); - float const rightlen(ui().get_string_width(rightcol, text_size)); - float const textlen(leftlen + rightlen); - float const tmp_size3((textlen > sc) ? (text_size * (sc / textlen)) : text_size); + } + str << (found ? _("Requires CHD\tYes\n") : _("Requires CHD\tNo\n")); - // draw in two parts - ui().draw_text_full( - container(), leftcol, - origx1 + gutter_width, oy1, sc, - ui::text_layout::LEFT, ui::text_layout::TRUNCATE, - mame_ui_manager::NORMAL, ui().colors().text_color(), ui().colors().text_bg_color(), - nullptr, nullptr, - tmp_size3); - ui().draw_text_full( - container(), rightcol, - origx1 + gutter_width, oy1, sc, - ui::text_layout::RIGHT, ui::text_layout::TRUNCATE, - mame_ui_manager::NORMAL, ui().colors().text_color(), ui().colors().text_bg_color(), - nullptr, nullptr, - tmp_size3); - } - else if (justify == 'f' || justify == 'p') // full or partial justify - { - // check size - float const textlen = ui().get_string_width(tempbuf, text_size); - float tmp_size3 = (textlen > sc) ? text_size * (sc / textlen) : text_size; - ui().draw_text_full( - container(), tempbuf, - origx1 + gutter_width, oy1, origx2 - origx1, - ui::text_layout::LEFT, ui::text_layout::TRUNCATE, - mame_ui_manager::NORMAL, ui().colors().text_color(), ui().colors().text_bg_color(), - nullptr, nullptr, - tmp_size3); - } + // audit the game first to see if we're going to work + if (ui().options().info_audit()) + { + driver_enumerator enumerator(machine().options(), driver); + enumerator.next(); + media_auditor auditor(enumerator); + media_auditor::summary summary = auditor.audit_media(AUDIT_VALIDATE_FAST); + media_auditor::summary summary_samples = auditor.audit_samples(); + + // if everything looks good, schedule the new driver + if (audit_passed(summary)) + str << _("Media Audit Result\tOK\n"); else - { - ui().draw_text_full( - container(), tempbuf, - origx1 + gutter_width, oy1, origx2 - origx1, - ui::text_layout::LEFT, ui::text_layout::TRUNCATE, - mame_ui_manager::NORMAL, ui().colors().text_color(), ui().colors().text_bg_color(), - nullptr, nullptr, - text_size); - } + str << _("Media Audit Result\tBAD\n"); - oy1 += (line_height * text_size); + if (summary_samples == media_auditor::NONE_NEEDED) + str << _("Samples Audit Result\tNone Needed\n"); + else if (audit_passed(summary_samples)) + str << _("Samples Audit Result\tOK\n"); + else + str << _("Samples Audit Result\tBAD\n"); } - // return the number of visible lines, minus 1 for top arrow and 1 for bottom arrow - m_right_visible_lines = r_visible_lines - (m_topline_datsview != 0) - (m_topline_datsview + r_visible_lines != m_total_lines); + else + { + str << _("Media Audit\tDisabled\nSamples Audit\tDisabled\n"); + } + + util::stream_format(str, _("Source File\t%1$s\n"), info_xml_creator::format_sourcefile(driver.type.source())); + + buffer = std::move(str).str(); } } // namespace ui |