diff options
Diffstat (limited to 'src/frontend')
146 files changed, 32081 insertions, 16294 deletions
diff --git a/src/frontend/mame/audit.cpp b/src/frontend/mame/audit.cpp index a33dd4bb52a..dbbb9ad8f3d 100644 --- a/src/frontend/mame/audit.cpp +++ b/src/frontend/mame/audit.cpp @@ -9,14 +9,188 @@ ***************************************************************************/ #include "emu.h" -#include "emuopts.h" #include "audit.h" -#include "chd.h" + +#include "sound/samples.h" + +#include "emuopts.h" #include "drivenum.h" +#include "fileio.h" #include "romload.h" -#include "sound/samples.h" #include "softlist_dev.h" +#include "chd.h" +#include "path.h" + +#include <algorithm> + +//#define VERBOSE 1 +#define LOG_OUTPUT_FUNC osd_printf_verbose +#include "logmacro.h" + + +namespace { + +struct parent_rom +{ + parent_rom(device_type t, rom_entry const *r) : type(t), name(r->name()), hashes(r->hashdata()), length(rom_file_size(r)) { } + + std::reference_wrapper<std::remove_reference_t<device_type> > type; + std::string name; + util::hash_collection hashes; + uint64_t length; +}; + + +class parent_rom_vector : public std::vector<parent_rom> +{ +public: + using std::vector<parent_rom>::vector; + + void remove_redundant_parents(device_t const &device) + { + // remove parents with no shared ROMs + const_reverse_iterator firstparent(crend()); + for (rom_entry const *region = rom_first_region(device); region && ((crend() == firstparent) || (back().type.get() != firstparent->type.get())); region = rom_next_region(region)) + { + for (rom_entry const *rom = rom_first_file(region); rom; rom = rom_next_file(rom)) + { + util::hash_collection const hashes(rom->hashdata()); + auto const match( + std::find_if( + crbegin(), + firstparent, + [rom, &hashes] (parent_rom const &r) + { + if (r.length != rom_file_size(rom)) + return false; + else if (!hashes.flag(util::hash_collection::FLAG_NO_DUMP)) + return r.hashes == hashes; + else + return r.name == rom->name(); + })); + if (match != firstparent) + { + firstparent = std::find_if( + crbegin(), + match, + [&match] (parent_rom const &r) { return r.type.get() == match->type.get(); }); + if (back().type.get() == match->type.get()) + break; + } + } + } + erase(firstparent.base(), cend()); + + while (!empty()) + { + // find where the next parent starts + auto const last( + std::find_if( + std::next(cbegin()), + cend(), + [this] (parent_rom const &r) { return &front().type.get() != &r.type.get(); })); + + // examine dumped ROMs in this generation + for (auto i = cbegin(); last != i; ++i) + { + if (!i->hashes.flag(util::hash_collection::FLAG_NO_DUMP)) + { + auto const match( + std::find_if( + last, + cend(), + [&i] (parent_rom const &r) { return (i->length == r.length) && (i->hashes == r.hashes); })); + if (cend() == match) + return; + } + } + erase(cbegin(), last); + } + } + + std::add_pointer_t<device_type> find_shared_device(device_t ¤t, std::string_view name, util::hash_collection const &hashes, uint64_t length) const + { + // if we're examining a child device, it will always have a perfect match + if (current.owner()) + return ¤t.type(); + + // scan backwards through parents for a matching definition + bool const dumped(!hashes.flag(util::hash_collection::FLAG_NO_DUMP)); + std::add_pointer_t<device_type> best(nullptr); + for (const_reverse_iterator it = crbegin(); crend() != it; ++it) + { + if (it->length == length) + { + if (dumped) + { + if (it->hashes == hashes) + return &it->type.get(); + } + else if (it->name == name) + { + if (it->hashes.flag(util::hash_collection::FLAG_NO_DUMP)) + return &it->type.get(); + else if (!best) + best = &it->type.get(); + } + } + } + return best; + } + + std::pair<std::add_pointer_t<device_type>, bool> actual_matches_shared(device_t ¤t, media_auditor::audit_record const &record) + { + // no result if no matching file was found + if ((record.status() != media_auditor::audit_status::GOOD) && (record.status() != media_auditor::audit_status::FOUND_INVALID)) + return std::make_pair(nullptr, false); + + // if we're examining a child device, scan it first + bool matches_device_undumped(false); + if (current.owner()) + { + for (const rom_entry *region = rom_first_region(current); region; region = rom_next_region(region)) + { + for (const rom_entry *rom = rom_first_file(region); rom; rom = rom_next_file(rom)) + { + if (rom_file_size(rom) == record.actual_length()) + { + util::hash_collection const hashes(rom->hashdata()); + if (hashes == record.actual_hashes()) + return std::make_pair(¤t.type(), empty()); + else if (hashes.flag(util::hash_collection::FLAG_NO_DUMP) && (rom->name() == record.name())) + matches_device_undumped = true; + } + } + } + } + + // look for a matching parent ROM + std::add_pointer_t<device_type> closest_bad(nullptr); + for (const_reverse_iterator it = crbegin(); crend() != it; ++it) + { + if (it->length == record.actual_length()) + { + if (it->hashes == record.actual_hashes()) + return std::make_pair(&it->type.get(), it->type.get() == front().type.get()); + else if (it->hashes.flag(util::hash_collection::FLAG_NO_DUMP) && (it->name == record.name())) + closest_bad = &it->type.get(); + } + } + + // fall back to the nearest bad dump + if (closest_bad) + return std::make_pair(closest_bad, front().type.get() == *closest_bad); + else if (matches_device_undumped) + return std::make_pair(¤t.type(), empty()); + else + return std::make_pair(nullptr, false); + } +}; + +} // anonymous namespace + + //************************************************************************** // CORE FUNCTIONS @@ -29,7 +203,6 @@ media_auditor::media_auditor(const driver_enumerator &enumerator) : m_enumerator(enumerator) , m_validation(AUDIT_VALIDATE_FULL) - , m_searchpath(nullptr) { } @@ -47,68 +220,100 @@ media_auditor::summary media_auditor::audit_media(const char *validation) // store validation for later m_validation = validation; -// temporary hack until romload is update: get the driver path and support it for -// all searches -const char *driverpath = m_enumerator.config()->root_device().searchpath(); + // first walk the parent chain for required ROMs + parent_rom_vector parentroms; + for (auto drvindex = m_enumerator.find(m_enumerator.driver().parent); 0 <= drvindex; drvindex = m_enumerator.find(m_enumerator.driver(drvindex).parent)) + { + game_driver const &parent(m_enumerator.driver(drvindex)); + LOG("Checking parent %s for ROM files\n", parent.type.shortname()); + std::vector<rom_entry> const roms(rom_build_entries(parent.rom)); + for (rom_entry const *region = rom_first_region(&roms.front()); region; region = rom_next_region(region)) + { + for (rom_entry const *rom = rom_first_file(region); rom; rom = rom_next_file(rom)) + { + LOG("Adding parent ROM %s\n", rom->name()); + parentroms.emplace_back(parent.type, rom); + } + } + } + parentroms.remove_redundant_parents(m_enumerator.config()->root_device()); - std::size_t found = 0; - std::size_t required = 0; - std::size_t shared_found = 0; - std::size_t shared_required = 0; + // count ROMs required/found + std::size_t found(0); + std::size_t required(0); + std::size_t shared_found(0); + std::size_t shared_required(0); + std::size_t parent_found(0); // iterate over devices and regions - for (device_t &device : device_iterator(m_enumerator.config()->root_device())) + std::vector<std::string> searchpath; + for (device_t &device : device_enumerator(m_enumerator.config()->root_device())) { - // determine the search path for this source and iterate through the regions - m_searchpath = device.searchpath(); + searchpath.clear(); // now iterate over regions and ROMs within for (const rom_entry *region = rom_first_region(device); region; region = rom_next_region(region)) { -// temporary hack: add the driver path & region name -std::string combinedpath = util::string_format("%s;%s", device.searchpath(), driverpath); -if (device.shortname()) - combinedpath.append(";").append(device.shortname()); -m_searchpath = combinedpath.c_str(); - for (const rom_entry *rom = rom_first_file(region); rom; rom = rom_next_file(rom)) { - char const *const name(ROM_GETNAME(rom)); - util::hash_collection const hashes(ROM_GETHASHDATA(rom)); - device_t *const shared_device(find_shared_device(device, name, hashes, ROM_GETLENGTH(rom))); + if (searchpath.empty()) + { + LOG("Audit media for device %s(%s)\n", device.shortname(), device.tag()); + searchpath = device.searchpath(); + } + + // look for a matching parent or device ROM + std::string const &name(rom->name()); + util::hash_collection const hashes(rom->hashdata()); + bool const dumped(!hashes.flag(util::hash_collection::FLAG_NO_DUMP)); + std::add_pointer_t<device_type> const shared_device(parentroms.find_shared_device(device, name, hashes, rom_file_size(rom))); + if (shared_device) + LOG("File '%s' %s%sdumped shared with %s\n", name, ROM_ISOPTIONAL(rom) ? "optional " : "", dumped ? "" : "un", shared_device->shortname()); + else + LOG("File '%s' %s%sdumped\n", name, ROM_ISOPTIONAL(rom) ? "optional " : "", dumped ? "" : "un"); // count the number of files with hashes - if (!hashes.flag(util::hash_collection::FLAG_NO_DUMP) && !ROM_ISOPTIONAL(rom)) + if (dumped && !ROM_ISOPTIONAL(rom)) { required++; if (shared_device) shared_required++; } - audit_record *record = nullptr; + audit_record *record(nullptr); if (ROMREGION_ISROMDATA(region)) - record = &audit_one_rom(rom); + record = &audit_one_rom(searchpath, rom); else if (ROMREGION_ISDISKDATA(region)) - record = &audit_one_disk(rom, nullptr); + record = &audit_one_disk(rom, device); if (record) { + // see if the actual content found belongs to a parent + auto const matchesshared(parentroms.actual_matches_shared(device, *record)); + if (matchesshared.first) + LOG("Actual ROM file shared with %sparent %s\n", matchesshared.second ? "immediate " : "", matchesshared.first->shortname()); + // count the number of files that are found. - if ((record->status() == audit_status::GOOD) || ((record->status() == audit_status::FOUND_INVALID) && !find_shared_device(device, name, record->actual_hashes(), record->actual_length()))) + if ((record->status() == audit_status::GOOD) || ((record->status() == audit_status::FOUND_INVALID) && !matchesshared.first)) { found++; if (shared_device) shared_found++; + if (matchesshared.second) + parent_found++; } record->set_shared_device(shared_device); } } } + + if (!searchpath.empty()) + LOG("Total required=%u (shared=%u) found=%u (shared=%u parent=%u)\n", required, shared_required, found, shared_found, parent_found); } - // if we only find files that are in the parent & either the set has no unique files or the parent is not found, then assume we don't have the set at all - if ((found == shared_found) && (required > 0) && ((required != shared_required) || (shared_found == 0))) + // if we only find files that are in the parent and either the set has no unique files or the parent is not found, then assume we don't have the set at all + if ((found == shared_found) && required && (found != required) && ((required != shared_required) || !parent_found)) { m_record_list.clear(); return NOTFOUND; @@ -130,12 +335,32 @@ media_auditor::summary media_auditor::audit_device(device_t &device, const char // store validation for later m_validation = validation; - m_searchpath = device.shortname(); std::size_t found = 0; std::size_t required = 0; - audit_regions(rom_first_region(device), nullptr, found, required); + std::vector<std::string> searchpath; + audit_regions( + [this, &device, &searchpath] (rom_entry const *region, rom_entry const *rom) -> audit_record const * + { + if (ROMREGION_ISROMDATA(region)) + { + if (searchpath.empty()) + searchpath = device.searchpath(); + return &audit_one_rom(searchpath, rom); + } + else if (ROMREGION_ISDISKDATA(region)) + { + return &audit_one_disk(rom, device); + } + else + { + return nullptr; + } + }, + rom_first_region(device), + found, + required); if ((found == 0) && (required > 0)) { @@ -151,7 +376,7 @@ media_auditor::summary media_auditor::audit_device(device_t &device, const char //------------------------------------------------- // audit_software //------------------------------------------------- -media_auditor::summary media_auditor::audit_software(const std::string &list_name, const software_info *swinfo, const char *validation) +media_auditor::summary media_auditor::audit_software(software_list_device &swlist, const software_info &swinfo, const char *validation) { // start fresh m_record_list.clear(); @@ -159,21 +384,31 @@ media_auditor::summary media_auditor::audit_software(const std::string &list_nam // store validation for later m_validation = validation; - std::string combinedpath(util::string_format("%s;%s%s%s", swinfo->shortname(), list_name, PATH_SEPARATOR, swinfo->shortname())); - std::string locationtag(util::string_format("%s%%%s%%", list_name, swinfo->shortname())); - if (!swinfo->parentname().empty()) - { - locationtag.append(swinfo->parentname()); - combinedpath.append(util::string_format(";%s;%s%s%s", swinfo->parentname(), list_name, PATH_SEPARATOR, swinfo->parentname())); - } - m_searchpath = combinedpath.c_str(); - std::size_t found = 0; std::size_t required = 0; // now iterate over software parts - for (const software_part &part : swinfo->parts()) - audit_regions(part.romdata().data(), locationtag.c_str(), found, required); + std::vector<std::string> searchpath; + auto const do_audit = + [this, &swlist, &swinfo, &searchpath] (rom_entry const *region, rom_entry const *rom) -> audit_record const * + { + if (ROMREGION_ISROMDATA(region)) + { + if (searchpath.empty()) + searchpath = rom_load_manager::get_software_searchpath(swlist, swinfo); + return &audit_one_rom(searchpath, rom); + } + else if (ROMREGION_ISDISKDATA(region)) + { + return &audit_one_disk(rom, swlist, swinfo); + } + else + { + return nullptr; + } + }; + for (const software_part &part : swinfo.parts()) + audit_regions(do_audit, part.romdata().data(), found, required); if ((found == 0) && (required > 0)) { @@ -182,7 +417,7 @@ media_auditor::summary media_auditor::audit_software(const std::string &list_nam } // return a summary - return summarize(list_name.c_str()); + return summarize(swlist.list_name().c_str()); } @@ -200,7 +435,7 @@ media_auditor::summary media_auditor::audit_samples() std::size_t found = 0; // iterate over sample entries - for (samples_device &device : samples_device_iterator(m_enumerator.config()->root_device())) + for (samples_device &device : samples_device_enumerator(m_enumerator.config()->root_device())) { // by default we just search using the driver name std::string searchpath(m_enumerator.driver().name); @@ -220,16 +455,18 @@ media_auditor::summary media_auditor::audit_samples() // look for the files emu_file file(m_enumerator.options().sample_path(), OPEN_FLAG_READ | OPEN_FLAG_NO_PRELOAD); - path_iterator path(searchpath.c_str()); + path_iterator path(searchpath); std::string curpath; - while (path.next(curpath, samplename)) + while (path.next(curpath)) { + util::path_append(curpath, samplename); + // attempt to access the file (.flac) or (.wav) - osd_file::error filerr = file.open(curpath.c_str(), ".flac"); - if (filerr != osd_file::error::NONE) - filerr = file.open(curpath.c_str(), ".wav"); + std::error_condition filerr = file.open(curpath + ".flac"); + if (filerr) + filerr = file.open(curpath + ".wav"); - if (filerr == osd_file::error::NONE) + if (!filerr) { record.set_status(audit_status::GOOD, audit_substatus::GOOD); found++; @@ -313,7 +550,7 @@ media_auditor::summary media_auditor::summarize(const char *name, std::ostream * case audit_substatus::NOT_FOUND: if (output) { - device_t *const shared_device = record.shared_device(); + std::add_pointer_t<device_type> const shared_device = record.shared_device(); if (shared_device) util::stream_format(*output, "NOT FOUND (%s)\n", shared_device->shortname()); else @@ -346,25 +583,22 @@ media_auditor::summary media_auditor::summarize(const char *name, std::ostream * // audit_regions - validate/count for regions //------------------------------------------------- -void media_auditor::audit_regions(const rom_entry *region, const char *locationtag, std::size_t &found, std::size_t &required) +template <typename T> +void media_auditor::audit_regions(T do_audit, const rom_entry *region, std::size_t &found, std::size_t &required) { // now iterate over regions + std::vector<std::string> searchpath; for ( ; region; region = rom_next_region(region)) { // now iterate over rom definitions - for (const rom_entry *rom = rom_first_file(region); rom; rom = rom_next_file(rom)) + for (rom_entry const *rom = rom_first_file(region); rom; rom = rom_next_file(rom)) { - util::hash_collection const hashes(ROM_GETHASHDATA(rom)); - // count the number of files with hashes + util::hash_collection const hashes(rom->hashdata()); if (!hashes.flag(util::hash_collection::FLAG_NO_DUMP) && !ROM_ISOPTIONAL(rom)) required++; - audit_record const *record = nullptr; - if (ROMREGION_ISROMDATA(region)) - record = &audit_one_rom(rom); - else if (ROMREGION_ISDISKDATA(region)) - record = &audit_one_disk(rom, locationtag); + audit_record const *const record = do_audit(region, rom); // count the number of files that are found. if (record && ((record->status() == audit_status::GOOD) || (record->status() == audit_status::FOUND_INVALID))) @@ -378,7 +612,7 @@ void media_auditor::audit_regions(const rom_entry *region, const char *locationt // audit_one_rom - validate a single ROM entry //------------------------------------------------- -media_auditor::audit_record &media_auditor::audit_one_rom(const rom_entry *rom) +media_auditor::audit_record &media_auditor::audit_one_rom(const std::vector<std::string> &searchpath, const rom_entry *rom) { // allocate and append a new record audit_record &record = *m_record_list.emplace(m_record_list.end(), *rom, media_type::ROM); @@ -388,26 +622,19 @@ media_auditor::audit_record &media_auditor::audit_one_rom(const rom_entry *rom) bool const has_crc = record.expected_hashes().crc(crc); // find the file and checksum it, getting the file length along the way - emu_file file(m_enumerator.options().media_path(), OPEN_FLAG_READ | OPEN_FLAG_NO_PRELOAD); - file.set_restrict_to_mediapath(true); - path_iterator path(m_searchpath); - std::string curpath; - while (path.next(curpath, record.name())) - { - // open the file if we can - osd_file::error filerr; - if (has_crc) - filerr = file.open(curpath.c_str(), crc); - else - filerr = file.open(curpath.c_str()); + emu_file file(m_enumerator.options().media_path(), searchpath, OPEN_FLAG_READ | OPEN_FLAG_NO_PRELOAD); + file.set_restrict_to_mediapath(1); - // if it worked, get the actual length and hashes, then stop - if (filerr == osd_file::error::NONE) - { - record.set_actual(file.hashes(m_validation), file.size()); - break; - } - } + // open the file if we can + std::error_condition filerr; + if (has_crc) + filerr = file.open(record.name(), crc); + else + filerr = file.open(record.name()); + + // if it worked, get the actual length and hashes, then stop + if (!filerr) + record.set_actual(file.hashes(m_validation), file.size()); // compute the final status compute_status(record, rom, record.actual_length() != 0); @@ -419,17 +646,21 @@ media_auditor::audit_record &media_auditor::audit_one_rom(const rom_entry *rom) // audit_one_disk - validate a single disk entry //------------------------------------------------- -media_auditor::audit_record &media_auditor::audit_one_disk(const rom_entry *rom, const char *locationtag) +template <typename... T> +media_auditor::audit_record &media_auditor::audit_one_disk(const rom_entry *rom, T &&... args) { // allocate and append a new record audit_record &record = *m_record_list.emplace(m_record_list.end(), *rom, media_type::DISK); // open the disk chd_file source; - chd_error err = chd_error(open_disk_image(m_enumerator.options(), &m_enumerator.driver(), rom, source, locationtag)); + const std::error_condition err = rom_load_manager::open_disk_image(m_enumerator.options(), std::forward<T>(args)..., rom, source); + + // FIXME: A CHD with an invalid header or missing parent is treated as not found. + // We need a way to report more detailed errors for bad disk images. // if we succeeded, get the hashes - if (err == CHDERR_NONE) + if (!err) { util::hash_collection hashes; @@ -442,7 +673,7 @@ media_auditor::audit_record &media_auditor::audit_one_disk(const rom_entry *rom, } // compute the final status - compute_status(record, rom, err == CHDERR_NONE); + compute_status(record, rom, !err); return record; } @@ -481,59 +712,6 @@ void media_auditor::compute_status(audit_record &record, const rom_entry *rom, b //------------------------------------------------- -// find_shared_device - return the source that -// shares a media entry with the same hashes -//------------------------------------------------- - -device_t *media_auditor::find_shared_device(device_t &device, const char *name, const util::hash_collection &romhashes, uint64_t romlength) -{ - bool const dumped = !romhashes.flag(util::hash_collection::FLAG_NO_DUMP); - - // special case for non-root devices - device_t *highest_device = nullptr; - if (device.owner()) - { - for (const rom_entry *region = rom_first_region(device); region; region = rom_next_region(region)) - { - for (const rom_entry *rom = rom_first_file(region); rom; rom = rom_next_file(rom)) - { - if (ROM_GETLENGTH(rom) == romlength) - { - util::hash_collection hashes(ROM_GETHASHDATA(rom)); - if ((dumped && hashes == romhashes) || (!dumped && ROM_GETNAME(rom) == name)) - highest_device = &device; - } - } - } - } - else - { - // iterate up the parent chain - for (auto drvindex = m_enumerator.find(m_enumerator.driver().parent); drvindex >= 0; drvindex = m_enumerator.find(m_enumerator.driver(drvindex).parent)) - { - for (device_t &scandevice : device_iterator(m_enumerator.config(drvindex)->root_device())) - { - for (const rom_entry *region = rom_first_region(scandevice); region; region = rom_next_region(region)) - { - for (const rom_entry *rom = rom_first_file(region); rom; rom = rom_next_file(rom)) - { - if (ROM_GETLENGTH(rom) == romlength) - { - util::hash_collection hashes(ROM_GETHASHDATA(rom)); - if ((dumped && hashes == romhashes) || (!dumped && ROM_GETNAME(rom) == name)) - highest_device = &scandevice; - } - } - } - } - } - } - - return highest_device; -} - - -//------------------------------------------------- // audit_record - constructor //------------------------------------------------- @@ -541,14 +719,13 @@ media_auditor::audit_record::audit_record(const rom_entry &media, media_type typ : m_type(type) , m_status(audit_status::UNVERIFIED) , m_substatus(audit_substatus::UNVERIFIED) - , m_name(ROM_GETNAME(&media)) + , m_name(media.name()) , m_explength(rom_file_size(&media)) , m_length(0) - , m_exphashes() + , m_exphashes(media.hashdata()) , m_hashes() , m_shared_device(nullptr) { - m_exphashes.from_internal_string(ROM_GETHASHDATA(&media)); } media_auditor::audit_record::audit_record(const char *name, media_type type) diff --git a/src/frontend/mame/audit.h b/src/frontend/mame/audit.h index be6dccf297c..10a32926613 100644 --- a/src/frontend/mame/audit.h +++ b/src/frontend/mame/audit.h @@ -7,13 +7,10 @@ ROM, disk, and sample auditing functions. ***************************************************************************/ - -#pragma once - #ifndef MAME_FRONTEND_AUDIT_H #define MAME_FRONTEND_AUDIT_H -#include "hash.h" +#pragma once #include <iosfwd> #include <list> @@ -38,6 +35,7 @@ // forward declarations class driver_enumerator; +class software_list_device; @@ -104,12 +102,12 @@ public: media_type type() const { return m_type; } audit_status status() const { return m_status; } audit_substatus substatus() const { return m_substatus; } - const char *name() const { return m_name; } + const std::string &name() const { return m_name; } uint64_t expected_length() const { return m_explength; } uint64_t actual_length() const { return m_length; } const util::hash_collection &expected_hashes() const { return m_exphashes; } const util::hash_collection &actual_hashes() const { return m_hashes; } - device_t *shared_device() const { return m_shared_device; } + std::add_pointer_t<device_type> shared_device() const { return m_shared_device; } // setters void set_status(audit_status status, audit_substatus substatus) @@ -130,22 +128,22 @@ public: m_length = length; } - void set_shared_device(device_t *shared_device) + void set_shared_device(std::add_pointer_t<device_type> shared_device) { m_shared_device = shared_device; } private: // internal state - media_type m_type; // type of item that was audited - audit_status m_status; // status of audit on this item - audit_substatus m_substatus; // finer-detail status - const char * m_name; // name of item - uint64_t m_explength; // expected length of item - uint64_t m_length; // actual length of item - util::hash_collection m_exphashes; // expected hash data - util::hash_collection m_hashes; // actual hash information - device_t * m_shared_device; // device that shares the rom + media_type m_type; // type of item that was audited + audit_status m_status; // status of audit on this item + audit_substatus m_substatus; // finer-detail status + std::string m_name; // name of item + uint64_t m_explength; // expected length of item + uint64_t m_length; // actual length of item + util::hash_collection m_exphashes; // expected hash data + util::hash_collection m_hashes; // actual hash information + std::add_pointer_t<device_type> m_shared_device; // device that shares the ROM }; using record_list = std::list<audit_record>; @@ -158,23 +156,21 @@ public: // audit operations summary audit_media(const char *validation = AUDIT_VALIDATE_FULL); summary audit_device(device_t &device, const char *validation = AUDIT_VALIDATE_FULL); - summary audit_software(const std::string &list_name, const software_info *swinfo, const char *validation = AUDIT_VALIDATE_FULL); + summary audit_software(software_list_device &swlist, const software_info &swinfo, const char *validation = AUDIT_VALIDATE_FULL); summary audit_samples(); summary summarize(const char *name, std::ostream *output = nullptr) const; private: // internal helpers - void audit_regions(const rom_entry *region, const char *locationtag, std::size_t &found, std::size_t &required); - audit_record &audit_one_rom(const rom_entry *rom); - audit_record &audit_one_disk(const rom_entry *rom, const char *locationtag); + template <typename T> void audit_regions(T do_audit, const rom_entry *region, std::size_t &found, std::size_t &required); + audit_record &audit_one_rom(const std::vector<std::string> &searchpath, const rom_entry *rom); + template <typename... T> audit_record &audit_one_disk(const rom_entry *rom, T &&... args); void compute_status(audit_record &record, const rom_entry *rom, bool found); - device_t *find_shared_device(device_t &device, const char *name, const util::hash_collection &romhashes, uint64_t romlength); // internal state record_list m_record_list; const driver_enumerator & m_enumerator; const char * m_validation; - const char * m_searchpath; }; diff --git a/src/frontend/mame/cheat.cpp b/src/frontend/mame/cheat.cpp index 815c788e241..e0d890ebda6 100644 --- a/src/frontend/mame/cheat.cpp +++ b/src/frontend/mame/cheat.cpp @@ -79,15 +79,15 @@ #include "ui/ui.h" #include "ui/menu.h" -#include "debugger.h" +#include "corestr.h" #include "emuopts.h" -#include "debug/debugcpu.h" +#include "fileio.h" #include <cstring> #include <iterator> #include <utility> -#include <ctype.h> +#include <cctype> @@ -138,7 +138,7 @@ inline std::string number_and_format::format() const // cheat_parameter - constructor //------------------------------------------------- -cheat_parameter::cheat_parameter(cheat_manager &manager, symbol_table &symbols, const char *filename, util::xml::data_node const ¶mnode) +cheat_parameter::cheat_parameter(cheat_manager &manager, symbol_table &symbols, std::string const &filename, util::xml::data_node const ¶mnode) : m_minval(number_and_format(paramnode.get_attribute_int("min", 0), paramnode.get_attribute_int_format("min"))) , m_maxval(number_and_format(paramnode.get_attribute_int("max", 0), paramnode.get_attribute_int_format("max"))) , m_stepval(number_and_format(paramnode.get_attribute_int("step", 1), paramnode.get_attribute_int_format("step"))) @@ -153,7 +153,7 @@ cheat_parameter::cheat_parameter(cheat_manager &manager, symbol_table &symbols, if (!itemnode->get_value() || !itemnode->get_value()[0]) throw emu_fatalerror("%s.xml(%d): item is missing text\n", filename, itemnode->line); - // check for non-existant value + // check for non-existent value if (!itemnode->has_attribute("value")) throw emu_fatalerror("%s.xml(%d): item is value\n", filename, itemnode->line); @@ -162,7 +162,7 @@ cheat_parameter::cheat_parameter(cheat_manager &manager, symbol_table &symbols, util::xml::data_node::int_format const format(itemnode->get_attribute_int_format("value")); // allocate and append a new item - item &curitem(*m_itemlist.emplace(m_itemlist.end(), itemnode->get_value(), value, format)); + item &curitem(m_itemlist.emplace_back(itemnode->get_value(), value, format)); // ensure the maximum expands to suit m_maxval = std::max(m_maxval, curitem.value()); @@ -205,7 +205,7 @@ const char *cheat_parameter::text() // save - save a single cheat parameter //------------------------------------------------- -void cheat_parameter::save(emu_file &cheatfile) const +void cheat_parameter::save(util::core_file &cheatfile) const { // output the parameter tag cheatfile.printf("\t\t<parameter"); @@ -214,11 +214,11 @@ void cheat_parameter::save(emu_file &cheatfile) const { // if no items, just output min/max/step if (m_minval != 0) - cheatfile.printf(" min=\"%s\"", m_minval.format().c_str()); + cheatfile.printf(" min=\"%s\"", m_minval.format()); if (m_maxval != 0) - cheatfile.printf(" max=\"%s\"", m_maxval.format().c_str()); + cheatfile.printf(" max=\"%s\"", m_maxval.format()); if (m_stepval != 1) - cheatfile.printf(" step=\"%s\"", m_stepval.format().c_str()); + cheatfile.printf(" step=\"%s\"", m_stepval.format()); cheatfile.printf("/>\n"); } else @@ -226,7 +226,7 @@ void cheat_parameter::save(emu_file &cheatfile) const // iterate over items cheatfile.printf(">\n"); for (item const &curitem : m_itemlist) - cheatfile.printf("\t\t\t<item value=\"%s\">%s</item>\n", curitem.value().format().c_str(), curitem.text()); + cheatfile.printf("\t\t\t<item value=\"%s\">%s</item>\n", curitem.value().format(), curitem.text()); cheatfile.printf("\t\t</parameter>\n"); } } @@ -310,6 +310,9 @@ bool cheat_parameter::set_next_state() // CHEAT SCRIPT //************************************************************************** +constexpr int cheat_script::script_entry::MAX_ARGUMENTS; + + //------------------------------------------------- // cheat_script - constructor //------------------------------------------------- @@ -317,7 +320,7 @@ bool cheat_parameter::set_next_state() cheat_script::cheat_script( cheat_manager &manager, symbol_table &symbols, - char const *filename, + std::string const &filename, util::xml::data_node const &scriptnode) : m_state(SCRIPT_STATE_RUN) { @@ -365,7 +368,7 @@ void cheat_script::execute(cheat_manager &manager, uint64_t &argindex) // save - save a single cheat script //------------------------------------------------- -void cheat_script::save(emu_file &cheatfile) const +void cheat_script::save(util::core_file &cheatfile) const { // output the script tag cheatfile.printf("\t\t<script"); @@ -395,11 +398,11 @@ void cheat_script::save(emu_file &cheatfile) const cheat_script::script_entry::script_entry( cheat_manager &manager, symbol_table &symbols, - char const *filename, + std::string const &filename, util::xml::data_node const &entrynode, bool isaction) - : m_condition(&symbols) - , m_expression(&symbols) + : m_condition(symbols) + , m_expression(symbols) { char const *expression(nullptr); try @@ -416,6 +419,10 @@ cheat_script::script_entry::script_entry( if (!expression || !expression[0]) throw emu_fatalerror("%s.xml(%d): missing expression in action tag\n", filename, entrynode.line); m_expression.parse(expression); + + // initialise these to defautlt values + m_line = 0; + m_justify = ui::text_layout::text_justify::LEFT; } else { @@ -429,12 +436,12 @@ cheat_script::script_entry::script_entry( // extract other attributes m_line = entrynode.get_attribute_int("line", 0); - m_justify = ui::text_layout::LEFT; + m_justify = ui::text_layout::text_justify::LEFT; char const *const align(entrynode.get_attribute_string("align", "left")); if (!std::strcmp(align, "center")) - m_justify = ui::text_layout::CENTER; + m_justify = ui::text_layout::text_justify::CENTER; else if (!std::strcmp(align, "right")) - m_justify = ui::text_layout::RIGHT; + m_justify = ui::text_layout::text_justify::RIGHT; else if (std::strcmp(align, "left")) throw emu_fatalerror("%s.xml(%d): invalid alignment '%s' specified\n", filename, entrynode.line, align); @@ -525,29 +532,29 @@ void cheat_script::script_entry::execute(cheat_manager &manager, uint64_t &argin // save - save a single action or output //------------------------------------------------- -void cheat_script::script_entry::save(emu_file &cheatfile) const +void cheat_script::script_entry::save(util::core_file &cheatfile) const { if (m_format.empty()) { // output an action cheatfile.printf("\t\t\t<action"); if (!m_condition.is_empty()) - cheatfile.printf(" condition=\"%s\"", cheat_manager::quote_expression(m_condition).c_str()); - cheatfile.printf(">%s</action>\n", cheat_manager::quote_expression(m_expression).c_str()); + cheatfile.printf(" condition=\"%s\"", cheat_manager::quote_expression(m_condition)); + cheatfile.printf(">%s</action>\n", cheat_manager::quote_expression(m_expression)); } else { // output an output - cheatfile.printf("\t\t\t<output format=\"%s\"", m_format.c_str()); + cheatfile.printf("\t\t\t<output format=\"%s\"", m_format); if (!m_condition.is_empty()) - cheatfile.printf(" condition=\"%s\"", cheat_manager::quote_expression(m_condition).c_str()); + cheatfile.printf(" condition=\"%s\"", cheat_manager::quote_expression(m_condition)); if (m_line != 0) cheatfile.printf(" line=\"%d\"", m_line); - if (m_justify == ui::text_layout::CENTER) + if (m_justify == ui::text_layout::text_justify::CENTER) cheatfile.printf(" align=\"center\""); - else if (m_justify == ui::text_layout::RIGHT) + else if (m_justify == ui::text_layout::text_justify::RIGHT) cheatfile.printf(" align=\"right\""); if (m_arglist.size() == 0) @@ -571,7 +578,7 @@ void cheat_script::script_entry::save(emu_file &cheatfile) const // has the correct number and type of arguments //------------------------------------------------- -void cheat_script::script_entry::validate_format(const char *filename, int line) +void cheat_script::script_entry::validate_format(std::string const &filename, int line) { // first count arguments int argsprovided(0); @@ -587,14 +594,14 @@ void cheat_script::script_entry::validate_format(const char *filename, int line) // look for a valid type if (!strchr("cdiouxX", *p)) - throw emu_fatalerror("%s.xml(%d): invalid format specification \"%s\"\n", filename, line, m_format.c_str()); + throw emu_fatalerror("%s.xml(%d): invalid format specification \"%s\"\n", filename, line, m_format); } // did we match? if (argscounted < argsprovided) - throw emu_fatalerror("%s.xml(%d): too many arguments provided (%d) for format \"%s\"\n", filename, line, argsprovided, m_format.c_str()); + throw emu_fatalerror("%s.xml(%d): too many arguments provided (%d) for format \"%s\"\n", filename, line, argsprovided, m_format); if (argscounted > argsprovided) - throw emu_fatalerror("%s.xml(%d): not enough arguments provided (%d) for format \"%s\"\n", filename, line, argsprovided, m_format.c_str()); + throw emu_fatalerror("%s.xml(%d): not enough arguments provided (%d) for format \"%s\"\n", filename, line, argsprovided, m_format); } @@ -605,9 +612,9 @@ void cheat_script::script_entry::validate_format(const char *filename, int line) cheat_script::script_entry::output_argument::output_argument( cheat_manager &manager, symbol_table &symbols, - char const *filename, + std::string const &filename, util::xml::data_node const &argnode) - : m_expression(&symbols) + : m_expression(symbols) , m_count(0) { // first extract attributes @@ -656,12 +663,12 @@ int cheat_script::script_entry::output_argument::values(uint64_t &argindex, uint // save - save a single output argument //------------------------------------------------- -void cheat_script::script_entry::output_argument::save(emu_file &cheatfile) const +void cheat_script::script_entry::output_argument::save(util::core_file &cheatfile) const { cheatfile.printf("\t\t\t\t<argument"); if (m_count != 1) cheatfile.printf(" count=\"%d\"", int(m_count)); - cheatfile.printf(">%s</argument>\n", cheat_manager::quote_expression(m_expression).c_str()); + cheatfile.printf(">%s</argument>\n", cheat_manager::quote_expression(m_expression)); } @@ -674,9 +681,9 @@ void cheat_script::script_entry::output_argument::save(emu_file &cheatfile) cons // cheat_entry - constructor //------------------------------------------------- -cheat_entry::cheat_entry(cheat_manager &manager, symbol_table &globaltable, const char *filename, util::xml::data_node const &cheatnode) +cheat_entry::cheat_entry(cheat_manager &manager, symbol_table &globaltable, std::string const &filename, util::xml::data_node const &cheatnode) : m_manager(manager) - , m_symbols(&manager.machine(), &globaltable) + , m_symbols(manager.machine(), &globaltable) , m_state(SCRIPT_STATE_OFF) , m_numtemp(DEFAULT_TEMP_VARIABLES) , m_argindex(0) @@ -756,13 +763,13 @@ cheat_entry::~cheat_entry() // save - save a single cheat entry //------------------------------------------------- -void cheat_entry::save(emu_file &cheatfile) const +void cheat_entry::save(util::core_file &cheatfile) const { // determine if we have scripts bool const has_scripts(m_off_script || m_on_script || m_run_script || m_change_script); // output the cheat tag - cheatfile.printf("\t<cheat desc=\"%s\"", m_description.c_str()); + cheatfile.printf("\t<cheat desc=\"%s\"", m_description); if (m_numtemp != DEFAULT_TEMP_VARIABLES) cheatfile.printf(" tempvariables=\"%d\"", m_numtemp); @@ -776,7 +783,7 @@ void cheat_entry::save(emu_file &cheatfile) const // save the comment if (!m_comment.empty()) - cheatfile.printf("\t\t<comment><![CDATA[\n%s\n\t\t]]></comment>\n", m_comment.c_str()); + cheatfile.printf("\t\t<comment><![CDATA[\n%s\n\t\t]]></comment>\n", m_comment); // output the parameter, if present if (m_parameter) m_parameter->save(cheatfile); @@ -810,14 +817,14 @@ bool cheat_entry::activate() // if we're a oneshot cheat, execute the "on" script and indicate change execute_on_script(); changed = true; - m_manager.machine().popmessage("Activated %s", m_description.c_str()); + m_manager.machine().popmessage("Activated %s", m_description); } else if (is_oneshot_parameter() && (m_state != SCRIPT_STATE_OFF)) { // if we're a oneshot parameter cheat and we're active, execute the "state change" script and indicate change execute_change_script(); changed = true; - m_manager.machine().popmessage("Activated\n %s = %s", m_description.c_str(), m_parameter->text()); + m_manager.machine().popmessage("Activated\n %s = %s", m_description, m_parameter->text()); } return changed; @@ -950,7 +957,7 @@ void cheat_entry::menu_text(std::string &description, std::string &state, uint32 // some cheat entries are just text for display if (!description.empty()) { - strtrimspace(description); + description = strtrimspace(description); if (description.empty()) description = MENU_SEPARATOR_ITEM; } @@ -1054,8 +1061,11 @@ constexpr int cheat_manager::CHEAT_VERSION; cheat_manager::cheat_manager(running_machine &machine) : m_machine(machine) + , m_framecount(0) + , m_numlines(0) + , m_lastline(0) , m_disabled(true) - , m_symtable(&machine) + , m_symtable(machine) { // if the cheat engine is disabled, we're done if (!machine.options().cheat()) @@ -1078,19 +1088,6 @@ cheat_manager::cheat_manager(running_machine &machine) m_symtable.add("frombcd", 1, 1, execute_frombcd); m_symtable.add("tobcd", 1, 1, execute_tobcd); - // we rely on the debugger expression callbacks; if the debugger isn't - // enabled, we must jumpstart them manually - if ((machine.debug_flags & DEBUG_FLAG_ENABLED) == 0) - { - m_cpu = std::make_unique<debugger_cpu>(machine); - m_cpu->configure_memory(m_symtable); - } - else - { - // configure for memory access (shared with debugger) - machine.debugger().cpu().configure_memory(m_symtable); - } - // load the cheats reload(); } @@ -1101,7 +1098,7 @@ cheat_manager::cheat_manager(running_machine &machine) // cheat engine //------------------------------------------------- -void cheat_manager::set_enable(bool enable) +void cheat_manager::set_enable(bool enable, bool show) { // if the cheat engine is disabled, we're done if (!machine().options().cheat()) @@ -1117,7 +1114,8 @@ void cheat_manager::set_enable(bool enable) if (cheat->state() == SCRIPT_STATE_RUN) cheat->execute_off_script(); } - machine().popmessage("Cheats Disabled"); + if (show) + machine().popmessage("Cheats Disabled"); m_disabled = true; } else if (m_disabled && enable) @@ -1131,7 +1129,8 @@ void cheat_manager::set_enable(bool enable) if (cheat->state() == SCRIPT_STATE_RUN) cheat->execute_on_script(); } - machine().popmessage("Cheats Enabled"); + if (show) + machine().popmessage("Cheats Enabled"); } } @@ -1158,7 +1157,7 @@ void cheat_manager::reload() // load the cheat file, if it's a system that has a software list then try softlist_name/shortname.xml first, // if it fails to load then try to load via crc32 - basename/crc32.xml ( eg. 01234567.xml ) - for (device_image_interface &image : image_interface_iterator(machine().root_device())) + for (device_image_interface &image : image_interface_enumerator(machine().root_device())) { if (image.exists()) { @@ -1167,7 +1166,7 @@ void cheat_manager::reload() // have the same shortname if (image.loaded_through_softlist()) { - load_cheats(string_format("%s%s%s", image.software_list_name(), PATH_SEPARATOR, image.basename()).c_str()); + load_cheats(string_format("%s" PATH_SEPARATOR "%s", image.software_list_name(), image.basename())); break; } // else we are loading outside the software list, try to load machine_basename/crc32.xml @@ -1176,7 +1175,7 @@ void cheat_manager::reload() uint32_t crc = image.crc(); if (crc != 0) { - load_cheats(string_format("%s%s%08X", machine().basename(), PATH_SEPARATOR, crc).c_str()); + load_cheats(string_format("%s" PATH_SEPARATOR "%08X", machine().basename(), crc)); break; } } @@ -1198,14 +1197,14 @@ void cheat_manager::reload() // memory to the given filename //------------------------------------------------- -bool cheat_manager::save_all(const char *filename) +bool cheat_manager::save_all(std::string const &filename) { // open the file with the proper name emu_file cheatfile(machine().options().cheat_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); - osd_file::error const filerr(cheatfile.open(filename, ".xml")); + std::error_condition const filerr(cheatfile.open(filename + ".xml")); // if that failed, return nothing - if (filerr != osd_file::error::NONE) + if (filerr) return false; // wrap the rest of catch errors @@ -1227,7 +1226,7 @@ bool cheat_manager::save_all(const char *filename) catch (emu_fatalerror const &err) { // catch errors and cleanup - osd_printf_error("%s\n", err.string()); + osd_printf_error("%s\n", err.what()); cheatfile.remove_on_close(); } return false; @@ -1247,10 +1246,13 @@ void cheat_manager::render_text(mame_ui_manager &mui, render_container &containe if (!m_output[linenum].empty()) { // output the text - mui.draw_text_full(container, m_output[linenum].c_str(), + mui.draw_text_full( + container, + m_output[linenum], 0.0f, float(linenum) * mui.get_line_height(), 1.0f, - m_justify[linenum], ui::text_layout::NEVER, mame_ui_manager::OPAQUE_, - rgb_t::white(), rgb_t::black(), nullptr, nullptr); + m_justify[linenum], ui::text_layout::word_wrapping::NEVER, + mame_ui_manager::OPAQUE_, rgb_t::white(), rgb_t::black(), + nullptr, nullptr); } } } @@ -1275,7 +1277,8 @@ std::string &cheat_manager::get_output_string(int row, ui::text_layout::text_jus row = (row < 0) ? (m_numlines + row) : (row - 1); // clamp within range - row = std::min(std::max(row, 0), m_numlines - 1); + assert(m_numlines > 0); + row = std::clamp(row, 0, m_numlines - 1); // return the appropriate string m_justify[row] = justify; @@ -1303,6 +1306,11 @@ std::string cheat_manager::quote_expression(const parsed_expression &expression) strreplace(str, "& ", " band "); strreplace(str, "&", " band "); + strreplace(str, " << ", " lshift "); + strreplace(str, " <<", " lshift "); + strreplace(str, "<< ", " lshift "); + strreplace(str, "<<", " lshift "); + strreplace(str, " <= ", " le "); strreplace(str, " <=", " le "); strreplace(str, "<= ", " le "); @@ -1313,11 +1321,6 @@ std::string cheat_manager::quote_expression(const parsed_expression &expression) strreplace(str, "< ", " lt "); strreplace(str, "<", " lt "); - strreplace(str, " << ", " lshift "); - strreplace(str, " <<", " lshift "); - strreplace(str, "<< ", " lshift "); - strreplace(str, "<<", " lshift "); - return str; } @@ -1326,7 +1329,7 @@ std::string cheat_manager::quote_expression(const parsed_expression &expression) // execute_frombcd - convert a value from BCD //------------------------------------------------- -uint64_t cheat_manager::execute_frombcd(symbol_table &table, int params, const uint64_t *param) +uint64_t cheat_manager::execute_frombcd(int params, const uint64_t *param) { uint64_t value(param[0]); uint64_t multiplier(1); @@ -1346,7 +1349,7 @@ uint64_t cheat_manager::execute_frombcd(symbol_table &table, int params, const u // execute_tobcd - convert a value to BCD //------------------------------------------------- -uint64_t cheat_manager::execute_tobcd(symbol_table &table, int params, const uint64_t *param) +uint64_t cheat_manager::execute_tobcd(int params, const uint64_t *param) { uint64_t value(param[0]); uint64_t result(0); @@ -1389,19 +1392,19 @@ void cheat_manager::frame_update() // and create the cheat entry list //------------------------------------------------- -void cheat_manager::load_cheats(const char *filename) +void cheat_manager::load_cheats(std::string const &filename) { std::string searchstr(machine().options().cheat_path()); std::string curpath; for (path_iterator path(searchstr); path.next(curpath); ) { - searchstr.append(";").append(curpath).append(PATH_SEPARATOR).append("cheat"); + searchstr.append(";").append(curpath).append(PATH_SEPARATOR "cheat"); } emu_file cheatfile(std::move(searchstr), OPEN_FLAG_READ); try { - // loop over all instrances of the files found in our search paths - for (osd_file::error filerr = cheatfile.open(filename, ".xml"); filerr == osd_file::error::NONE; filerr = cheatfile.open_next()) + // loop over all instances of the files found in our search paths + for (std::error_condition filerr = cheatfile.open(filename + ".xml"); !filerr; filerr = cheatfile.open_next()) { osd_printf_verbose("Loading cheats file from %s\n", cheatfile.fullpath()); @@ -1447,7 +1450,7 @@ void cheat_manager::load_cheats(const char *filename) catch (emu_fatalerror const &err) { // just move on to the next cheat - osd_printf_error("%s\n", err.string()); + osd_printf_error("%s\n", err.what()); } } } @@ -1455,7 +1458,7 @@ void cheat_manager::load_cheats(const char *filename) catch (emu_fatalerror const &err) { // handle errors cleanly - osd_printf_error("%s\n", err.string()); + osd_printf_error("%s\n", err.what()); m_cheatlist.clear(); } } diff --git a/src/frontend/mame/cheat.h b/src/frontend/mame/cheat.h index d05890c7152..04f8006ae97 100644 --- a/src/frontend/mame/cheat.h +++ b/src/frontend/mame/cheat.h @@ -14,7 +14,6 @@ #pragma once #include "debug/express.h" -#include "debug/debugcpu.h" #include "ui/text.h" #include "xmlfile.h" @@ -88,7 +87,7 @@ public: cheat_parameter( cheat_manager &manager, symbol_table &symbols, - char const *filename, + std::string const &filename, util::xml::data_node const ¶mnode); // queries @@ -103,7 +102,7 @@ public: bool set_next_state(); // actions - void save(emu_file &cheatfile) const; + void save(util::core_file &cheatfile) const; private: // a single item in a parameter item list @@ -153,7 +152,7 @@ public: cheat_script( cheat_manager &manager, symbol_table &symbols, - char const *filename, + std::string const &filename, util::xml::data_node const &scriptnode); // getters @@ -161,7 +160,7 @@ public: // actions void execute(cheat_manager &manager, uint64_t &argindex); - void save(emu_file &cheatfile) const; + void save(util::core_file &cheatfile) const; private: // an entry within the script @@ -172,13 +171,13 @@ private: script_entry( cheat_manager &manager, symbol_table &symbols, - char const *filename, + std::string const &filename, util::xml::data_node const &entrynode, bool isaction); // actions void execute(cheat_manager &manager, uint64_t &argindex); - void save(emu_file &cheatfile) const; + void save(util::core_file &cheatfile) const; private: // an argument for output @@ -189,7 +188,7 @@ private: output_argument( cheat_manager &manager, symbol_table &symbols, - char const *filename, + std::string const &filename, util::xml::data_node const &argnode); // getters @@ -197,7 +196,7 @@ private: int values(uint64_t &argindex, uint64_t *result); // actions - void save(emu_file &cheatfile) const; + void save(util::core_file &cheatfile) const; private: // internal state @@ -206,7 +205,7 @@ private: }; // internal helpers - void validate_format(char const *filename, int line); + void validate_format(std::string const &filename, int line); // internal state parsed_expression m_condition; // condition under which this is executed @@ -233,7 +232,7 @@ class cheat_entry { public: // construction/destruction - cheat_entry(cheat_manager &manager, symbol_table &globaltable, const char *filename, util::xml::data_node const &cheatnode); + cheat_entry(cheat_manager &manager, symbol_table &globaltable, std::string const &filename, util::xml::data_node const &cheatnode); ~cheat_entry(); // getters @@ -268,7 +267,7 @@ public: bool select_default_state(); bool select_previous_state(); bool select_next_state(); - void save(emu_file &cheatfile) const; + void save(util::core_file &cheatfile) const; // UI helpers void menu_text(std::string &description, std::string &state, uint32_t &flags); @@ -315,11 +314,11 @@ public: std::vector<std::unique_ptr<cheat_entry>> const &entries() const { return m_cheatlist; } // setters - void set_enable(bool enable); + void set_enable(bool enable, bool show); // actions void reload(); - bool save_all(const char *filename); + bool save_all(std::string const &filename); void render_text(mame_ui_manager &mui, render_container &container); // output helpers @@ -327,13 +326,13 @@ public: // global helpers static std::string quote_expression(parsed_expression const &expression); - static uint64_t execute_frombcd(symbol_table &table, int params, uint64_t const *param); - static uint64_t execute_tobcd(symbol_table &table, int params, uint64_t const *param); + static uint64_t execute_frombcd(int params, uint64_t const *param); + static uint64_t execute_tobcd(int params, uint64_t const *param); private: // internal helpers void frame_update(); - void load_cheats(char const *filename); + void load_cheats(std::string const &filename); // internal state running_machine & m_machine; // reference to our machine @@ -345,7 +344,6 @@ private: int8_t m_lastline; // last line used for output bool m_disabled; // true if the cheat engine is disabled symbol_table m_symtable; // global symbol table - std::unique_ptr<debugger_cpu> m_cpu; // debugger interface for cpus/memory // constants static constexpr int CHEAT_VERSION = 1; diff --git a/src/frontend/mame/clifront.cpp b/src/frontend/mame/clifront.cpp index d8c1420e656..da9900ff680 100644 --- a/src/frontend/mame/clifront.cpp +++ b/src/frontend/mame/clifront.cpp @@ -9,31 +9,40 @@ ***************************************************************************/ #include "emu.h" +#include "clifront.h" + +#include "ui/moptions.h" + +#include "audit.h" +#include "infoxml.h" +#include "language.h" #include "luaengine.h" #include "mame.h" -#include "chd.h" -#include "emuopts.h" #include "mameopts.h" -#include "audit.h" -#include "info.h" +#include "media_ident.h" +#include "pluginopts.h" + +#include "emuopts.h" +#include "fileio.h" #include "romload.h" -#include "unzip.h" +#include "softlist_dev.h" #include "validity.h" #include "sound/samples.h" -#include "clifront.h" + +#include "chd.h" +#include "corestr.h" +#include "path.h" +#include "unzip.h" #include "xmlfile.h" -#include "media_ident.h" #include "osdepend.h" -#include "softlist_dev.h" - -#include "ui/moptions.h" -#include "language.h" -#include "pluginopts.h" #include <algorithm> #include <new> -#include <ctype.h> +#include <set> +#include <tuple> +#include <cctype> +#include <iostream> //************************************************************************** @@ -57,13 +66,14 @@ #define CLICOMMAND_LISTBROTHERS "listbrothers" #define CLICOMMAND_LISTCRC "listcrc" #define CLICOMMAND_LISTROMS "listroms" +#define CLICOMMAND_LISTBIOS "listbios" #define CLICOMMAND_LISTSAMPLES "listsamples" #define CLICOMMAND_VERIFYROMS "verifyroms" #define CLICOMMAND_VERIFYSAMPLES "verifysamples" #define CLICOMMAND_ROMIDENT "romident" #define CLICOMMAND_LISTDEVICES "listdevices" #define CLICOMMAND_LISTSLOTS "listslots" -#define CLICOMMAND_LISTMEDIA "listmedia" // needed by MESS +#define CLICOMMAND_LISTMEDIA "listmedia" #define CLICOMMAND_LISTSOFTWARE "listsoftware" #define CLICOMMAND_VERIFYSOFTWARE "verifysoftware" #define CLICOMMAND_GETSOFTLIST "getsoftlist" @@ -75,6 +85,7 @@ namespace { + //************************************************************************** // COMMAND-LINE OPTIONS //************************************************************************** @@ -82,40 +93,41 @@ namespace { const options_entry cli_option_entries[] = { /* core commands */ - { nullptr, nullptr, OPTION_HEADER, "CORE COMMANDS" }, - { CLICOMMAND_HELP ";h;?", "0", OPTION_COMMAND, "show help message" }, - { CLICOMMAND_VALIDATE ";valid", "0", OPTION_COMMAND, "perform validation on system drivers and devices" }, + { nullptr, nullptr, core_options::option_type::HEADER, "CORE COMMANDS" }, + { CLICOMMAND_HELP ";h;?", "0", core_options::option_type::COMMAND, "show help message" }, + { CLICOMMAND_VALIDATE ";valid", "0", core_options::option_type::COMMAND, "perform validation on system drivers and devices" }, /* configuration commands */ - { nullptr, nullptr, OPTION_HEADER, "CONFIGURATION COMMANDS" }, - { CLICOMMAND_CREATECONFIG ";cc", "0", OPTION_COMMAND, "create the default configuration file" }, - { CLICOMMAND_SHOWCONFIG ";sc", "0", OPTION_COMMAND, "display running parameters" }, - { CLICOMMAND_SHOWUSAGE ";su", "0", OPTION_COMMAND, "show this help" }, + { nullptr, nullptr, core_options::option_type::HEADER, "CONFIGURATION COMMANDS" }, + { CLICOMMAND_CREATECONFIG ";cc", "0", core_options::option_type::COMMAND, "create the default configuration file" }, + { CLICOMMAND_SHOWCONFIG ";sc", "0", core_options::option_type::COMMAND, "display running parameters" }, + { CLICOMMAND_SHOWUSAGE ";su", "0", core_options::option_type::COMMAND, "show this help" }, /* frontend commands */ - { nullptr, nullptr, OPTION_HEADER, "FRONTEND COMMANDS" }, - { CLICOMMAND_LISTXML ";lx", "0", OPTION_COMMAND, "all available info on driver in XML format" }, - { CLICOMMAND_LISTFULL ";ll", "0", OPTION_COMMAND, "short name, full name" }, - { CLICOMMAND_LISTSOURCE ";ls", "0", OPTION_COMMAND, "driver sourcefile" }, - { CLICOMMAND_LISTCLONES ";lc", "0", OPTION_COMMAND, "show clones" }, - { CLICOMMAND_LISTBROTHERS ";lb", "0", OPTION_COMMAND, "show \"brothers\", or other drivers from same sourcefile" }, - { CLICOMMAND_LISTCRC, "0", OPTION_COMMAND, "CRC-32s" }, - { CLICOMMAND_LISTROMS ";lr", "0", OPTION_COMMAND, "list required ROMs for a driver" }, - { CLICOMMAND_LISTSAMPLES, "0", OPTION_COMMAND, "list optional samples for a driver" }, - { CLICOMMAND_VERIFYROMS, "0", OPTION_COMMAND, "report romsets that have problems" }, - { CLICOMMAND_VERIFYSAMPLES, "0", OPTION_COMMAND, "report samplesets that have problems" }, - { CLICOMMAND_ROMIDENT, "0", OPTION_COMMAND, "compare files with known MAME ROMs" }, - { CLICOMMAND_LISTDEVICES ";ld", "0", OPTION_COMMAND, "list available devices" }, - { CLICOMMAND_LISTSLOTS ";lslot", "0", OPTION_COMMAND, "list available slots and slot devices" }, - { CLICOMMAND_LISTMEDIA ";lm", "0", OPTION_COMMAND, "list available media for the system" }, - { CLICOMMAND_LISTSOFTWARE ";lsoft", "0", OPTION_COMMAND, "list known software for the system" }, - { CLICOMMAND_VERIFYSOFTWARE ";vsoft", "0", OPTION_COMMAND, "verify known software for the system" }, - { CLICOMMAND_GETSOFTLIST ";glist", "0", OPTION_COMMAND, "retrieve software list by name" }, - { CLICOMMAND_VERIFYSOFTLIST ";vlist", "0", OPTION_COMMAND, "verify software list by name" }, - { CLICOMMAND_VERSION, "0", OPTION_COMMAND, "get MAME version" }, - - { nullptr, nullptr, OPTION_HEADER, "FRONTEND COMMAND OPTIONS" }, - { CLIOPTION_DTD, "1", OPTION_BOOLEAN, "include DTD in XML output" }, + { nullptr, nullptr, core_options::option_type::HEADER, "FRONTEND COMMANDS" }, + { CLICOMMAND_LISTXML ";lx", "0", core_options::option_type::COMMAND, "all available info on driver in XML format" }, + { CLICOMMAND_LISTFULL ";ll", "0", core_options::option_type::COMMAND, "short name, full name" }, + { CLICOMMAND_LISTSOURCE ";ls", "0", core_options::option_type::COMMAND, "driver sourcefile" }, + { CLICOMMAND_LISTCLONES ";lc", "0", core_options::option_type::COMMAND, "show clones" }, + { CLICOMMAND_LISTBROTHERS ";lb", "0", core_options::option_type::COMMAND, "show \"brothers\", or other drivers from same sourcefile" }, + { CLICOMMAND_LISTCRC, "0", core_options::option_type::COMMAND, "CRC-32s" }, + { CLICOMMAND_LISTROMS ";lr", "0", core_options::option_type::COMMAND, "list required ROMs for a driver" }, + { CLICOMMAND_LISTSAMPLES, "0", core_options::option_type::COMMAND, "list optional samples for a driver" }, + { CLICOMMAND_VERIFYROMS, "0", core_options::option_type::COMMAND, "report romsets that have problems" }, + { CLICOMMAND_VERIFYSAMPLES, "0", core_options::option_type::COMMAND, "report samplesets that have problems" }, + { CLICOMMAND_ROMIDENT, "0", core_options::option_type::COMMAND, "compare files with known MAME ROMs" }, + { CLICOMMAND_LISTDEVICES ";ld", "0", core_options::option_type::COMMAND, "list devices in a system" }, + { CLICOMMAND_LISTSLOTS ";lslot", "0", core_options::option_type::COMMAND, "list available slots and slot devices" }, + { CLICOMMAND_LISTBIOS, "0", core_options::option_type::COMMAND, "list BIOS options for a system" }, + { CLICOMMAND_LISTMEDIA ";lm", "0", core_options::option_type::COMMAND, "list available media for the system" }, + { CLICOMMAND_LISTSOFTWARE ";lsoft", "0", core_options::option_type::COMMAND, "list known software for the system" }, + { CLICOMMAND_VERIFYSOFTWARE ";vsoft", "0", core_options::option_type::COMMAND, "verify known software for the system" }, + { CLICOMMAND_GETSOFTLIST ";glist", "0", core_options::option_type::COMMAND, "retrieve software list by name" }, + { CLICOMMAND_VERIFYSOFTLIST ";vlist", "0", core_options::option_type::COMMAND, "verify software list by name" }, + { CLICOMMAND_VERSION, "0", core_options::option_type::COMMAND, "get MAME version" }, + + { nullptr, nullptr, core_options::option_type::HEADER, "FRONTEND COMMAND OPTIONS" }, + { CLIOPTION_DTD, "1", core_options::option_type::BOOLEAN, "include DTD in XML output" }, { nullptr } }; @@ -213,25 +225,29 @@ void cli_frontend::start_execution(mame_machine_manager *manager, const std::vec try { m_options.parse_command_line(args, OPTION_PRIORITY_CMDLINE); - m_osd.set_verbose(m_options.verbose()); + } + catch (options_warning_exception &ex) + { + osd_printf_error("%s", ex.message()); } catch (options_exception &ex) { // if we failed, check for no command and a system name first; in that case error on the name if (m_options.command().empty() && mame_options::system(m_options) == nullptr && !m_options.attempted_system_name().empty()) - throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "Unknown system '%s'", m_options.attempted_system_name().c_str()); + throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "Unknown system '%s'", m_options.attempted_system_name()); // otherwise, error on the options - throw emu_fatalerror(EMU_ERR_INVALID_CONFIG, "%s", ex.message().c_str()); + throw emu_fatalerror(EMU_ERR_INVALID_CONFIG, "%s", ex.message()); } + m_osd.set_verbose(m_options.verbose()); // determine the base name of the EXE - std::string exename = core_filename_extract_base(args[0], true); + std::string_view exename = core_filename_extract_base(args[0], true); // if we have a command, execute that if (!m_options.command().empty()) { - execute_commands(exename.c_str()); + execute_commands(exename); return; } @@ -250,15 +266,12 @@ void cli_frontend::start_execution(mame_machine_manager *manager, const std::vec manager->start_luaengine(); if (option_errors.tellp() > 0) - { - std::string option_errors_string = option_errors.str(); - osd_printf_error("Error in command line:\n%s\n", strtrimspace(option_errors_string).c_str()); - } + osd_printf_error("Error in command line:\n%s\n", strtrimspace(option_errors.str())); // if we can't find it, give an appropriate error const game_driver *system = mame_options::system(m_options); if (system == nullptr && *(m_options.system_name()) != 0) - throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "Unknown system '%s'", m_options.system_name()); + throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "Unknown system '%s'", m_options.system_name()); // otherwise just run the game m_result = manager->execute(); @@ -282,22 +295,20 @@ int cli_frontend::execute(std::vector<std::string> &args) // handle exceptions of various types catch (emu_fatalerror &fatal) { - std::string str(fatal.string()); - strtrimspace(str); - osd_printf_error("%s\n", str.c_str()); + osd_printf_error("%s\n", strtrimspace(fatal.what())); m_result = (fatal.exitcode() != 0) ? fatal.exitcode() : EMU_ERR_FATALERROR; // if a game was specified, wasn't a wildcard, and our error indicates this was the // reason for failure, offer some suggestions - if (m_result == EMU_ERR_NO_SUCH_GAME + if (m_result == EMU_ERR_NO_SUCH_SYSTEM && !m_options.attempted_system_name().empty() - && !core_iswildstr(m_options.attempted_system_name().c_str()) + && !core_iswildstr(m_options.attempted_system_name()) && mame_options::system(m_options) == nullptr) { // get the top 16 approximate matches driver_enumerator drivlist(m_options); int matches[16]; - drivlist.find_approximate_matches(m_options.attempted_system_name(), ARRAY_LENGTH(matches), matches); + drivlist.find_approximate_matches(m_options.attempted_system_name(), std::size(matches), matches); // work out how wide the titles need to be int titlelen(0); @@ -307,13 +318,13 @@ int cli_frontend::execute(std::vector<std::string> &args) // print them out osd_printf_error("\n\"%s\" approximately matches the following\n" - "supported machines (best match first):\n\n", m_options.attempted_system_name().c_str()); + "supported machines (best match first):\n\n", m_options.attempted_system_name()); for (int match : matches) { if (0 <= match) { game_driver const &drv(drivlist.driver(match)); - osd_printf_error("%s", util::string_format("%-18s%-*s(%s, %s)\n", drv.name, titlelen + 2, drv.type.fullname(), drv.manufacturer, drv.year).c_str()); + osd_printf_error("%-18s%-*s(%s, %s)\n", drv.name, titlelen + 2, drv.type.fullname(), drv.manufacturer, drv.year); } } } @@ -340,7 +351,7 @@ int cli_frontend::execute(std::vector<std::string> &args) } util::archive_file::cache_clear(); - global_free(manager); + delete manager; return m_result; } @@ -392,7 +403,10 @@ void cli_frontend::listsource(const std::vector<std::string> &args) { auto const list_system_source = [] (device_type type) { - osd_printf_info("%-16s %s\n", type.shortname(), core_filename_extract_base(type.source()).c_str()); + osd_printf_info( + "%-16s %s\n", + type.shortname(), + info_xml_creator::format_sourcefile(type.source())); }; apply_action( args, @@ -431,7 +445,7 @@ void cli_frontend::listclones(const std::vector<std::string> &args) { // see if we match but just weren't a clone if (original_count == 0) - throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching systems found for '%s'", gamename); + throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", gamename); else osd_printf_info("Found %lu match(es) for '%s' but none were clones\n", (unsigned long)drivlist.count(), gamename); // FIXME: this never gets hit return; @@ -464,7 +478,7 @@ void cli_frontend::listbrothers(const std::vector<std::string> &args) // start with a filtered list of drivers; return an error if none found driver_enumerator initial_drivlist(m_options, gamename); if (initial_drivlist.count() == 0) - throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching systems found for '%s'", gamename); + throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", gamename); // for the final list, start with an empty driver list driver_enumerator drivlist(m_options); @@ -491,11 +505,12 @@ void cli_frontend::listbrothers(const std::vector<std::string> &args) drivlist.reset(); while (drivlist.next()) { - int clone_of = drivlist.clone(); + auto const src(info_xml_creator::format_sourcefile(drivlist.driver().type.source())); + int const clone_of(drivlist.clone()); if (clone_of != -1) - osd_printf_info("%-20s %-16s %s\n", core_filename_extract_base(drivlist.driver().type.source()).c_str(), drivlist.driver().name, (clone_of == -1 ? "" : drivlist.driver(clone_of).name)); + osd_printf_info("%-20s %-16s %s\n", src, drivlist.driver().name, (clone_of == -1 ? "" : drivlist.driver(clone_of).name)); else - osd_printf_info("%-20s %s\n", core_filename_extract_base(drivlist.driver().type.source()).c_str(), drivlist.driver().name); + osd_printf_info("%-20s %s\n", src, drivlist.driver().name); } } @@ -511,7 +526,7 @@ void cli_frontend::listcrc(const std::vector<std::string> &args) args, [] (device_t &root, char const *type, bool first) { - for (device_t const &device : device_iterator(root)) + for (device_t const &device : device_enumerator(root)) { for (tiny_rom_entry const *rom = device.rom_region(); rom && !ROMENTRY_ISEND(rom); ++rom) { @@ -544,59 +559,150 @@ void cli_frontend::listroms(const std::vector<std::string> &args) osd_printf_info("\n"); // iterate through ROMs - bool hasroms = false; - for (device_t const &device : device_iterator(root)) + std::list<std::tuple<std::string, int64_t, std::string>> entries; + std::set<std::string_view> devnames; + for (device_t const &device : device_enumerator(root)) { + bool hasroms = false; for (const rom_entry *region = rom_first_region(device); region; region = rom_next_region(region)) { for (const rom_entry *rom = rom_first_file(region); rom; rom = rom_next_file(rom)) { - // print a header if (!hasroms) - osd_printf_info( - "ROMs required for %s \"%s\".\n" - "%-32s %10s %s\n", - type, root.shortname(), "Name", "Size", "Checksum"); - hasroms = true; + { + hasroms = true; + if (&device != &root) + devnames.insert(device.shortname()); + } // accumulate the total length of all chunks int64_t length = -1; if (ROMREGION_ISROMDATA(region)) length = rom_file_size(rom); - // start with the name - const char *name = ROM_GETNAME(rom); - osd_printf_info("%-32s ", name); + entries.emplace_back(rom->name(), length, rom->hashdata()); + } + } + } - // output the length next - if (length >= 0) - osd_printf_info("%10u", unsigned(uint64_t(length))); + // print results + if (entries.empty()) + osd_printf_info("No ROMs required for %s \"%s\".\n", type, root.shortname()); + else + { + // print a header + osd_printf_info("ROMs required for %s \"%s\"", type, root.shortname()); + if (!devnames.empty()) + { + osd_printf_info(" (including device%s", devnames.size() > 1 ? "s" : ""); + bool first = true; + for (const std::string_view &devname : devnames) + { + if (first) + first = false; else - osd_printf_info("%10s", ""); + osd_printf_info(","); + osd_printf_info(" \"%s\"", devname); + } + osd_printf_info(")"); + } + osd_printf_info(".\n%-32s %10s %s\n", "Name", "Size", "Checksum"); - // output the hash data - util::hash_collection hashes(ROM_GETHASHDATA(rom)); - if (!hashes.flag(util::hash_collection::FLAG_NO_DUMP)) - { - if (hashes.flag(util::hash_collection::FLAG_BAD_DUMP)) - osd_printf_info(" BAD"); - osd_printf_info(" %s", hashes.macro_string().c_str()); - } - else - osd_printf_info(" NO GOOD DUMP KNOWN"); + for (auto &entry : entries) + { + // start with the name + osd_printf_info("%-32s ", std::get<0>(entry)); - // end with a CR - osd_printf_info("\n"); + // output the length next + int64_t length = std::get<1>(entry); + if (length >= 0) + osd_printf_info("%10u", unsigned(uint64_t(length))); + else + osd_printf_info("%10s", ""); + + // output the hash data + util::hash_collection hashes(std::get<2>(entry)); + if (!hashes.flag(util::hash_collection::FLAG_NO_DUMP)) + { + if (hashes.flag(util::hash_collection::FLAG_BAD_DUMP)) + osd_printf_info(" BAD"); + osd_printf_info(" %s", hashes.macro_string()); } + else + osd_printf_info(" NO GOOD DUMP KNOWN"); + + // end with a CR + osd_printf_info("\n"); } } - if (!hasroms) - osd_printf_info("No ROMs required for %s \"%s\".\n", type, root.shortname()); }); } //------------------------------------------------- +// listbios - output the BIOS options for a system +// by matching systems/devices +//------------------------------------------------- + +void cli_frontend::listbios(const std::vector<std::string> &args) +{ + const char *gamename = args.empty() ? nullptr : args[0].c_str(); + + // determine which drivers to output; return an error if none found + driver_enumerator drivlist(m_options, gamename); + if (drivlist.count() == 0) + throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", gamename); + + // iterate over drivers + bool firstsystem = true; + std::vector<std::pair<std::string, std::string> > bioses; + while (drivlist.next()) + { + device_t &root = drivlist.config()->root_device(); + if (firstsystem) + firstsystem = false; + else + printf("\n"); + + // print system BIOS options if there are any + bool firstbios = true; + for (const romload::system_bios &bios : romload::system_bioses(root.rom_region())) + { + if (firstbios) + { + printf("BIOS options for system %s (%s):\n", root.name(), root.shortname()); + firstbios = false; + } + printf(" %-16s %s\n", bios.get_name(), bios.get_description()); + } + if (firstbios) + printf("No BIOS options for system %s (%s)\n", root.name(), root.shortname()); + + // iterate over slots + for (const device_slot_interface &slot : slot_interface_enumerator(root)) + { + // ignore fixed or empty slots + device_t *const card = slot.get_card_device(); + if (slot.fixed() || !card || !card->rom_region()) + continue; + + // print card BIOS options if there are any + bool firstcard = true; + for (const romload::system_bios &bios : romload::system_bioses(card->rom_region())) + { + if (firstcard) + { + printf("\n BIOS options for device %s (-%s %s):\n", card->name(), slot.device().tag() + 1, card->basetag()); + firstcard = false; + } + printf(" %-16s %s\n", bios.get_name(), bios.get_description()); + } + } + } +} + + +//------------------------------------------------- // listsamples - output the list of samples // referenced by a given game or set of games //------------------------------------------------- @@ -608,14 +714,14 @@ void cli_frontend::listsamples(const std::vector<std::string> &args) // determine which drivers to output; return an error if none found driver_enumerator drivlist(m_options, gamename); if (drivlist.count() == 0) - throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching systems found for '%s'", gamename); + throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", gamename); // iterate over drivers, looking for SAMPLES devices bool first = true; while (drivlist.next()) { // see if we have samples - samples_device_iterator iter(drivlist.config()->root_device()); + samples_device_enumerator iter(drivlist.config()->root_device()); if (iter.count() == 0) continue; @@ -648,7 +754,7 @@ void cli_frontend::listdevices(const std::vector<std::string> &args) // determine which drivers to output; return an error if none found driver_enumerator drivlist(m_options, gamename); if (drivlist.count() == 0) - throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching systems found for '%s'", gamename); + throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", gamename); // iterate over drivers, looking for SAMPLES devices bool first = true; @@ -662,7 +768,7 @@ void cli_frontend::listdevices(const std::vector<std::string> &args) // build a list of devices std::vector<device_t *> device_list; - for (device_t &device : device_iterator(drivlist.config()->root_device())) + for (device_t &device : device_enumerator(drivlist.config()->root_device())) device_list.push_back(&device); // sort them by tag @@ -723,7 +829,7 @@ void cli_frontend::listdevices(const std::vector<std::string> &args) //------------------------------------------------- // listslots - output the list of slot devices -// referenced by a given game or set of games +// present in a system or set of systems //------------------------------------------------- void cli_frontend::listslots(const std::vector<std::string> &args) @@ -733,7 +839,7 @@ void cli_frontend::listslots(const std::vector<std::string> &args) // determine which drivers to output; return an error if none found driver_enumerator drivlist(m_options, gamename); if (drivlist.count() == 0) - throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching systems found for '%s'", gamename); + throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", gamename); // print header printf("%-16s %-16s %-16s %s\n", "SYSTEM", "SLOT NAME", "SLOT OPTIONS", "SLOT DEVICE NAME"); @@ -742,11 +848,12 @@ void cli_frontend::listslots(const std::vector<std::string> &args) // iterate over drivers while (drivlist.next()) { - // iterate + // iterate over slots bool first = true; - for (const device_slot_interface &slot : slot_interface_iterator(drivlist.config()->root_device())) + for (const device_slot_interface &slot : slot_interface_enumerator(drivlist.config()->root_device())) { - if (slot.fixed()) continue; + if (slot.fixed()) + continue; // build a list of user-selectable options std::vector<device_slot_interface::slot_option const *> option_list; @@ -755,9 +862,13 @@ void cli_frontend::listslots(const std::vector<std::string> &args) option_list.push_back(option.second.get()); // sort them by name - std::sort(option_list.begin(), option_list.end(), [](device_slot_interface::slot_option const *opt1, device_slot_interface::slot_option const *opt2) { - return strcmp(opt1->name(), opt2->name()) < 0; - }); + std::sort( + option_list.begin(), + option_list.end(), + [] (device_slot_interface::slot_option const *opt1, device_slot_interface::slot_option const *opt2) + { + return strcmp(opt1->name(), opt2->name()) < 0; + }); // output the line, up to the list of extensions @@ -801,7 +912,7 @@ void cli_frontend::listmedia(const std::vector<std::string> &args) // determine which drivers to output; return an error if none found driver_enumerator drivlist(m_options, gamename); if (drivlist.count() == 0) - throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching systems found for '%s'", gamename); + throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", gamename); // print header printf("%-16s %-16s %-10s %s\n", "SYSTEM", "MEDIA NAME", "(brief)", "IMAGE FILE EXTENSIONS SUPPORTED"); @@ -812,7 +923,7 @@ void cli_frontend::listmedia(const std::vector<std::string> &args) { // iterate bool first = true; - for (const device_image_interface &imagedev : image_interface_iterator(drivlist.config()->root_device())) + for (const device_image_interface &imagedev : image_interface_enumerator(drivlist.config()->root_device())) { if (!imagedev.user_loadable()) continue; @@ -821,7 +932,7 @@ void cli_frontend::listmedia(const std::vector<std::string> &args) std::string paren_shortname = string_format("(%s)", imagedev.brief_instance_name()); // output the line, up to the list of extensions - printf("%-16s %-16s %-10s ", first ? drivlist.driver().name : "", imagedev.instance_name().c_str(), paren_shortname.c_str()); + printf("%-16s %-16s %-10s ", drivlist.driver().name, imagedev.instance_name().c_str(), paren_shortname.c_str()); // get the extensions and print them std::string extensions(imagedev.file_extensions()); @@ -850,7 +961,7 @@ void cli_frontend::listmedia(const std::vector<std::string> &args) //------------------------------------------------- void cli_frontend::verifyroms(const std::vector<std::string> &args) { - bool const iswild((1U != args.size()) || core_iswildstr(args[0].c_str())); + bool const iswild((1U != args.size()) || core_iswildstr(args[0])); std::vector<bool> matched(args.size(), false); unsigned matchcount = 0; auto const included = [&args, &matched, &matchcount] (char const *name) -> bool @@ -865,7 +976,7 @@ void cli_frontend::verifyroms(const std::vector<std::string> &args) auto it = matched.begin(); for (std::string const &pat : args) { - if (!core_strwildcmp(pat.c_str(), name)) + if (!core_strwildcmp(pat, name)) { ++matchcount; result = true; @@ -938,7 +1049,7 @@ void cli_frontend::verifyroms(const std::vector<std::string> &args) for (std::string const &pat : args) { if (!*it) - throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching systems found for '%s'", pat.c_str()); + throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", pat); ++it; } @@ -947,9 +1058,9 @@ void cli_frontend::verifyroms(const std::vector<std::string> &args) { // if we didn't get anything at all, display a generic end message if (notfound > 0) - throw emu_fatalerror(EMU_ERR_MISSING_FILES, "romset \"%s\" not found!\n", args[0].c_str()); + throw emu_fatalerror(EMU_ERR_MISSING_FILES, "romset \"%s\" not found!\n", args[0]); else - throw emu_fatalerror(EMU_ERR_MISSING_FILES, "romset \"%s\" has no roms!\n", args[0].c_str()); + throw emu_fatalerror(EMU_ERR_MISSING_FILES, "romset \"%s\" has no roms!\n", args[0]); } else { @@ -1002,20 +1113,19 @@ void cli_frontend::verifysamples(const std::vector<std::string> &args) // return an error if none found if (matched == 0) - throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching systems found for '%s'", gamename); + throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", gamename); - // if we didn't get anything at all, display a generic end message if (matched > 0 && correct == 0 && incorrect == 0) { + // if we didn't get anything at all, display a generic end message if (notfound > 0) throw emu_fatalerror(EMU_ERR_MISSING_FILES, "sampleset \"%s\" not found!\n", gamename); else throw emu_fatalerror(EMU_ERR_MISSING_FILES, "sampleset \"%s\" not required!\n", gamename); } - - // otherwise, print a summary else { + // otherwise, print a summary if (incorrect > 0) throw emu_fatalerror(EMU_ERR_MISSING_FILES, "%u samplesets found, %u were OK.\n", correct + incorrect, correct); osd_printf_info("%u samplesets found, %u were OK.\n", correct, correct); @@ -1023,93 +1133,100 @@ void cli_frontend::verifysamples(const std::vector<std::string> &args) } const char cli_frontend::s_softlist_xml_dtd[] = - "<?xml version=\"1.0\"?>\n" \ - "<!DOCTYPE softwarelists [\n" \ - "<!ELEMENT softwarelists (softwarelist*)>\n" \ - "\t<!ELEMENT softwarelist (software+)>\n" \ - "\t\t<!ATTLIST softwarelist name CDATA #REQUIRED>\n" \ - "\t\t<!ATTLIST softwarelist description CDATA #IMPLIED>\n" \ - "\t\t<!ELEMENT software (description, year, publisher, info*, sharedfeat*, part*)>\n" \ - "\t\t\t<!ATTLIST software name CDATA #REQUIRED>\n" \ - "\t\t\t<!ATTLIST software cloneof CDATA #IMPLIED>\n" \ - "\t\t\t<!ATTLIST software supported (yes|partial|no) \"yes\">\n" \ - "\t\t\t<!ELEMENT description (#PCDATA)>\n" \ - "\t\t\t<!ELEMENT year (#PCDATA)>\n" \ - "\t\t\t<!ELEMENT publisher (#PCDATA)>\n" \ - "\t\t\t<!ELEMENT info EMPTY>\n" \ - "\t\t\t\t<!ATTLIST info name CDATA #REQUIRED>\n" \ - "\t\t\t\t<!ATTLIST info value CDATA #IMPLIED>\n" \ - "\t\t\t<!ELEMENT sharedfeat EMPTY>\n" \ - "\t\t\t\t<!ATTLIST sharedfeat name CDATA #REQUIRED>\n" \ - "\t\t\t\t<!ATTLIST sharedfeat value CDATA #IMPLIED>\n" \ - "\t\t\t<!ELEMENT part (feature*, dataarea*, diskarea*, dipswitch*)>\n" \ - "\t\t\t\t<!ATTLIST part name CDATA #REQUIRED>\n" \ - "\t\t\t\t<!ATTLIST part interface CDATA #REQUIRED>\n" \ - "\t\t\t\t<!ELEMENT feature EMPTY>\n" \ - "\t\t\t\t\t<!ATTLIST feature name CDATA #REQUIRED>\n" \ - "\t\t\t\t\t<!ATTLIST feature value CDATA #IMPLIED>\n" \ - "\t\t\t\t<!ELEMENT dataarea (rom*)>\n" \ - "\t\t\t\t\t<!ATTLIST dataarea name CDATA #REQUIRED>\n" \ - "\t\t\t\t\t<!ATTLIST dataarea size CDATA #REQUIRED>\n" \ - "\t\t\t\t\t<!ATTLIST dataarea databits (8|16|32|64) \"8\">\n" \ - "\t\t\t\t\t<!ATTLIST dataarea endian (big|little) \"little\">\n" \ - "\t\t\t\t\t<!ELEMENT rom EMPTY>\n" \ - "\t\t\t\t\t\t<!ATTLIST rom name CDATA #IMPLIED>\n" \ - "\t\t\t\t\t\t<!ATTLIST rom size CDATA #IMPLIED>\n" \ - "\t\t\t\t\t\t<!ATTLIST rom length CDATA #IMPLIED>\n" \ - "\t\t\t\t\t\t<!ATTLIST rom crc CDATA #IMPLIED>\n" \ - "\t\t\t\t\t\t<!ATTLIST rom sha1 CDATA #IMPLIED>\n" \ - "\t\t\t\t\t\t<!ATTLIST rom offset CDATA #IMPLIED>\n" \ - "\t\t\t\t\t\t<!ATTLIST rom value CDATA #IMPLIED>\n" \ - "\t\t\t\t\t\t<!ATTLIST rom status (baddump|nodump|good) \"good\">\n" \ - "\t\t\t\t\t\t<!ATTLIST rom loadflag (load16_byte|load16_word|load16_word_swap|load32_byte|load32_word|load32_word_swap|load32_dword|load64_word|load64_word_swap|reload|fill|continue|reload_plain) #IMPLIED>\n" \ - "\t\t\t\t<!ELEMENT diskarea (disk*)>\n" \ - "\t\t\t\t\t<!ATTLIST diskarea name CDATA #REQUIRED>\n" \ - "\t\t\t\t\t<!ELEMENT disk EMPTY>\n" \ - "\t\t\t\t\t\t<!ATTLIST disk name CDATA #REQUIRED>\n" \ - "\t\t\t\t\t\t<!ATTLIST disk sha1 CDATA #IMPLIED>\n" \ - "\t\t\t\t\t\t<!ATTLIST disk status (baddump|nodump|good) \"good\">\n" \ - "\t\t\t\t\t\t<!ATTLIST disk writeable (yes|no) \"no\">\n" \ - "\t\t\t\t<!ELEMENT dipswitch (dipvalue*)>\n" \ - "\t\t\t\t\t<!ATTLIST dipswitch name CDATA #REQUIRED>\n" \ - "\t\t\t\t\t<!ATTLIST dipswitch tag CDATA #REQUIRED>\n" \ - "\t\t\t\t\t<!ATTLIST dipswitch mask CDATA #REQUIRED>\n" \ - "\t\t\t\t\t<!ELEMENT dipvalue EMPTY>\n" \ - "\t\t\t\t\t\t<!ATTLIST dipvalue name CDATA #REQUIRED>\n" \ - "\t\t\t\t\t\t<!ATTLIST dipvalue value CDATA #REQUIRED>\n" \ - "\t\t\t\t\t\t<!ATTLIST dipvalue default (yes|no) \"no\">\n" \ + "<?xml version=\"1.0\"?>\n" + "<!DOCTYPE softwarelists [\n" + "<!ELEMENT softwarelists (softwarelist*)>\n" + "\t<!ELEMENT softwarelist (notes?, software+)>\n" + "\t\t<!ATTLIST softwarelist name CDATA #REQUIRED>\n" + "\t\t<!ATTLIST softwarelist description CDATA #IMPLIED>\n" + "\t\t<!ELEMENT notes (#PCDATA)>\n" + "\t\t<!ELEMENT software (description, year, publisher, notes?, info*, sharedfeat*, part*)>\n" + "\t\t\t<!ATTLIST software name CDATA #REQUIRED>\n" + "\t\t\t<!ATTLIST software cloneof CDATA #IMPLIED>\n" + "\t\t\t<!ATTLIST software supported (yes|partial|no) \"yes\">\n" + "\t\t\t<!ELEMENT description (#PCDATA)>\n" + "\t\t\t<!ELEMENT year (#PCDATA)>\n" + "\t\t\t<!ELEMENT publisher (#PCDATA)>\n" + "\t\t\t<!ELEMENT notes (#PCDATA)>\n" + "\t\t\t<!ELEMENT info EMPTY>\n" + "\t\t\t\t<!ATTLIST info name CDATA #REQUIRED>\n" + "\t\t\t\t<!ATTLIST info value CDATA #IMPLIED>\n" + "\t\t\t<!ELEMENT sharedfeat EMPTY>\n" + "\t\t\t\t<!ATTLIST sharedfeat name CDATA #REQUIRED>\n" + "\t\t\t\t<!ATTLIST sharedfeat value CDATA #IMPLIED>\n" + "\t\t\t<!ELEMENT part (feature*, dataarea*, diskarea*, dipswitch*)>\n" + "\t\t\t\t<!ATTLIST part name CDATA #REQUIRED>\n" + "\t\t\t\t<!ATTLIST part interface CDATA #REQUIRED>\n" + "\t\t\t\t<!ELEMENT feature EMPTY>\n" + "\t\t\t\t\t<!ATTLIST feature name CDATA #REQUIRED>\n" + "\t\t\t\t\t<!ATTLIST feature value CDATA #IMPLIED>\n" + "\t\t\t\t<!ELEMENT dataarea (rom*)>\n" + "\t\t\t\t\t<!ATTLIST dataarea name CDATA #REQUIRED>\n" + "\t\t\t\t\t<!ATTLIST dataarea size CDATA #REQUIRED>\n" + "\t\t\t\t\t<!ATTLIST dataarea databits (8|16|32|64) \"8\">\n" + "\t\t\t\t\t<!ATTLIST dataarea endian (big|little) \"little\">\n" + "\t\t\t\t\t<!ELEMENT rom EMPTY>\n" + "\t\t\t\t\t\t<!ATTLIST rom name CDATA #IMPLIED>\n" + "\t\t\t\t\t\t<!ATTLIST rom size CDATA #IMPLIED>\n" + "\t\t\t\t\t\t<!ATTLIST rom length CDATA #IMPLIED>\n" + "\t\t\t\t\t\t<!ATTLIST rom crc CDATA #IMPLIED>\n" + "\t\t\t\t\t\t<!ATTLIST rom sha1 CDATA #IMPLIED>\n" + "\t\t\t\t\t\t<!ATTLIST rom offset CDATA #IMPLIED>\n" + "\t\t\t\t\t\t<!ATTLIST rom value CDATA #IMPLIED>\n" + "\t\t\t\t\t\t<!ATTLIST rom status (baddump|nodump|good) \"good\">\n" + "\t\t\t\t\t\t<!ATTLIST rom loadflag (load16_byte|load16_word|load16_word_swap|load32_byte|load32_word|load32_word_swap|load32_dword|load64_word|load64_word_swap|reload|fill|continue|reload_plain) #IMPLIED>\n" + "\t\t\t\t<!ELEMENT diskarea (disk*)>\n" + "\t\t\t\t\t<!ATTLIST diskarea name CDATA #REQUIRED>\n" + "\t\t\t\t\t<!ELEMENT disk EMPTY>\n" + "\t\t\t\t\t\t<!ATTLIST disk name CDATA #REQUIRED>\n" + "\t\t\t\t\t\t<!ATTLIST disk sha1 CDATA #IMPLIED>\n" + "\t\t\t\t\t\t<!ATTLIST disk status (baddump|nodump|good) \"good\">\n" + "\t\t\t\t\t\t<!ATTLIST disk writeable (yes|no) \"no\">\n" + "\t\t\t\t<!ELEMENT dipswitch (dipvalue*)>\n" + "\t\t\t\t\t<!ATTLIST dipswitch name CDATA #REQUIRED>\n" + "\t\t\t\t\t<!ATTLIST dipswitch tag CDATA #REQUIRED>\n" + "\t\t\t\t\t<!ATTLIST dipswitch mask CDATA #REQUIRED>\n" + "\t\t\t\t\t<!ELEMENT dipvalue EMPTY>\n" + "\t\t\t\t\t\t<!ATTLIST dipvalue name CDATA #REQUIRED>\n" + "\t\t\t\t\t\t<!ATTLIST dipvalue value CDATA #REQUIRED>\n" + "\t\t\t\t\t\t<!ATTLIST dipvalue default (yes|no) \"no\">\n" "]>\n\n"; -void cli_frontend::output_single_softlist(FILE *out, software_list_device &swlistdev) +void cli_frontend::output_single_softlist(std::ostream &out, software_list_device &swlistdev) { - fprintf(out, "\t<softwarelist name=\"%s\" description=\"%s\">\n", swlistdev.list_name().c_str(), util::xml::normalize_string(swlistdev.description().c_str())); + using util::xml::normalize_string; + + util::stream_format(out, "\t<softwarelist name=\"%s\" description=\"%s\">\n", swlistdev.list_name(), normalize_string(swlistdev.description())); for (const software_info &swinfo : swlistdev.get_info()) { - fprintf(out, "\t\t<software name=\"%s\"", swinfo.shortname().c_str()); + util::stream_format(out, "\t\t<software name=\"%s\"", normalize_string(swinfo.shortname())); if (!swinfo.parentname().empty()) - fprintf(out, " cloneof=\"%s\"", swinfo.parentname().c_str()); - if (swinfo.supported() == SOFTWARE_SUPPORTED_PARTIAL) - fprintf(out, " supported=\"partial\""); - if (swinfo.supported() == SOFTWARE_SUPPORTED_NO) - fprintf(out, " supported=\"no\""); - fprintf(out, ">\n" ); - fprintf(out, "\t\t\t<description>%s</description>\n", util::xml::normalize_string(swinfo.longname().c_str())); - fprintf(out, "\t\t\t<year>%s</year>\n", util::xml::normalize_string(swinfo.year().c_str())); - fprintf(out, "\t\t\t<publisher>%s</publisher>\n", util::xml::normalize_string(swinfo.publisher().c_str())); - - for (const feature_list_item &flist : swinfo.other_info()) - fprintf( out, "\t\t\t<info name=\"%s\" value=\"%s\"/>\n", flist.name().c_str(), util::xml::normalize_string( flist.value().c_str()) ); + util::stream_format(out, " cloneof=\"%s\"", normalize_string(swinfo.parentname())); + if (swinfo.supported() == software_support::PARTIALLY_SUPPORTED) + out << " supported=\"partial\""; + else if (swinfo.supported() == software_support::UNSUPPORTED) + out << " supported=\"no\""; + out << ">\n"; + util::stream_format(out, "\t\t\t<description>%s</description>\n", normalize_string(swinfo.longname())); + util::stream_format(out, "\t\t\t<year>%s</year>\n", normalize_string(swinfo.year())); + util::stream_format(out, "\t\t\t<publisher>%s</publisher>\n", normalize_string(swinfo.publisher())); + + for (const auto &flist : swinfo.info()) + util::stream_format(out, "\t\t\t<info name=\"%s\" value=\"%s\"/>\n", flist.name(), normalize_string(flist.value())); + + for (const auto &flist : swinfo.shared_features()) + util::stream_format(out, "\t\t\t<sharedfeat name=\"%s\" value=\"%s\"/>\n", flist.name(), normalize_string(flist.value())); for (const software_part &part : swinfo.parts()) { - fprintf(out, "\t\t\t<part name=\"%s\"", part.name().c_str()); + util::stream_format(out, "\t\t\t<part name=\"%s\"", normalize_string(part.name())); if (!part.interface().empty()) - fprintf(out, " interface=\"%s\"", part.interface().c_str()); + util::stream_format(out, " interface=\"%s\"", normalize_string(part.interface())); - fprintf(out, ">\n"); + out << ">\n"; - for (const feature_list_item &flist : part.featurelist()) - fprintf(out, "\t\t\t\t<feature name=\"%s\" value=\"%s\" />\n", flist.name().c_str(), util::xml::normalize_string(flist.value().c_str())); + for (const auto &flist : part.features()) + util::stream_format(out, "\t\t\t\t<feature name=\"%s\" value=\"%s\" />\n", flist.name(), normalize_string(flist.value())); // TODO: display ROM region information for (const rom_entry *region = part.romdata().data(); region; region = rom_next_region(region)) @@ -1117,127 +1234,124 @@ void cli_frontend::output_single_softlist(FILE *out, software_list_device &swlis int is_disk = ROMREGION_ISDISKDATA(region); if (!is_disk) - fprintf( out, "\t\t\t\t<dataarea name=\"%s\" size=\"%d\">\n", ROMREGION_GETTAG(region), ROMREGION_GETLENGTH(region) ); + util::stream_format(out, "\t\t\t\t<dataarea name=\"%s\" size=\"%d\">\n", normalize_string(region->name()), region->get_length()); else - fprintf( out, "\t\t\t\t<diskarea name=\"%s\">\n", ROMREGION_GETTAG(region) ); + util::stream_format(out, "\t\t\t\t<diskarea name=\"%s\">\n", normalize_string(region->name())); - for ( const rom_entry *rom = rom_first_file( region ); rom && !ROMENTRY_ISREGIONEND(rom); rom++ ) + for (const rom_entry *rom = rom_first_file(region); rom && !ROMENTRY_ISREGIONEND(rom); rom++) { - if ( ROMENTRY_ISFILE(rom) ) + if (ROMENTRY_ISFILE(rom)) { if (!is_disk) - fprintf( out, "\t\t\t\t\t<rom name=\"%s\" size=\"%d\"", util::xml::normalize_string(ROM_GETNAME(rom)), rom_file_size(rom) ); + util::stream_format(out, "\t\t\t\t\t<rom name=\"%s\" size=\"%d\"", normalize_string(ROM_GETNAME(rom)), rom_file_size(rom)); else - fprintf( out, "\t\t\t\t\t<disk name=\"%s\"", util::xml::normalize_string(ROM_GETNAME(rom)) ); + util::stream_format(out, "\t\t\t\t\t<disk name=\"%s\"", normalize_string(ROM_GETNAME(rom))); - /* dump checksum information only if there is a known dump */ - util::hash_collection hashes(ROM_GETHASHDATA(rom)); - if ( !hashes.flag(util::hash_collection::FLAG_NO_DUMP) ) - fprintf( out, " %s", hashes.attribute_string().c_str() ); + // dump checksum information only if there is a known dump + util::hash_collection hashes(rom->hashdata()); + if (!hashes.flag(util::hash_collection::FLAG_NO_DUMP)) + util::stream_format(out, " %s", hashes.attribute_string()); else - fprintf( out, " status=\"nodump\"" ); + out << " status=\"nodump\""; if (is_disk) - fprintf( out, " writeable=\"%s\"", (ROM_GETFLAGS(rom) & DISK_READONLYMASK) ? "no" : "yes"); + util::stream_format(out, " writeable=\"%s\"", (ROM_GETFLAGS(rom) & DISK_READONLYMASK) ? "no" : "yes"); if ((ROM_GETFLAGS(rom) & ROM_SKIPMASK) == ROM_SKIP(1)) - fprintf( out, " loadflag=\"load16_byte\"" ); + out << " loadflag=\"load16_byte\""; if ((ROM_GETFLAGS(rom) & ROM_SKIPMASK) == ROM_SKIP(3)) - fprintf( out, " loadflag=\"load32_byte\"" ); + out << " loadflag=\"load32_byte\""; if (((ROM_GETFLAGS(rom) & ROM_SKIPMASK) == ROM_SKIP(2)) && ((ROM_GETFLAGS(rom) & ROM_GROUPMASK) == ROM_GROUPWORD)) { if (!(ROM_GETFLAGS(rom) & ROM_REVERSEMASK)) - fprintf( out, " loadflag=\"load32_word\"" ); + out << " loadflag=\"load32_word\""; else - fprintf( out, " loadflag=\"load32_word_swap\"" ); + out << " loadflag=\"load32_word_swap\""; } if (((ROM_GETFLAGS(rom) & ROM_SKIPMASK) == ROM_SKIP(6)) && ((ROM_GETFLAGS(rom) & ROM_GROUPMASK) == ROM_GROUPWORD)) { if (!(ROM_GETFLAGS(rom) & ROM_REVERSEMASK)) - fprintf( out, " loadflag=\"load64_word\"" ); + out << " loadflag=\"load64_word\""; else - fprintf( out, " loadflag=\"load64_word_swap\"" ); + out << " loadflag=\"load64_word_swap\""; } if (((ROM_GETFLAGS(rom) & ROM_SKIPMASK) == ROM_NOSKIP) && ((ROM_GETFLAGS(rom) & ROM_GROUPMASK) == ROM_GROUPWORD)) { if (!(ROM_GETFLAGS(rom) & ROM_REVERSEMASK)) - fprintf( out, " loadflag=\"load32_dword\"" ); + out << " loadflag=\"load32_dword\""; else - fprintf( out, " loadflag=\"load16_word_swap\"" ); + out << " loadflag=\"load16_word_swap\""; } - fprintf( out, "/>\n" ); + out << "/>\n"; } - else if ( ROMENTRY_ISRELOAD(rom) ) + else if (ROMENTRY_ISRELOAD(rom)) { - fprintf( out, "\t\t\t\t\t<rom size=\"%d\" offset=\"0x%x\" loadflag=\"reload\" />\n", ROM_GETLENGTH(rom), ROM_GETOFFSET(rom) ); + util::stream_format(out, "\t\t\t\t\t<rom size=\"%d\" offset=\"0x%x\" loadflag=\"reload\" />\n", ROM_GETLENGTH(rom), ROM_GETOFFSET(rom)); } - else if ( ROMENTRY_ISFILL(rom) ) + else if (ROMENTRY_ISFILL(rom)) { - fprintf( out, "\t\t\t\t\t<rom size=\"%d\" offset=\"0x%x\" loadflag=\"fill\" />\n", ROM_GETLENGTH(rom), ROM_GETOFFSET(rom) ); + util::stream_format(out, "\t\t\t\t\t<rom size=\"%d\" offset=\"0x%x\" loadflag=\"fill\" />\n", ROM_GETLENGTH(rom), ROM_GETOFFSET(rom)); } } if (!is_disk) - fprintf( out, "\t\t\t\t</dataarea>\n" ); + out << "\t\t\t\t</dataarea>\n"; else - fprintf( out, "\t\t\t\t</diskarea>\n" ); + out << "\t\t\t\t</diskarea>\n"; } - fprintf( out, "\t\t\t</part>\n" ); + out << "\t\t\t</part>\n"; } - fprintf( out, "\t\t</software>\n" ); + out << "\t\t</software>\n"; } - fprintf(out, "\t</softwarelist>\n" ); + out << "\t</softwarelist>\n"; } + + /*------------------------------------------------- info_listsoftware - output the list of software supported by a given game or set of games TODO: Add all information read from the source files - Possible improvement: use a sorted list for - identifying duplicate lists. -------------------------------------------------*/ void cli_frontend::listsoftware(const std::vector<std::string> &args) { - const char *gamename = args.empty() ? nullptr : args[0].c_str(); - - FILE *out = stdout; std::unordered_set<std::string> list_map; - bool isfirst = true; - - // determine which drivers to output; return an error if none found - driver_enumerator drivlist(m_options, gamename); - if (drivlist.count() == 0) - throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching systems found for '%s'", gamename); - - while (drivlist.next()) - { - for (software_list_device &swlistdev : software_list_device_iterator(drivlist.config()->root_device())) - if (list_map.insert(swlistdev.list_name()).second) - if (!swlistdev.get_info().empty()) + bool firstlist(true); + apply_device_action( + args, + [this, &list_map, &firstlist] (device_t &root, char const *type, bool first) + { + for (software_list_device &swlistdev : software_list_device_enumerator(root)) { - if (isfirst) + if (list_map.insert(swlistdev.list_name()).second) { - if (m_options.bool_value(CLIOPTION_DTD)) - fprintf(out, s_softlist_xml_dtd); - fprintf(out, "<softwarelists>\n"); - isfirst = false; + if (!swlistdev.get_info().empty()) + { + if (firstlist) + { + if (m_options.bool_value(CLIOPTION_DTD)) + std::cout << s_softlist_xml_dtd; + std::cout << "<softwarelists>\n"; + firstlist = false; + } + output_single_softlist(std::cout, swlistdev); + } } - output_single_softlist(out, swlistdev); } - } + }); - if (!isfirst) - fprintf( out, "</softwarelists>\n" ); + if (!firstlist) + std::cout << "</softwarelists>\n"; else - fprintf( out, "No software lists found for this system\n" ); + fprintf(stdout, "No software lists found for this system\n"); // TODO: should this go to stderr instead? } @@ -1247,30 +1361,27 @@ void cli_frontend::listsoftware(const std::vector<std::string> &args) -------------------------------------------------*/ void cli_frontend::verifysoftware(const std::vector<std::string> &args) { - const char *gamename = args.empty() ? "*" : args[0].c_str(); + char const *const gamename = args.empty() ? "*" : args[0].c_str(); + + // determine which drivers to process; return an error if none found + driver_enumerator drivlist(m_options, gamename); + if (!drivlist.count()) + throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", gamename); std::unordered_set<std::string> list_map; unsigned correct = 0; unsigned incorrect = 0; unsigned notfound = 0; - unsigned matched = 0; unsigned nrlists = 0; - // determine which drivers to process; return an error if none found - driver_enumerator drivlist(m_options, gamename); - if (drivlist.count() == 0) - throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching systems found for '%s'", gamename); - media_auditor auditor(drivlist); util::ovectorstream summary_string; while (drivlist.next()) { - matched++; - - for (software_list_device &swlistdev : software_list_device_iterator(drivlist.config()->root_device())) + for (software_list_device &swlistdev : software_list_device_enumerator(drivlist.config()->root_device())) { - if (swlistdev.list_type() == SOFTWARE_LIST_ORIGINAL_SYSTEM) + if (swlistdev.is_original()) { if (list_map.insert(swlistdev.list_name()).second) { @@ -1279,7 +1390,7 @@ void cli_frontend::verifysoftware(const std::vector<std::string> &args) nrlists++; for (const software_info &swinfo : swlistdev.get_info()) { - media_auditor::summary summary = auditor.audit_software(swlistdev.list_name(), &swinfo, AUDIT_VALIDATE_FAST); + media_auditor::summary summary = auditor.audit_software(swlistdev, swinfo, AUDIT_VALIDATE_FAST); print_summary( auditor, summary, false, @@ -1297,18 +1408,19 @@ void cli_frontend::verifysoftware(const std::vector<std::string> &args) util::archive_file::cache_clear(); // return an error if none found - if (matched == 0) - throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching systems found for '%s'", gamename); - - // if we didn't get anything at all, display a generic end message - if (matched > 0 && correct == 0 && incorrect == 0) + if (!nrlists) { - throw emu_fatalerror(EMU_ERR_MISSING_FILES, "romset \"%s\" has no software entries defined!\n", gamename); + throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No software list items are defined for systems matching '%s'", gamename); + } + else if (!correct && !incorrect) + { + // if we didn't get anything at all, display a generic end message + throw emu_fatalerror(EMU_ERR_MISSING_FILES, "No software items found for systems matching '%s'", gamename); } - // otherwise, print a summary else { - if (incorrect > 0) + // otherwise, print a summary + if (incorrect) throw emu_fatalerror(EMU_ERR_MISSING_FILES, "%u romsets found in %u software lists, %u were OK.\n", correct + incorrect, nrlists, correct); osd_printf_info("%u romsets found in %u software lists, %u romsets were OK.\n", correct, nrlists, correct); } @@ -1324,32 +1436,35 @@ void cli_frontend::getsoftlist(const std::vector<std::string> &args) { const char *gamename = args.empty() ? "*" : args[0].c_str(); - FILE *out = stdout; std::unordered_set<std::string> list_map; - bool isfirst = true; - - driver_enumerator drivlist(m_options); - while (drivlist.next()) - { - for (software_list_device &swlistdev : software_list_device_iterator(drivlist.config()->root_device())) - if (core_strwildcmp(gamename, swlistdev.list_name().c_str()) == 0 && list_map.insert(swlistdev.list_name()).second) - if (!swlistdev.get_info().empty()) + bool firstlist(true); + apply_device_action( + std::vector<std::string>(), + [this, gamename, &list_map, &firstlist] (device_t &root, char const *type, bool first) + { + for (software_list_device &swlistdev : software_list_device_enumerator(root)) { - if (isfirst) + if (core_strwildcmp(gamename, swlistdev.list_name()) == 0 && list_map.insert(swlistdev.list_name()).second) { - if (m_options.bool_value(CLIOPTION_DTD)) - fprintf(out, s_softlist_xml_dtd); - fprintf(out, "<softwarelists>\n"); - isfirst = false; + if (!swlistdev.get_info().empty()) + { + if (firstlist) + { + if (m_options.bool_value(CLIOPTION_DTD)) + std::cout << s_softlist_xml_dtd; + std::cout << "<softwarelists>\n"; + firstlist = false; + } + output_single_softlist(std::cout, swlistdev); + } } - output_single_softlist(out, swlistdev); } - } + }); - if (!isfirst) - fprintf( out, "</softwarelists>\n" ); + if (!firstlist) + std::cout << "</softwarelists>\n"; else - fprintf( out, "No such software lists found\n" ); + fprintf(stdout, "No such software lists found\n"); // TODO: should this go to stderr instead? } @@ -1372,9 +1487,9 @@ void cli_frontend::verifysoftlist(const std::vector<std::string> &args) while (drivlist.next()) { - for (software_list_device &swlistdev : software_list_device_iterator(drivlist.config()->root_device())) + for (software_list_device &swlistdev : software_list_device_enumerator(drivlist.config()->root_device())) { - if (core_strwildcmp(gamename, swlistdev.list_name().c_str()) == 0 && list_map.insert(swlistdev.list_name()).second) + if (core_strwildcmp(gamename, swlistdev.list_name()) == 0 && list_map.insert(swlistdev.list_name()).second) { if (!swlistdev.get_info().empty()) { @@ -1383,7 +1498,7 @@ void cli_frontend::verifysoftlist(const std::vector<std::string> &args) // Get the actual software list contents for (const software_info &swinfo : swlistdev.get_info()) { - media_auditor::summary summary = auditor.audit_software(swlistdev.list_name(), &swinfo, AUDIT_VALIDATE_FAST); + media_auditor::summary summary = auditor.audit_software(swlistdev, swinfo, AUDIT_VALIDATE_FAST); print_summary( auditor, summary, false, @@ -1401,7 +1516,7 @@ void cli_frontend::verifysoftlist(const std::vector<std::string> &args) // return an error if none found if (matched == 0) - throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching software lists found for '%s'", gamename); + throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching software lists found for '%s'", gamename); // if we didn't get anything at all, display a generic end message if (matched > 0 && correct == 0 && incorrect == 0) @@ -1424,7 +1539,7 @@ void cli_frontend::verifysoftlist(const std::vector<std::string> &args) void cli_frontend::version(const std::vector<std::string> &args) { - osd_printf_info("%s", emulator_info::get_build_version()); + osd_printf_info("%s\n", emulator_info::get_build_version()); } @@ -1440,7 +1555,7 @@ void cli_frontend::romident(const std::vector<std::string> &args) // create our own copy of options for the purposes of ROM identification // so we are not "polluted" with driver-specific slot/image options emu_options options; - options.set_value(OPTION_MEDIAPATH, m_options.media_path(), OPTION_PRIORITY_DEFAULT); + options.set_value(OPTION_HASHPATH, m_options.hash_path(), OPTION_PRIORITY_DEFAULT); media_identifier ident(options); @@ -1449,7 +1564,9 @@ void cli_frontend::romident(const std::vector<std::string> &args) ident.identify(filename); // return the appropriate error code - if (ident.matches() == ident.total()) + if (ident.total() == 0) + throw emu_fatalerror(EMU_ERR_MISSING_FILES, "No files found.\n"); + else if (ident.matches() == ident.total()) return; else if (ident.matches() == ident.total() - ident.nonroms()) throw emu_fatalerror(EMU_ERR_IDENT_NONROMS, "Out of %d files, %d matched, %d are not roms.\n", ident.total(), ident.matches(), ident.nonroms()); @@ -1468,7 +1585,7 @@ void cli_frontend::romident(const std::vector<std::string> &args) template <typename T, typename U> void cli_frontend::apply_action(const std::vector<std::string> &args, T &&drvact, U &&devact) { - bool const iswild((1U != args.size()) || core_iswildstr(args[0].c_str())); + bool const iswild((1U != args.size()) || core_iswildstr(args[0])); std::vector<bool> matched(args.size(), false); auto const included = [&args, &matched] (char const *name) -> bool { @@ -1479,7 +1596,7 @@ template <typename T, typename U> void cli_frontend::apply_action(const std::vec auto it = matched.begin(); for (std::string const &pat : args) { - if (!core_strwildcmp(pat.c_str(), name)) + if (!core_strwildcmp(pat, name)) { result = true; *it = true; @@ -1528,7 +1645,7 @@ template <typename T, typename U> void cli_frontend::apply_action(const std::vec for (std::string const &pat : args) { if (!*it) - throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching systems found for '%s'", pat.c_str()); + throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", pat); ++it; } @@ -1575,6 +1692,7 @@ const cli_frontend::info_command_struct *cli_frontend::find_command(const std::s { CLICOMMAND_LISTCRC, 0, -1, &cli_frontend::listcrc, "[system name]" }, { CLICOMMAND_LISTDEVICES, 0, 1, &cli_frontend::listdevices, "[system name]" }, { CLICOMMAND_LISTSLOTS, 0, 1, &cli_frontend::listslots, "[system name]" }, + { CLICOMMAND_LISTBIOS, 0, 1, &cli_frontend::listbios, "[system name]" }, { CLICOMMAND_LISTROMS, 0, -1, &cli_frontend::listroms, "[pattern] ..." }, { CLICOMMAND_LISTSAMPLES, 0, 1, &cli_frontend::listsamples, "[system name]" }, { CLICOMMAND_VERIFYROMS, 0, -1, &cli_frontend::verifyroms, "[pattern] ..." }, @@ -1602,7 +1720,7 @@ const cli_frontend::info_command_struct *cli_frontend::find_command(const std::s // commands //------------------------------------------------- -void cli_frontend::execute_commands(const char *exename) +void cli_frontend::execute_commands(std::string_view exename) { // help? if (m_options.command() == CLICOMMAND_HELP) @@ -1615,7 +1733,7 @@ void cli_frontend::execute_commands(const char *exename) if (m_options.command() == CLICOMMAND_SHOWUSAGE) { osd_printf_info("Usage: %s [machine] [media] [software] [options]",exename); - osd_printf_info("\n\nOptions:\n%s", m_options.output_help().c_str()); + osd_printf_info("\n\nOptions:\n%s", m_options.output_help()); return; } @@ -1627,8 +1745,7 @@ void cli_frontend::execute_commands(const char *exename) osd_printf_error("Auxiliary verb -validate takes at most 1 argument\n"); return; } - validity_checker valid(m_options); - valid.set_validate_all(true); + validity_checker valid(m_options, false); const char *sysname = m_options.command_arguments().empty() ? nullptr : m_options.command_arguments()[0].c_str(); bool result = valid.check_all_matching(sysname); if (!result) @@ -1640,41 +1757,46 @@ void cli_frontend::execute_commands(const char *exename) std::ostringstream option_errors; mame_options::parse_standard_inis(m_options,option_errors); if (option_errors.tellp() > 0) - osd_printf_error("%s\n", option_errors.str().c_str()); + osd_printf_error("%s\n", option_errors.str()); // createconfig? if (m_options.command() == CLICOMMAND_CREATECONFIG) { - // attempt to open the output file + // attempt to open the output file and generate the updated (mame).ini emu_file file(OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); - if (file.open(emulator_info::get_configname(), ".ini") != osd_file::error::NONE) + if (file.open(std::string(emulator_info::get_configname()) + ".ini")) throw emu_fatalerror("Unable to create file %s.ini\n",emulator_info::get_configname()); - // generate the updated INI - file.puts(m_options.output_ini().c_str()); + file.puts(m_options.output_ini()); + // ui.ini ui_options ui_opts; emu_file file_ui(OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); - if (file_ui.open("ui.ini") != osd_file::error::NONE) + if (file_ui.open("ui.ini")) throw emu_fatalerror("Unable to create file ui.ini\n"); - // generate the updated INI - file_ui.puts(ui_opts.output_ini().c_str()); + file_ui.puts(ui_opts.output_ini()); + // plugin.ini plugin_options plugin_opts; path_iterator iter(m_options.plugins_path()); std::string pluginpath; while (iter.next(pluginpath)) - { - osd_subst_env(pluginpath, pluginpath); plugin_opts.scan_directory(pluginpath, true); - } - emu_file file_plugin(OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); - if (file_plugin.open("plugin.ini") != osd_file::error::NONE) - throw emu_fatalerror("Unable to create file plugin.ini\n"); - // generate the updated INI - file_plugin.puts(plugin_opts.output_ini().c_str()); + std::string plugins(plugin_opts.output_ini()); + + // only update the file when it found plugins + if (!plugins.empty()) + { + emu_file file_plugin(OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); + if (file_plugin.open("plugin.ini")) + throw emu_fatalerror("Unable to create file plugin.ini\n"); + + file_plugin.puts(plugins); + } + else + osd_printf_error("Skipped plugin.ini, could not find any plugins\n"); return; } @@ -1713,7 +1835,7 @@ void cli_frontend::execute_commands(const char *exename) if (!m_osd.execute_command(m_options.command().c_str())) // if we get here, we don't know what has been requested - throw emu_fatalerror(EMU_ERR_INVALID_CONFIG, "Unknown command '%s' specified", m_options.command().c_str()); + throw emu_fatalerror(EMU_ERR_INVALID_CONFIG, "Unknown command '%s' specified", m_options.command()); } @@ -1722,18 +1844,27 @@ void cli_frontend::execute_commands(const char *exename) // output //------------------------------------------------- -void cli_frontend::display_help(const char *exename) +void cli_frontend::display_help(std::string_view exename) { - osd_printf_info("%s v%s\n%s\n\n", emulator_info::get_appname(),build_version,emulator_info::get_copyright_info()); - osd_printf_info("This software reproduces, more or less faithfully, the behaviour of a wide range\n" - "of machines. But hardware is useless without software, so images of the ROMs and\n" - "other media which run on that hardware are also required.\n\n"); - osd_printf_info("Usage: %s [machine] [media] [software] [options]",exename); - osd_printf_info("\n\n" - " %s -showusage for a list of options\n" - " %s -showconfig to show your current %s.ini\n" - " %s -listmedia for a full list of supported media\n" - " %s -createconfig to create a %s.ini\n\n" - "For usage instructions, please consult the files config.txt and windows.txt.\n",exename, - exename,emulator_info::get_configname(),exename,exename,emulator_info::get_configname()); + osd_printf_info( + "%3$s v%2$s\n" + "%5$s\n" + "\n" + "This software reproduces, more or less faithfully, the behaviour of a wide range\n" + "of machines. But hardware is useless without software, so images of the ROMs and\n" + "other media which run on that hardware are also required.\n" + "\n" + "Usage: %1$s [machine] [media] [software] [options]\n" + "\n" + " %1$s -showusage for a list of options\n" + " %1$s -showconfig to show current configuration in %4$s.ini format\n" + " %1$s -listmedia for a full list of supported media\n" + " %1$s -createconfig to create a %4$s.ini file\n" + "\n" + "For usage instructions, please visit https://docs.mamedev.org/\n", + exename, + build_version, + emulator_info::get_appname(), + emulator_info::get_configname(), + emulator_info::get_copyright_info()); } diff --git a/src/frontend/mame/clifront.h b/src/frontend/mame/clifront.h index 84157f2a6d8..0857a3d98b1 100644 --- a/src/frontend/mame/clifront.h +++ b/src/frontend/mame/clifront.h @@ -54,6 +54,7 @@ private: void listbrothers(const std::vector<std::string> &args); void listcrc(const std::vector<std::string> &args); void listroms(const std::vector<std::string> &args); + void listbios(const std::vector<std::string> &args); void listsamples(const std::vector<std::string> &args); void listdevices(const std::vector<std::string> &args); void listslots(const std::vector<std::string> &args); @@ -70,9 +71,9 @@ private: // internal helpers template <typename T, typename U> void apply_action(const std::vector<std::string> &args, T &&drvact, U &&devact); template <typename T> void apply_device_action(const std::vector<std::string> &args, T &&action); - void execute_commands(const char *exename); - void display_help(const char *exename); - void output_single_softlist(FILE *out, software_list_device &swlist); + void execute_commands(std::string_view exename); + void display_help(std::string_view exename); + void output_single_softlist(std::ostream &out, software_list_device &swlist); void start_execution(mame_machine_manager *manager, const std::vector<std::string> &args); static const info_command_struct *find_command(const std::string &s); diff --git a/src/frontend/mame/info.cpp b/src/frontend/mame/infoxml.cpp index b06453c0958..46978109432 100644 --- a/src/frontend/mame/info.cpp +++ b/src/frontend/mame/infoxml.cpp @@ -9,249 +9,401 @@ ***************************************************************************/ #include "emu.h" +#include "infoxml.h" -#include "info.h" #include "mameopts.h" +// devices #include "machine/ram.h" #include "sound/samples.h" +// emu #include "config.h" #include "drivenum.h" +#include "main.h" #include "romload.h" #include "screen.h" #include "softlist_dev.h" #include "speaker.h" +// lib/util +#include "corestr.h" #include "xmlfile.h" -#include <ctype.h> +#include <algorithm> +#include <cctype> #include <cstring> -#include <unordered_set> -#include <queue> #include <future> +#include <locale> +#include <queue> +#include <sstream> +#include <type_traits> +#include <unordered_set> +#include <utility> #define XML_ROOT "mame" #define XML_TOP "machine" +namespace { + //************************************************************************** // ANONYMOUS NAMESPACE PROTOTYPES //************************************************************************** -namespace +class device_type_compare +{ +public: + bool operator()(const std::add_pointer_t<device_type> &lhs, const std::add_pointer_t<device_type> &rhs) const; +}; + + +class device_filter { - class device_type_compare +public: + device_filter(const std::function<bool(const char *shortname, bool &done)> &callback) + : m_callback(callback) + , m_done(false) { - public: - bool operator()(const std::add_pointer_t<device_type> &lhs, const std::add_pointer_t<device_type> &rhs) const; - }; + } + + // methods + bool filter(const char *shortname); - typedef std::set<std::add_pointer_t<device_type>, device_type_compare> device_type_set; - - std::string normalize_string(const char *string); - - // internal helper - void output_header(std::ostream &out, bool dtd); - void output_footer(std::ostream &out); - - void output_one(std::ostream &out, driver_enumerator &drivlist, const game_driver &driver, device_type_set *devtypes); - void output_sampleof(std::ostream &out, device_t &device); - void output_bios(std::ostream &out, device_t const &device); - void output_rom(std::ostream &out, driver_enumerator *drivlist, const game_driver *driver, device_t &device); - void output_device_refs(std::ostream &out, device_t &root); - void output_sample(std::ostream &out, device_t &device); - void output_chips(std::ostream &out, device_t &device, const char *root_tag); - void output_display(std::ostream &out, device_t &device, machine_flags::type const *flags, const char *root_tag); - void output_sound(std::ostream &out, device_t &device); - void output_ioport_condition(std::ostream &out, const ioport_condition &condition, unsigned indent); - void output_input(std::ostream &out, const ioport_list &portlist); - void output_switches(std::ostream &out, const ioport_list &portlist, const char *root_tag, int type, const char *outertag, const char *loctag, const char *innertag); - void output_ports(std::ostream &out, const ioport_list &portlist); - void output_adjusters(std::ostream &out, const ioport_list &portlist); - void output_driver(std::ostream &out, game_driver const &driver, device_t::feature_type unemulated, device_t::feature_type imperfect); - void output_features(std::ostream &out, device_type type, device_t::feature_type unemulated, device_t::feature_type imperfect); - void output_images(std::ostream &out, device_t &device, const char *root_tag); - void output_slots(std::ostream &out, machine_config &config, device_t &device, const char *root_tag, device_type_set *devtypes); - void output_software_list(std::ostream &out, device_t &root); - void output_ramoptions(std::ostream &out, device_t &root); - - void output_one_device(std::ostream &out, machine_config &config, device_t &device, const char *devtag); - void output_devices(std::ostream &out, emu_options &lookup_options, device_type_set const *filter); - - const char *get_merge_name(driver_enumerator &drivlist, const game_driver &driver, util::hash_collection const &romhashes); + // accessors + bool done() const { return m_done; } + +private: + const std::function<bool(const char *shortname, bool &done)> & m_callback; + bool m_done; }; +class filtered_driver_enumerator +{ +public: + filtered_driver_enumerator(driver_enumerator &drivlist, device_filter &devfilter) + : m_drivlist(drivlist) + , m_devfilter(devfilter) + , m_done(false) + { + } + + // methods + std::vector<std::reference_wrapper<const game_driver>> next(int count); + + // accessors + bool done() const { return m_done || m_devfilter.done(); } + +private: + driver_enumerator & m_drivlist; + device_filter & m_devfilter; + bool m_done; +}; + + +using device_type_set = std::set<std::add_pointer_t<device_type>, device_type_compare>; +using device_type_vector = std::vector<std::add_pointer_t<device_type> >; + + +struct prepared_info +{ + prepared_info() = default; + prepared_info(const prepared_info &) = delete; + prepared_info(prepared_info &&) = default; +#if defined(_CPPLIB_VER) && defined(_MSVC_STL_VERSION) + // MSVCPRT currently requires default-constructible std::future promise types to be assignable + // remove this workaround when that's fixed + prepared_info &operator=(const prepared_info &) = default; +#else + prepared_info &operator=(const prepared_info &) = delete; +#endif + + std::string m_xml_snippet; + device_type_set m_dev_set; +}; + + +// internal helper +void output_header(std::ostream &out, bool dtd); +void output_footer(std::ostream &out); + +void output_one(std::ostream &out, driver_enumerator &drivlist, const game_driver &driver, device_type_set *devtypes); +void output_sampleof(std::ostream &out, device_t &device); +void output_bios(std::ostream &out, device_t const &device); +void output_rom(std::ostream &out, machine_config &config, driver_list const *drivlist, const game_driver *driver, device_t &device); +void output_device_refs(std::ostream &out, device_t &root); +void output_sample(std::ostream &out, device_t &device); +void output_chips(std::ostream &out, device_t &device, const char *root_tag); +void output_display(std::ostream &out, device_t &device, machine_flags::type const *flags, const char *root_tag); +void output_sound(std::ostream &out, device_t &device); +void output_ioport_condition(std::ostream &out, const ioport_condition &condition, unsigned indent); +void output_input(std::ostream &out, const ioport_list &portlist); +void output_switches(std::ostream &out, const ioport_list &portlist, const char *root_tag, int type, const char *outertag, const char *loctag, const char *innertag); +void output_ports(std::ostream &out, const ioport_list &portlist); +void output_adjusters(std::ostream &out, const ioport_list &portlist); +void output_driver(std::ostream &out, game_driver const &driver, device_t::flags_type flags, device_t::feature_type unemulated, device_t::feature_type imperfect); +void output_features(std::ostream &out, device_type type, device_t::feature_type unemulated, device_t::feature_type imperfect); +void output_images(std::ostream &out, device_t &device, const char *root_tag); +void output_slots(std::ostream &out, machine_config &config, device_t &device, const char *root_tag, device_type_set *devtypes); +void output_software_lists(std::ostream &out, device_t &root, const char *root_tag); +void output_ramoptions(std::ostream &out, device_t &root); + +void output_one_device(std::ostream &out, machine_config &config, device_t &device, const char *devtag, device_type_set *devtypes); +void output_devices(std::ostream &out, emu_options &lookup_options, device_type_set *filter); + +char const *get_merge_name(driver_list const &drivlist, game_driver const &driver, util::hash_collection const &romhashes); +char const *get_merge_name(machine_config &config, device_t const &device, util::hash_collection const &romhashes); +char const *get_merge_name(tiny_rom_entry const *roms, util::hash_collection const &romhashes); + + //************************************************************************** // GLOBAL VARIABLES //************************************************************************** // DTD string describing the data -static const char s_dtd_string[] = -"<!DOCTYPE __XML_ROOT__ [\n" -"<!ELEMENT __XML_ROOT__ (__XML_TOP__+)>\n" -"\t<!ATTLIST __XML_ROOT__ build CDATA #IMPLIED>\n" -"\t<!ATTLIST __XML_ROOT__ debug (yes|no) \"no\">\n" -"\t<!ATTLIST __XML_ROOT__ mameconfig CDATA #REQUIRED>\n" -"\t<!ELEMENT __XML_TOP__ (description, year?, manufacturer?, biosset*, rom*, disk*, device_ref*, sample*, chip*, display*, sound?, input?, dipswitch*, configuration*, port*, adjuster*, driver?, feature*, device*, slot*, softwarelist*, ramoption*)>\n" -"\t\t<!ATTLIST __XML_TOP__ name CDATA #REQUIRED>\n" -"\t\t<!ATTLIST __XML_TOP__ sourcefile CDATA #IMPLIED>\n" -"\t\t<!ATTLIST __XML_TOP__ isbios (yes|no) \"no\">\n" -"\t\t<!ATTLIST __XML_TOP__ isdevice (yes|no) \"no\">\n" -"\t\t<!ATTLIST __XML_TOP__ ismechanical (yes|no) \"no\">\n" -"\t\t<!ATTLIST __XML_TOP__ runnable (yes|no) \"yes\">\n" -"\t\t<!ATTLIST __XML_TOP__ cloneof CDATA #IMPLIED>\n" -"\t\t<!ATTLIST __XML_TOP__ romof CDATA #IMPLIED>\n" -"\t\t<!ATTLIST __XML_TOP__ sampleof CDATA #IMPLIED>\n" -"\t\t<!ELEMENT description (#PCDATA)>\n" -"\t\t<!ELEMENT year (#PCDATA)>\n" -"\t\t<!ELEMENT manufacturer (#PCDATA)>\n" -"\t\t<!ELEMENT biosset EMPTY>\n" -"\t\t\t<!ATTLIST biosset name CDATA #REQUIRED>\n" -"\t\t\t<!ATTLIST biosset description CDATA #REQUIRED>\n" -"\t\t\t<!ATTLIST biosset default (yes|no) \"no\">\n" -"\t\t<!ELEMENT rom EMPTY>\n" -"\t\t\t<!ATTLIST rom name CDATA #REQUIRED>\n" -"\t\t\t<!ATTLIST rom bios CDATA #IMPLIED>\n" -"\t\t\t<!ATTLIST rom size CDATA #REQUIRED>\n" -"\t\t\t<!ATTLIST rom crc CDATA #IMPLIED>\n" -"\t\t\t<!ATTLIST rom sha1 CDATA #IMPLIED>\n" -"\t\t\t<!ATTLIST rom merge CDATA #IMPLIED>\n" -"\t\t\t<!ATTLIST rom region CDATA #IMPLIED>\n" -"\t\t\t<!ATTLIST rom offset CDATA #IMPLIED>\n" -"\t\t\t<!ATTLIST rom status (baddump|nodump|good) \"good\">\n" -"\t\t\t<!ATTLIST rom optional (yes|no) \"no\">\n" -"\t\t<!ELEMENT disk EMPTY>\n" -"\t\t\t<!ATTLIST disk name CDATA #REQUIRED>\n" -"\t\t\t<!ATTLIST disk sha1 CDATA #IMPLIED>\n" -"\t\t\t<!ATTLIST disk merge CDATA #IMPLIED>\n" -"\t\t\t<!ATTLIST disk region CDATA #IMPLIED>\n" -"\t\t\t<!ATTLIST disk index CDATA #IMPLIED>\n" -"\t\t\t<!ATTLIST disk writable (yes|no) \"no\">\n" -"\t\t\t<!ATTLIST disk status (baddump|nodump|good) \"good\">\n" -"\t\t\t<!ATTLIST disk optional (yes|no) \"no\">\n" -"\t\t<!ELEMENT device_ref EMPTY>\n" -"\t\t\t<!ATTLIST device_ref name CDATA #REQUIRED>\n" -"\t\t<!ELEMENT sample EMPTY>\n" -"\t\t\t<!ATTLIST sample name CDATA #REQUIRED>\n" -"\t\t<!ELEMENT chip EMPTY>\n" -"\t\t\t<!ATTLIST chip name CDATA #REQUIRED>\n" -"\t\t\t<!ATTLIST chip tag CDATA #IMPLIED>\n" -"\t\t\t<!ATTLIST chip type (cpu|audio) #REQUIRED>\n" -"\t\t\t<!ATTLIST chip clock CDATA #IMPLIED>\n" -"\t\t<!ELEMENT display EMPTY>\n" -"\t\t\t<!ATTLIST display tag CDATA #IMPLIED>\n" -"\t\t\t<!ATTLIST display type (raster|vector|lcd|svg|unknown) #REQUIRED>\n" -"\t\t\t<!ATTLIST display rotate (0|90|180|270) #IMPLIED>\n" -"\t\t\t<!ATTLIST display flipx (yes|no) \"no\">\n" -"\t\t\t<!ATTLIST display width CDATA #IMPLIED>\n" -"\t\t\t<!ATTLIST display height CDATA #IMPLIED>\n" -"\t\t\t<!ATTLIST display refresh CDATA #REQUIRED>\n" -"\t\t\t<!ATTLIST display pixclock CDATA #IMPLIED>\n" -"\t\t\t<!ATTLIST display htotal CDATA #IMPLIED>\n" -"\t\t\t<!ATTLIST display hbend CDATA #IMPLIED>\n" -"\t\t\t<!ATTLIST display hbstart CDATA #IMPLIED>\n" -"\t\t\t<!ATTLIST display vtotal CDATA #IMPLIED>\n" -"\t\t\t<!ATTLIST display vbend CDATA #IMPLIED>\n" -"\t\t\t<!ATTLIST display vbstart CDATA #IMPLIED>\n" -"\t\t<!ELEMENT sound EMPTY>\n" -"\t\t\t<!ATTLIST sound channels CDATA #REQUIRED>\n" -"\t\t<!ELEMENT condition EMPTY>\n" -"\t\t\t<!ATTLIST condition tag CDATA #REQUIRED>\n" -"\t\t\t<!ATTLIST condition mask CDATA #REQUIRED>\n" -"\t\t\t<!ATTLIST condition relation (eq|ne|gt|le|lt|ge) #REQUIRED>\n" -"\t\t\t<!ATTLIST condition value CDATA #REQUIRED>\n" -"\t\t<!ELEMENT input (control*)>\n" -"\t\t\t<!ATTLIST input service (yes|no) \"no\">\n" -"\t\t\t<!ATTLIST input tilt (yes|no) \"no\">\n" -"\t\t\t<!ATTLIST input players CDATA #REQUIRED>\n" -"\t\t\t<!ATTLIST input coins CDATA #IMPLIED>\n" -"\t\t\t<!ELEMENT control EMPTY>\n" -"\t\t\t\t<!ATTLIST control type CDATA #REQUIRED>\n" -"\t\t\t\t<!ATTLIST control player CDATA #IMPLIED>\n" -"\t\t\t\t<!ATTLIST control buttons CDATA #IMPLIED>\n" -"\t\t\t\t<!ATTLIST control reqbuttons CDATA #IMPLIED>\n" -"\t\t\t\t<!ATTLIST control minimum CDATA #IMPLIED>\n" -"\t\t\t\t<!ATTLIST control maximum CDATA #IMPLIED>\n" -"\t\t\t\t<!ATTLIST control sensitivity CDATA #IMPLIED>\n" -"\t\t\t\t<!ATTLIST control keydelta CDATA #IMPLIED>\n" -"\t\t\t\t<!ATTLIST control reverse (yes|no) \"no\">\n" -"\t\t\t\t<!ATTLIST control ways CDATA #IMPLIED>\n" -"\t\t\t\t<!ATTLIST control ways2 CDATA #IMPLIED>\n" -"\t\t\t\t<!ATTLIST control ways3 CDATA #IMPLIED>\n" -"\t\t<!ELEMENT dipswitch (condition?, diplocation*, dipvalue*)>\n" -"\t\t\t<!ATTLIST dipswitch name CDATA #REQUIRED>\n" -"\t\t\t<!ATTLIST dipswitch tag CDATA #REQUIRED>\n" -"\t\t\t<!ATTLIST dipswitch mask CDATA #REQUIRED>\n" -"\t\t\t<!ELEMENT diplocation EMPTY>\n" -"\t\t\t\t<!ATTLIST diplocation name CDATA #REQUIRED>\n" -"\t\t\t\t<!ATTLIST diplocation number CDATA #REQUIRED>\n" -"\t\t\t\t<!ATTLIST diplocation inverted (yes|no) \"no\">\n" -"\t\t\t<!ELEMENT dipvalue (condition?)>\n" -"\t\t\t\t<!ATTLIST dipvalue name CDATA #REQUIRED>\n" -"\t\t\t\t<!ATTLIST dipvalue value CDATA #REQUIRED>\n" -"\t\t\t\t<!ATTLIST dipvalue default (yes|no) \"no\">\n" -"\t\t<!ELEMENT configuration (condition?, conflocation*, confsetting*)>\n" -"\t\t\t<!ATTLIST configuration name CDATA #REQUIRED>\n" -"\t\t\t<!ATTLIST configuration tag CDATA #REQUIRED>\n" -"\t\t\t<!ATTLIST configuration mask CDATA #REQUIRED>\n" -"\t\t\t<!ELEMENT conflocation EMPTY>\n" -"\t\t\t\t<!ATTLIST conflocation name CDATA #REQUIRED>\n" -"\t\t\t\t<!ATTLIST conflocation number CDATA #REQUIRED>\n" -"\t\t\t\t<!ATTLIST conflocation inverted (yes|no) \"no\">\n" -"\t\t\t<!ELEMENT confsetting (condition?)>\n" -"\t\t\t\t<!ATTLIST confsetting name CDATA #REQUIRED>\n" -"\t\t\t\t<!ATTLIST confsetting value CDATA #REQUIRED>\n" -"\t\t\t\t<!ATTLIST confsetting default (yes|no) \"no\">\n" -"\t\t<!ELEMENT port (analog*)>\n" -"\t\t\t<!ATTLIST port tag CDATA #REQUIRED>\n" -"\t\t\t<!ELEMENT analog EMPTY>\n" -"\t\t\t\t<!ATTLIST analog mask CDATA #REQUIRED>\n" -"\t\t<!ELEMENT adjuster (condition?)>\n" -"\t\t\t<!ATTLIST adjuster name CDATA #REQUIRED>\n" -"\t\t\t<!ATTLIST adjuster default CDATA #REQUIRED>\n" -"\t\t<!ELEMENT driver EMPTY>\n" -"\t\t\t<!ATTLIST driver status (good|imperfect|preliminary) #REQUIRED>\n" -"\t\t\t<!ATTLIST driver emulation (good|imperfect|preliminary) #REQUIRED>\n" -"\t\t\t<!ATTLIST driver cocktail (good|imperfect|preliminary) #IMPLIED>\n" -"\t\t\t<!ATTLIST driver savestate (supported|unsupported) #REQUIRED>\n" -"\t\t<!ELEMENT feature EMPTY>\n" -"\t\t\t<!ATTLIST feature type (protection|timing|graphics|palette|sound|capture|camera|microphone|controls|keyboard|mouse|media|disk|printer|tape|punch|drum|rom|comms|lan|wan) #REQUIRED>\n" -"\t\t\t<!ATTLIST feature status (unemulated|imperfect) #IMPLIED>\n" -"\t\t\t<!ATTLIST feature overall (unemulated|imperfect) #IMPLIED>\n" -"\t\t<!ELEMENT device (instance?, extension*)>\n" -"\t\t\t<!ATTLIST device type CDATA #REQUIRED>\n" -"\t\t\t<!ATTLIST device tag CDATA #IMPLIED>\n" -"\t\t\t<!ATTLIST device fixed_image CDATA #IMPLIED>\n" -"\t\t\t<!ATTLIST device mandatory CDATA #IMPLIED>\n" -"\t\t\t<!ATTLIST device interface CDATA #IMPLIED>\n" -"\t\t\t<!ELEMENT instance EMPTY>\n" -"\t\t\t\t<!ATTLIST instance name CDATA #REQUIRED>\n" -"\t\t\t\t<!ATTLIST instance briefname CDATA #REQUIRED>\n" -"\t\t\t<!ELEMENT extension EMPTY>\n" -"\t\t\t\t<!ATTLIST extension name CDATA #REQUIRED>\n" -"\t\t<!ELEMENT slot (slotoption*)>\n" -"\t\t\t<!ATTLIST slot name CDATA #REQUIRED>\n" -"\t\t\t<!ELEMENT slotoption EMPTY>\n" -"\t\t\t\t<!ATTLIST slotoption name CDATA #REQUIRED>\n" -"\t\t\t\t<!ATTLIST slotoption devname CDATA #REQUIRED>\n" -"\t\t\t\t<!ATTLIST slotoption default (yes|no) \"no\">\n" -"\t\t<!ELEMENT softwarelist EMPTY>\n" -"\t\t\t<!ATTLIST softwarelist name CDATA #REQUIRED>\n" -"\t\t\t<!ATTLIST softwarelist status (original|compatible) #REQUIRED>\n" -"\t\t\t<!ATTLIST softwarelist filter CDATA #IMPLIED>\n" -"\t\t<!ELEMENT ramoption (#PCDATA)>\n" -"\t\t\t<!ATTLIST ramoption name CDATA #REQUIRED>\n" -"\t\t\t<!ATTLIST ramoption default CDATA #IMPLIED>\n" -"]>"; +constexpr char f_dtd_string[] = + "<!DOCTYPE __XML_ROOT__ [\n" + "<!ELEMENT __XML_ROOT__ (__XML_TOP__+)>\n" + "\t<!ATTLIST __XML_ROOT__ build CDATA #IMPLIED>\n" + "\t<!ATTLIST __XML_ROOT__ debug (yes|no) \"no\">\n" + "\t<!ATTLIST __XML_ROOT__ mameconfig CDATA #REQUIRED>\n" + "\t<!ELEMENT __XML_TOP__ (description, year?, manufacturer?, biosset*, rom*, disk*, device_ref*, sample*, chip*, display*, sound?, input?, dipswitch*, configuration*, port*, adjuster*, driver?, feature*, device*, slot*, softwarelist*, ramoption*)>\n" + "\t\t<!ATTLIST __XML_TOP__ name CDATA #REQUIRED>\n" + "\t\t<!ATTLIST __XML_TOP__ sourcefile CDATA #IMPLIED>\n" + "\t\t<!ATTLIST __XML_TOP__ isbios (yes|no) \"no\">\n" + "\t\t<!ATTLIST __XML_TOP__ isdevice (yes|no) \"no\">\n" + "\t\t<!ATTLIST __XML_TOP__ ismechanical (yes|no) \"no\">\n" + "\t\t<!ATTLIST __XML_TOP__ runnable (yes|no) \"yes\">\n" + "\t\t<!ATTLIST __XML_TOP__ cloneof CDATA #IMPLIED>\n" + "\t\t<!ATTLIST __XML_TOP__ romof CDATA #IMPLIED>\n" + "\t\t<!ATTLIST __XML_TOP__ sampleof CDATA #IMPLIED>\n" + "\t\t<!ELEMENT description (#PCDATA)>\n" + "\t\t<!ELEMENT year (#PCDATA)>\n" + "\t\t<!ELEMENT manufacturer (#PCDATA)>\n" + "\t\t<!ELEMENT biosset EMPTY>\n" + "\t\t\t<!ATTLIST biosset name CDATA #REQUIRED>\n" + "\t\t\t<!ATTLIST biosset description CDATA #REQUIRED>\n" + "\t\t\t<!ATTLIST biosset default (yes|no) \"no\">\n" + "\t\t<!ELEMENT rom EMPTY>\n" + "\t\t\t<!ATTLIST rom name CDATA #REQUIRED>\n" + "\t\t\t<!ATTLIST rom bios CDATA #IMPLIED>\n" + "\t\t\t<!ATTLIST rom size CDATA #REQUIRED>\n" + "\t\t\t<!ATTLIST rom crc CDATA #IMPLIED>\n" + "\t\t\t<!ATTLIST rom sha1 CDATA #IMPLIED>\n" + "\t\t\t<!ATTLIST rom merge CDATA #IMPLIED>\n" + "\t\t\t<!ATTLIST rom region CDATA #IMPLIED>\n" + "\t\t\t<!ATTLIST rom offset CDATA #IMPLIED>\n" + "\t\t\t<!ATTLIST rom status (baddump|nodump|good) \"good\">\n" + "\t\t\t<!ATTLIST rom optional (yes|no) \"no\">\n" + "\t\t<!ELEMENT disk EMPTY>\n" + "\t\t\t<!ATTLIST disk name CDATA #REQUIRED>\n" + "\t\t\t<!ATTLIST disk sha1 CDATA #IMPLIED>\n" + "\t\t\t<!ATTLIST disk merge CDATA #IMPLIED>\n" + "\t\t\t<!ATTLIST disk region CDATA #IMPLIED>\n" + "\t\t\t<!ATTLIST disk index CDATA #IMPLIED>\n" + "\t\t\t<!ATTLIST disk writable (yes|no) \"no\">\n" + "\t\t\t<!ATTLIST disk status (baddump|nodump|good) \"good\">\n" + "\t\t\t<!ATTLIST disk optional (yes|no) \"no\">\n" + "\t\t<!ELEMENT device_ref EMPTY>\n" + "\t\t\t<!ATTLIST device_ref name CDATA #REQUIRED>\n" + "\t\t<!ELEMENT sample EMPTY>\n" + "\t\t\t<!ATTLIST sample name CDATA #REQUIRED>\n" + "\t\t<!ELEMENT chip EMPTY>\n" + "\t\t\t<!ATTLIST chip name CDATA #REQUIRED>\n" + "\t\t\t<!ATTLIST chip tag CDATA #IMPLIED>\n" + "\t\t\t<!ATTLIST chip type (cpu|audio) #REQUIRED>\n" + "\t\t\t<!ATTLIST chip clock CDATA #IMPLIED>\n" + "\t\t<!ELEMENT display EMPTY>\n" + "\t\t\t<!ATTLIST display tag CDATA #IMPLIED>\n" + "\t\t\t<!ATTLIST display type (raster|vector|lcd|svg|unknown) #REQUIRED>\n" + "\t\t\t<!ATTLIST display rotate (0|90|180|270) #IMPLIED>\n" + "\t\t\t<!ATTLIST display flipx (yes|no) \"no\">\n" + "\t\t\t<!ATTLIST display width CDATA #IMPLIED>\n" + "\t\t\t<!ATTLIST display height CDATA #IMPLIED>\n" + "\t\t\t<!ATTLIST display refresh CDATA #REQUIRED>\n" + "\t\t\t<!ATTLIST display pixclock CDATA #IMPLIED>\n" + "\t\t\t<!ATTLIST display htotal CDATA #IMPLIED>\n" + "\t\t\t<!ATTLIST display hbend CDATA #IMPLIED>\n" + "\t\t\t<!ATTLIST display hbstart CDATA #IMPLIED>\n" + "\t\t\t<!ATTLIST display vtotal CDATA #IMPLIED>\n" + "\t\t\t<!ATTLIST display vbend CDATA #IMPLIED>\n" + "\t\t\t<!ATTLIST display vbstart CDATA #IMPLIED>\n" + "\t\t<!ELEMENT sound EMPTY>\n" + "\t\t\t<!ATTLIST sound channels CDATA #REQUIRED>\n" + "\t\t<!ELEMENT condition EMPTY>\n" + "\t\t\t<!ATTLIST condition tag CDATA #REQUIRED>\n" + "\t\t\t<!ATTLIST condition mask CDATA #REQUIRED>\n" + "\t\t\t<!ATTLIST condition relation (eq|ne|gt|le|lt|ge) #REQUIRED>\n" + "\t\t\t<!ATTLIST condition value CDATA #REQUIRED>\n" + "\t\t<!ELEMENT input (control*)>\n" + "\t\t\t<!ATTLIST input service (yes|no) \"no\">\n" + "\t\t\t<!ATTLIST input tilt (yes|no) \"no\">\n" + "\t\t\t<!ATTLIST input players CDATA #REQUIRED>\n" + "\t\t\t<!ATTLIST input coins CDATA #IMPLIED>\n" + "\t\t\t<!ELEMENT control EMPTY>\n" + "\t\t\t\t<!ATTLIST control type CDATA #REQUIRED>\n" + "\t\t\t\t<!ATTLIST control player CDATA #IMPLIED>\n" + "\t\t\t\t<!ATTLIST control buttons CDATA #IMPLIED>\n" + "\t\t\t\t<!ATTLIST control reqbuttons CDATA #IMPLIED>\n" + "\t\t\t\t<!ATTLIST control minimum CDATA #IMPLIED>\n" + "\t\t\t\t<!ATTLIST control maximum CDATA #IMPLIED>\n" + "\t\t\t\t<!ATTLIST control sensitivity CDATA #IMPLIED>\n" + "\t\t\t\t<!ATTLIST control keydelta CDATA #IMPLIED>\n" + "\t\t\t\t<!ATTLIST control reverse (yes|no) \"no\">\n" + "\t\t\t\t<!ATTLIST control ways CDATA #IMPLIED>\n" + "\t\t\t\t<!ATTLIST control ways2 CDATA #IMPLIED>\n" + "\t\t\t\t<!ATTLIST control ways3 CDATA #IMPLIED>\n" + "\t\t<!ELEMENT dipswitch (condition?, diplocation*, dipvalue*)>\n" + "\t\t\t<!ATTLIST dipswitch name CDATA #REQUIRED>\n" + "\t\t\t<!ATTLIST dipswitch tag CDATA #REQUIRED>\n" + "\t\t\t<!ATTLIST dipswitch mask CDATA #REQUIRED>\n" + "\t\t\t<!ELEMENT diplocation EMPTY>\n" + "\t\t\t\t<!ATTLIST diplocation name CDATA #REQUIRED>\n" + "\t\t\t\t<!ATTLIST diplocation number CDATA #REQUIRED>\n" + "\t\t\t\t<!ATTLIST diplocation inverted (yes|no) \"no\">\n" + "\t\t\t<!ELEMENT dipvalue (condition?)>\n" + "\t\t\t\t<!ATTLIST dipvalue name CDATA #REQUIRED>\n" + "\t\t\t\t<!ATTLIST dipvalue value CDATA #REQUIRED>\n" + "\t\t\t\t<!ATTLIST dipvalue default (yes|no) \"no\">\n" + "\t\t<!ELEMENT configuration (condition?, conflocation*, confsetting*)>\n" + "\t\t\t<!ATTLIST configuration name CDATA #REQUIRED>\n" + "\t\t\t<!ATTLIST configuration tag CDATA #REQUIRED>\n" + "\t\t\t<!ATTLIST configuration mask CDATA #REQUIRED>\n" + "\t\t\t<!ELEMENT conflocation EMPTY>\n" + "\t\t\t\t<!ATTLIST conflocation name CDATA #REQUIRED>\n" + "\t\t\t\t<!ATTLIST conflocation number CDATA #REQUIRED>\n" + "\t\t\t\t<!ATTLIST conflocation inverted (yes|no) \"no\">\n" + "\t\t\t<!ELEMENT confsetting (condition?)>\n" + "\t\t\t\t<!ATTLIST confsetting name CDATA #REQUIRED>\n" + "\t\t\t\t<!ATTLIST confsetting value CDATA #REQUIRED>\n" + "\t\t\t\t<!ATTLIST confsetting default (yes|no) \"no\">\n" + "\t\t<!ELEMENT port (analog*)>\n" + "\t\t\t<!ATTLIST port tag CDATA #REQUIRED>\n" + "\t\t\t<!ELEMENT analog EMPTY>\n" + "\t\t\t\t<!ATTLIST analog mask CDATA #REQUIRED>\n" + "\t\t<!ELEMENT adjuster (condition?)>\n" + "\t\t\t<!ATTLIST adjuster name CDATA #REQUIRED>\n" + "\t\t\t<!ATTLIST adjuster default CDATA #REQUIRED>\n" + "\t\t<!ELEMENT driver EMPTY>\n" + "\t\t\t<!ATTLIST driver status (good|imperfect|preliminary) #REQUIRED>\n" + "\t\t\t<!ATTLIST driver emulation (good|imperfect|preliminary) #REQUIRED>\n" + "\t\t\t<!ATTLIST driver cocktail (good|imperfect|preliminary) #IMPLIED>\n" + "\t\t\t<!ATTLIST driver savestate (supported|unsupported) #REQUIRED>\n" + "\t\t\t<!ATTLIST driver requiresartwork (yes|no) \"no\">\n" + "\t\t\t<!ATTLIST driver unofficial (yes|no) \"no\">\n" + "\t\t\t<!ATTLIST driver nosoundhardware (yes|no) \"no\">\n" + "\t\t\t<!ATTLIST driver incomplete (yes|no) \"no\">\n" + "\t\t<!ELEMENT feature EMPTY>\n" + "\t\t\t<!ATTLIST feature type (protection|timing|graphics|palette|sound|capture|camera|microphone|controls|keyboard|mouse|media|disk|printer|tape|punch|drum|rom|comms|lan|wan) #REQUIRED>\n" + "\t\t\t<!ATTLIST feature status (unemulated|imperfect) #IMPLIED>\n" + "\t\t\t<!ATTLIST feature overall (unemulated|imperfect) #IMPLIED>\n" + "\t\t<!ELEMENT device (instance?, extension*)>\n" + "\t\t\t<!ATTLIST device type CDATA #REQUIRED>\n" + "\t\t\t<!ATTLIST device tag CDATA #IMPLIED>\n" + "\t\t\t<!ATTLIST device fixed_image CDATA #IMPLIED>\n" + "\t\t\t<!ATTLIST device mandatory CDATA #IMPLIED>\n" + "\t\t\t<!ATTLIST device interface CDATA #IMPLIED>\n" + "\t\t\t<!ELEMENT instance EMPTY>\n" + "\t\t\t\t<!ATTLIST instance name CDATA #REQUIRED>\n" + "\t\t\t\t<!ATTLIST instance briefname CDATA #REQUIRED>\n" + "\t\t\t<!ELEMENT extension EMPTY>\n" + "\t\t\t\t<!ATTLIST extension name CDATA #REQUIRED>\n" + "\t\t<!ELEMENT slot (slotoption*)>\n" + "\t\t\t<!ATTLIST slot name CDATA #REQUIRED>\n" + "\t\t\t<!ELEMENT slotoption EMPTY>\n" + "\t\t\t\t<!ATTLIST slotoption name CDATA #REQUIRED>\n" + "\t\t\t\t<!ATTLIST slotoption devname CDATA #REQUIRED>\n" + "\t\t\t\t<!ATTLIST slotoption default (yes|no) \"no\">\n" + "\t\t<!ELEMENT softwarelist EMPTY>\n" + "\t\t\t<!ATTLIST softwarelist tag CDATA #REQUIRED>\n" + "\t\t\t<!ATTLIST softwarelist name CDATA #REQUIRED>\n" + "\t\t\t<!ATTLIST softwarelist status (original|compatible) #REQUIRED>\n" + "\t\t\t<!ATTLIST softwarelist filter CDATA #IMPLIED>\n" + "\t\t<!ELEMENT ramoption (#PCDATA)>\n" + "\t\t\t<!ATTLIST ramoption name CDATA #REQUIRED>\n" + "\t\t\t<!ATTLIST ramoption default CDATA #IMPLIED>\n" + "]>"; + + +// XML feature names +constexpr std::pair<device_t::feature_type, char const *> f_feature_names[] = { + { device_t::feature::PROTECTION, "protection" }, + { device_t::feature::TIMING, "timing" }, + { device_t::feature::GRAPHICS, "graphics" }, + { device_t::feature::PALETTE, "palette" }, + { device_t::feature::SOUND, "sound" }, + { device_t::feature::CAPTURE, "capture" }, + { device_t::feature::CAMERA, "camera" }, + { device_t::feature::MICROPHONE, "microphone" }, + { device_t::feature::CONTROLS, "controls" }, + { device_t::feature::KEYBOARD, "keyboard" }, + { device_t::feature::MOUSE, "mouse" }, + { device_t::feature::MEDIA, "media" }, + { device_t::feature::DISK, "disk" }, + { device_t::feature::PRINTER, "printer" }, + { device_t::feature::TAPE, "tape" }, + { device_t::feature::PUNCH, "punch" }, + { device_t::feature::DRUM, "drum" }, + { device_t::feature::ROM, "rom" }, + { device_t::feature::COMMS, "comms" }, + { device_t::feature::LAN, "lan" }, + { device_t::feature::WAN, "wan" } }; + +} // anonymous namespace //************************************************************************** // INFO XML CREATOR //************************************************************************** + +//------------------------------------------------- +// feature_name - get XML name for feature +//------------------------------------------------- + +char const *info_xml_creator::feature_name(device_t::feature_type feature) +{ + auto const found = std::lower_bound( + std::begin(f_feature_names), + std::end(f_feature_names), + std::underlying_type_t<device_t::feature_type>(feature), + [] (auto const &a, auto const &b) + { + return std::underlying_type_t<device_t::feature_type>(a.first) < b; + }); + return ((std::end(f_feature_names) != found) && (found->first == feature)) ? found->second : nullptr; +} + + +//------------------------------------------------- +// format_sourcefile - sanitise source file path +//------------------------------------------------- + +std::string info_xml_creator::format_sourcefile(std::string_view path) +{ + using namespace std::literals; + + if (auto prefix(path.rfind("src/mame/"sv)); std::string_view::npos != prefix) + path.remove_prefix(prefix + 9); + else if (auto prefix(path.rfind("src\\mame\\"sv)); std::string_view::npos != prefix) + path.remove_prefix(prefix + 9); + else if (auto prefix(path.rfind("/src/"sv)); std::string_view::npos != prefix) + path.remove_prefix(prefix + 5); + else if (auto prefix(path.rfind("\\src\\"sv)); std::string_view::npos != prefix) + path.remove_prefix(prefix + 5); + else if (path.substr(0, 4) == "src/"sv) + path.remove_prefix(4); + else if (path.substr(0, 4) == "src\\"sv) + path.remove_prefix(4); + + std::string result(path); + std::replace(result.begin(), result.end(), '\\', '/'); + return result; +} + + //------------------------------------------------- // info_xml_creator - constructor //------------------------------------------------- @@ -285,7 +437,7 @@ void info_xml_creator::output(std::ostream &out, const std::vector<std::string> auto it = matched.begin(); for (const std::string &pat : patterns) { - if (!core_strwildcmp(pat.c_str(), shortname)) + if (!core_strwildcmp(pat, shortname)) { // this driver matches the pattern - tell the caller result = true; @@ -294,7 +446,7 @@ void info_xml_creator::output(std::ostream &out, const std::vector<std::string> if (!*it) { *it = true; - if (!core_iswildstr(pat.c_str())) + if (!core_iswildstr(pat)) { exact_matches++; @@ -315,7 +467,7 @@ void info_xml_creator::output(std::ostream &out, const std::vector<std::string> if (iter != matched.end()) { int index = iter - matched.begin(); - throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching machines found for '%s'", patterns[index].c_str()); + throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching machines found for '%s'", patterns[index]); } } } @@ -326,103 +478,116 @@ void info_xml_creator::output(std::ostream &out, const std::vector<std::string> // known (and filtered) machines //------------------------------------------------- -void info_xml_creator::output(std::ostream &out, const std::function<bool(const char *shortname, bool &done)> &filter, bool include_devices) +void info_xml_creator::output(std::ostream &out, const std::function<bool (const char *shortname, bool &done)> &filter, bool include_devices) { - struct prepared_info - { - std::string m_xml_snippet; - device_type_set m_dev_set; - }; - // prepare a driver enumerator and the queue driver_enumerator drivlist(m_lookup_options); - bool drivlist_done = false; - bool filter_done = false; + device_filter devfilter(filter); + filtered_driver_enumerator filtered_drivlist(drivlist, devfilter); bool header_outputted = false; - auto output_header_if_necessary = [this, &header_outputted](std::ostream &out) - { - if (!header_outputted) - { - output_header(out, m_dtd); - header_outputted = true; - } - }; + // essentially a local method to emit the header if necessary + auto const output_header_if_necessary = [this, &header_outputted] (std::ostream &out) + { + if (!header_outputted) + { + output_header(out, m_dtd); + header_outputted = true; + } + }; // only keep a device set when we're asked to track it - std::unique_ptr<device_type_set> devfilter; + std::optional<device_type_set> devset; if (include_devices && filter) - devfilter = std::make_unique<device_type_set>(); + devset.emplace(); - // prepare a queue of futures - std::queue<std::future<prepared_info>> queue; + // prepare a queue of tasks - this is a FIFO queue because of the + // need to be deterministic + std::queue<std::future<prepared_info> > tasks; - // try enumerating drivers and outputting them - while (!queue.empty() || (!drivlist_done && !filter_done)) + // while we want to be deterministic, asynchronous task scheduling is not; so we want to + // track the amount of active tasks so that we can keep on spawning tasks even if we're + // waiting on the task in the front of the queue + std::atomic<unsigned int> active_task_count = 0; + unsigned int const maximum_active_task_count = std::thread::hardware_concurrency() + 10; + unsigned int const maximum_outstanding_task_count = maximum_active_task_count + 20; + + // loop until we're done enumerating drivers, and until there are no outstanding tasks + while (!filtered_drivlist.done() || !tasks.empty()) { - // try populating the queue - while (queue.size() < 20 && !drivlist_done && !filter_done) + // loop until there are as many outstanding tasks as possible (we want to separately cap outstanding + // tasks and active tasks) + while (!filtered_drivlist.done() + && (active_task_count < maximum_active_task_count) + && (tasks.size() < maximum_outstanding_task_count)) { - if (!drivlist.next()) - { - // at this point we are done enumerating through drivlist and it is no - // longer safe to call next(), so record that we're done - drivlist_done = true; - } - else if (!filter || filter(drivlist.driver().name, filter_done)) - { - const game_driver &driver(drivlist.driver()); - std::future<prepared_info> future_pi = std::async(std::launch::async, [&drivlist, &driver, &devfilter] - { - prepared_info result; - std::ostringstream stream; - - output_one(stream, drivlist, driver, devfilter ? &result.m_dev_set : nullptr); - result.m_xml_snippet = stream.str(); - return result; - }); - queue.push(std::move(future_pi)); - } + // we want to launch a task; grab a packet of drivers to process + std::vector<std::reference_wrapper<const game_driver> > drivers = filtered_drivlist.next(20); + if (drivers.empty()) + break; + + // do the dirty work asynchronously + auto task_proc = [&drivlist, drivers = std::move(drivers), collect_devices = bool(devset), &active_task_count] + { + prepared_info result; + std::ostringstream stream; + stream.imbue(std::locale::classic()); + + // output each of the drivers + for (const game_driver &driver : drivers) + output_one(stream, drivlist, driver, collect_devices ? &result.m_dev_set : nullptr); + + // capture the XML snippet + result.m_xml_snippet = std::move(stream).str(); + + // we're done with the task; decrement the counter and return + active_task_count--; + return result; + }; + + // add this task to the queue + active_task_count++; + tasks.emplace(std::async(std::launch::async, std::move(task_proc))); } - // now that we have the queue populated, try grabbing one (assuming that it is not empty) - if (!queue.empty()) + // we've put as many outstanding tasks out as we can; are there any tasks outstanding? + if (!tasks.empty()) { - // wait for the future to complete and get the info - prepared_info pi = queue.front().get(); - queue.pop(); + // wait for the oldest task to complete and get the info, in the spirit of determinism + prepared_info pi = tasks.front().get(); + tasks.pop(); - // emit the XML + // emit whatever XML we accumulated in the task output_header_if_necessary(out); out << pi.m_xml_snippet; - // merge devices into devfilter, if appropriate - if (devfilter) + // merge devices into devset, if appropriate + if (devset) { for (const auto &x : pi.m_dev_set) - devfilter->insert(x); + devset->insert(x); } } } // iterate through the device types if not everything matches a driver - if (devfilter && !filter_done) + if (devset && !devfilter.done()) { for (device_type type : registered_device_types) { - if (!filter || filter(type.shortname(), filter_done)) - devfilter->insert(&type); + if (devfilter.filter(type.shortname())) + devset->insert(&type); - if (filter_done) + if (devfilter.done()) break; } } - // output devices (both devices with roms and slot devices) - if (include_devices && (!devfilter || !devfilter->empty())) + // output devices + if (include_devices && (!devset || !devset->empty())) { output_header_if_necessary(out); - output_devices(out, m_lookup_options, devfilter.get()); + output_devices(out, m_lookup_options, devset ? &*devset : nullptr); } if (header_outputted) @@ -438,31 +603,40 @@ namespace { //------------------------------------------------- -// normalize_string +// device_filter::filter - apply the filter, if +// present //------------------------------------------------- -std::string normalize_string(const char *string) +bool device_filter::filter(const char *shortname) { - std::ostringstream stream; + return !m_done && (!m_callback || m_callback(shortname, m_done)); +} - if (string != nullptr) + +//------------------------------------------------- +// filtered_driver_enumerator::next - take a number +// of game_drivers, while applying filters +//------------------------------------------------- + +std::vector<std::reference_wrapper<const game_driver> > filtered_driver_enumerator::next(int count) +{ + std::vector<std::reference_wrapper<const game_driver> > results; + results.reserve(count); + while (!done() && results.size() < count) { - while (*string) + if (!m_drivlist.next()) { - switch (*string) - { - case '\"': stream << """; break; - case '&': stream << "&"; break; - case '<': stream << "<"; break; - case '>': stream << ">"; break; - default: - stream << *string; - break; - } - ++string; + // at this point we are done enumerating through drivlist and it is no + // longer safe to call next(), so record that we're done + m_done = true; + } + else if (m_devfilter.filter(m_drivlist.driver().name)) + { + const game_driver &driver(m_drivlist.driver()); + results.emplace_back(driver); } } - return stream.str(); + return results; } @@ -477,7 +651,7 @@ void output_header(std::ostream &out, bool dtd) { // output the DTD out << "<?xml version=\"1.0\"?>\n"; - std::string dtd(s_dtd_string); + std::string dtd(f_dtd_string); strreplace(dtd, "__XML_ROOT__", XML_ROOT); strreplace(dtd, "__XML_TOP__", XML_TOP); @@ -485,7 +659,9 @@ void output_header(std::ostream &out, bool dtd) } // top-level tag - out << util::string_format("<%s build=\"%s\" debug=\"" + assert(emulator_info::get_build_version() != nullptr); + util::stream_format(out, + "<%s build=\"%s\" debug=\"" #ifdef MAME_DEBUG "yes" #else @@ -493,8 +669,8 @@ void output_header(std::ostream &out, bool dtd) #endif "\" mameconfig=\"%d\">\n", XML_ROOT, - normalize_string(emulator_info::get_build_version()), - CONFIG_VERSION); + util::xml::normalize_string(emulator_info::get_build_version()), + configuration_manager::CONFIG_VERSION); } @@ -505,7 +681,7 @@ void output_header(std::ostream &out, bool dtd) void output_footer(std::ostream &out) { // close the top level tag - out << util::string_format("</%s>\n", XML_ROOT); + util::stream_format(out, "</%s>\n", XML_ROOT); } @@ -516,22 +692,28 @@ void output_footer(std::ostream &out) void output_one(std::ostream &out, driver_enumerator &drivlist, const game_driver &driver, device_type_set *devtypes) { + using util::xml::normalize_string; + machine_config config(driver, drivlist.options()); - device_iterator iter(config.root_device()); + device_enumerator iter(config.root_device()); // allocate input ports and build overall emulation status ioport_list portlist; - std::string errors; + device_t::flags_type overall_flags(driver.type.emulation_flags()); device_t::feature_type overall_unemulated(driver.type.unemulated_features()); device_t::feature_type overall_imperfect(driver.type.imperfect_features()); - for (device_t &device : iter) { - portlist.append(device, errors); - overall_unemulated |= device.type().unemulated_features(); - overall_imperfect |= device.type().imperfect_features(); + std::ostringstream errors; + for (device_t &device : iter) + { + portlist.append(device, errors); + overall_flags |= device.type().emulation_flags() & ~device_t::flags::NOT_WORKING; + overall_unemulated |= device.type().unemulated_features(); + overall_imperfect |= device.type().imperfect_features(); - if (devtypes && device.owner()) - devtypes->insert(&device.type()); + if (devtypes && device.owner()) + devtypes->insert(&device.type()); + } } // renumber player numbers for controller ports @@ -564,14 +746,10 @@ void output_one(std::ostream &out, driver_enumerator &drivlist, const game_drive } // print the header and the machine name - out << util::string_format("\t<%s name=\"%s\"", XML_TOP, normalize_string(driver.name)); + util::stream_format(out, "\t<%s name=\"%s\"", XML_TOP, normalize_string(driver.name)); - // strip away any path information from the source_file and output it - const char *start = strrchr(driver.type.source(), '/'); - if (!start) - start = strrchr(driver.type.source(), '\\'); - start = start ? (start + 1) : driver.type.source(); - out << util::string_format(" sourcefile=\"%s\"", normalize_string(start)); + // strip away extra path information from the source file and output it + util::stream_format(out, " sourcefile=\"%s\"", normalize_string(info_xml_creator::format_sourcefile(driver.type.source()))); // append bios and runnable flags if (driver.flags & machine_flags::IS_BIOS_ROOT) @@ -582,9 +760,9 @@ void output_one(std::ostream &out, driver_enumerator &drivlist, const game_drive // display clone information int clone_of = drivlist.find(driver.parent); if (clone_of != -1 && !(drivlist.driver(clone_of).flags & machine_flags::IS_BIOS_ROOT)) - out << util::string_format(" cloneof=\"%s\"", normalize_string(drivlist.driver(clone_of).name)); + util::stream_format(out, " cloneof=\"%s\"", normalize_string(drivlist.driver(clone_of).name)); if (clone_of != -1) - out << util::string_format(" romof=\"%s\"", normalize_string(drivlist.driver(clone_of).name)); + util::stream_format(out, " romof=\"%s\"", normalize_string(drivlist.driver(clone_of).name)); // display sample information and close the game tag output_sampleof(out, config.root_device()); @@ -592,19 +770,19 @@ void output_one(std::ostream &out, driver_enumerator &drivlist, const game_drive // output game description if (driver.type.fullname() != nullptr) - out << util::string_format("\t\t<description>%s</description>\n", normalize_string(driver.type.fullname())); + util::stream_format(out, "\t\t<description>%s</description>\n", normalize_string(driver.type.fullname())); // print the year only if is a number or another allowed character (? or +) - if (driver.year != nullptr && strspn(driver.year, "0123456789?+") == strlen(driver.year)) - out << util::string_format("\t\t<year>%s</year>\n", normalize_string(driver.year)); + if (driver.year && strspn(driver.year, "0123456789?+") == strlen(driver.year)) + util::stream_format(out, "\t\t<year>%s</year>\n", normalize_string(driver.year)); // print the manufacturer information if (driver.manufacturer != nullptr) - out << util::string_format("\t\t<manufacturer>%s</manufacturer>\n", normalize_string(driver.manufacturer)); + util::stream_format(out, "\t\t<manufacturer>%s</manufacturer>\n", normalize_string(driver.manufacturer)); // now print various additional information output_bios(out, config.root_device()); - output_rom(out, &drivlist, &driver, config.root_device()); + output_rom(out, config, &drivlist, &driver, config.root_device()); output_device_refs(out, config.root_device()); output_sample(out, config.root_device()); output_chips(out, config.root_device(), ""); @@ -615,15 +793,15 @@ void output_one(std::ostream &out, driver_enumerator &drivlist, const game_drive output_switches(out, portlist, "", IPT_CONFIG, "configuration", "conflocation", "confsetting"); output_ports(out, portlist); output_adjusters(out, portlist); - output_driver(out, driver, overall_unemulated, overall_imperfect); + output_driver(out, driver, overall_flags, overall_unemulated, overall_imperfect); output_features(out, driver.type, overall_unemulated, overall_imperfect); output_images(out, config.root_device(), ""); output_slots(out, config, config.root_device(), "", devtypes); - output_software_list(out, config.root_device()); + output_software_lists(out, config.root_device(), ""); output_ramoptions(out, config.root_device()); // close the topmost tag - out << util::string_format("\t</%s>\n", XML_TOP); + util::stream_format(out, "\t</%s>\n", XML_TOP); } @@ -632,45 +810,58 @@ void output_one(std::ostream &out, driver_enumerator &drivlist, const game_drive // a single device //------------------------------------------------- -void output_one_device(std::ostream &out, machine_config &config, device_t &device, const char *devtag) +void output_one_device(std::ostream &out, machine_config &config, device_t &device, const char *devtag, device_type_set *devtypes) { + using util::xml::normalize_string; + bool has_speaker = false, has_input = false; // check if the device adds speakers to the system - sound_interface_iterator snditer(device); + sound_interface_enumerator snditer(device); if (snditer.first() != nullptr) has_speaker = true; // generate input list and build overall emulation status ioport_list portlist; - std::string errors; device_t::feature_type overall_unemulated(device.type().unemulated_features()); device_t::feature_type overall_imperfect(device.type().imperfect_features()); - for (device_t &dev : device_iterator(device)) { - portlist.append(dev, errors); - overall_unemulated |= dev.type().unemulated_features(); - overall_imperfect |= dev.type().imperfect_features(); + std::ostringstream errors; + for (device_t &dev : device_enumerator(device)) + { + portlist.append(dev, errors); + overall_unemulated |= dev.type().unemulated_features(); + overall_imperfect |= dev.type().imperfect_features(); + + if (devtypes) + devtypes->insert(&device.type()); + } } // check if the device adds player inputs (other than dsw and configs) to the system for (auto &port : portlist) + { for (ioport_field const &field : port.second->fields()) + { if (field.type() >= IPT_START1 && field.type() < IPT_UI_FIRST) { has_input = true; break; } + } + } // start to output info - out << util::string_format("\t<%s name=\"%s\"", XML_TOP, normalize_string(device.shortname())); - std::string src(device.source()); - strreplace(src,"../", ""); - out << util::string_format(" sourcefile=\"%s\" isdevice=\"yes\" runnable=\"no\"", normalize_string(src.c_str())); + util::stream_format(out, "\t<%s name=\"%s\"", XML_TOP, normalize_string(device.shortname())); + util::stream_format(out, " sourcefile=\"%s\" isdevice=\"yes\" runnable=\"no\"", normalize_string(info_xml_creator::format_sourcefile(device.source()))); + auto const parent(device.type().parent_rom_device_type()); + if (parent) + util::stream_format(out, " romof=\"%s\"", normalize_string(parent->shortname())); output_sampleof(out, device); - out << ">\n" << util::string_format("\t\t<description>%s</description>\n", normalize_string(device.name())); + out << ">\n"; + util::stream_format(out, "\t\t<description>%s</description>\n", normalize_string(device.name())); output_bios(out, device); - output_rom(out, nullptr, nullptr, device); + output_rom(out, config, nullptr, nullptr, device); output_device_refs(out, device); if (device.type().type() != typeid(samples_device)) // ignore samples_device itself @@ -687,8 +878,9 @@ void output_one_device(std::ostream &out, machine_config &config, device_t &devi output_adjusters(out, portlist); output_features(out, device.type(), overall_unemulated, overall_imperfect); output_images(out, device, devtag); - output_slots(out, config, device, devtag, nullptr); - out << util::string_format("\t</%s>\n", XML_TOP); + output_slots(out, config, device, devtag, devtypes); + output_software_lists(out, device, devtag); + util::stream_format(out, "\t</%s>\n", XML_TOP); } @@ -697,39 +889,115 @@ void output_one_device(std::ostream &out, machine_config &config, device_t &devi // registered device types //------------------------------------------------- -void output_devices(std::ostream &out, emu_options &lookup_options, device_type_set const *filter) +void output_devices(std::ostream &out, emu_options &lookup_options, device_type_set *filter) { - // get config for empty machine - machine_config config(GAME_NAME(___empty), lookup_options); - - auto const action = [&config, &out] (device_type type) + device_type_set catchup; + auto const action = [&lookup_options, &out, filter, &catchup] (auto &types, auto deref) { - // add it at the root of the machine config - device_t *dev; + // machinery for making output order deterministic and capping outstanding tasks + std::queue<std::future<prepared_info> > tasks; + std::atomic<unsigned int> active_task_count = 0; + unsigned int const maximum_active_task_count = std::thread::hardware_concurrency() + 10; + unsigned int const maximum_outstanding_task_count = maximum_active_task_count + 20; + + // loop until we're done enumerating devices and there are no outstanding tasks + auto it = std::begin(types); + while ((std::end(types) != it) || !tasks.empty()) { - machine_config::token const tok(config.begin_configuration(config.root_device())); - dev = config.device_add("_tmp", type, 0); - } + // look until there are as many outstanding tasks as possible + while ((std::end(types) != it) + && (active_task_count < maximum_active_task_count) + && (tasks.size() < maximum_outstanding_task_count)) + { + device_type_vector batch; + batch.reserve(10); + while ((std::end(types) != it) && (batch.size() < 10)) + batch.emplace_back(deref(*it++)); + if (batch.empty()) + break; - // notify this device and all its subdevices that they are now configured - for (device_t &device : device_iterator(*dev)) - if (!device.configured()) - device.config_complete(); + // do the dirty work asynchronously + auto task_proc = [&active_task_count, &lookup_options, batch = std::move(batch), collect_devices = bool(filter)] + { + // use a single machine configuration and stream for a batch of devices + machine_config config(GAME_NAME(___empty), lookup_options); + prepared_info result; + std::ostringstream stream; + stream.imbue(std::locale::classic()); + for (auto type : batch) + { + // add it at the root of the machine config + device_t *dev; + { + machine_config::token const tok(config.begin_configuration(config.root_device())); + dev = config.device_add("_tmp", *type, 0); + } + + // notify this device and all its subdevices that they are now configured + for (device_t &device : device_enumerator(*dev)) + if (!device.configured()) + device.config_complete(); + + // print details and remove it + output_one_device(stream, config, *dev, dev->tag(), collect_devices ? &result.m_dev_set : nullptr); + machine_config::token const tok(config.begin_configuration(config.root_device())); + config.device_remove("_tmp"); + } + + // capture the XML snippet + result.m_xml_snippet = std::move(stream).str(); + + // we're done with the task; decrement the counter and return + active_task_count--; + return result; + }; + + // add this task to the queue + active_task_count++; + tasks.emplace(std::async(std::launch::async, std::move(task_proc))); + } + + // we've put as many outstanding tasks out as we can; are there any tasks outstanding? + if (!tasks.empty()) + { + // wait for the oldest task to complete and get the info, in the spirit of determinism + prepared_info pi = tasks.front().get(); + tasks.pop(); - // print details and remove it - output_one_device(out, config, *dev, dev->tag()); - machine_config::token const tok(config.begin_configuration(config.root_device())); - config.device_remove("_tmp"); + // emit whatever XML we accumulated in the task + out << pi.m_xml_snippet; + + // recursively collect device types if necessary + if (filter) + { + for (const auto &x : pi.m_dev_set) + { + if (filter->find(x) == filter->end()) + catchup.insert(x); + } + } + } + } }; // run through devices if (filter) { - for (std::add_pointer_t<device_type> type : *filter) action(*type); + action(*filter, [] (auto &x) { return x; }); + + // repeat until no more device types are discovered + while (!catchup.empty()) + { + for (const auto &x : catchup) + filter->insert(x); + device_type_set more = std::move(catchup); + catchup = device_type_set(); + action(more, [] (auto &x) { return x; }); + } } else { - for (device_type type : registered_device_types) action(type); + action(registered_device_types, [] (auto &x) { return &x; }); } } @@ -741,9 +1009,9 @@ void output_devices(std::ostream &out, emu_options &lookup_options, device_type_ void output_device_refs(std::ostream &out, device_t &root) { - for (device_t &device : device_iterator(root)) + for (device_t &device : device_enumerator(root)) if (&device != &root) - out << util::string_format("\t\t<device_ref name=\"%s\"/>\n", normalize_string(device.shortname())); + util::stream_format(out, "\t\t<device_ref name=\"%s\"/>\n", util::xml::normalize_string(device.shortname())); } @@ -755,12 +1023,12 @@ void output_device_refs(std::ostream &out, device_t &root) void output_sampleof(std::ostream &out, device_t &device) { // iterate over sample devices - for (samples_device &samples : samples_device_iterator(device)) + for (samples_device &samples : samples_device_enumerator(device)) { samples_iterator sampiter(samples); if (sampiter.altbasename() != nullptr) { - out << util::string_format(" sampleof=\"%s\"", normalize_string(sampiter.altbasename())); + util::stream_format(out, " sampleof=\"%s\"", util::xml::normalize_string(sampiter.altbasename())); // must stop here, as there can only be one attribute of the same name return; @@ -788,8 +1056,8 @@ void output_bios(std::ostream &out, device_t const &device) { // output extracted name and descriptions' out << "\t\t<biosset"; - out << util::string_format(" name=\"%s\"", normalize_string(bios.get_name())); - out << util::string_format(" description=\"%s\"", normalize_string(bios.get_description())); + util::stream_format(out, " name=\"%s\"", util::xml::normalize_string(bios.get_name())); + util::stream_format(out, " description=\"%s\"", util::xml::normalize_string(bios.get_description())); if (defaultname && !std::strcmp(defaultname, bios.get_name())) out << " default=\"yes\""; out << "/>\n"; @@ -802,7 +1070,7 @@ void output_bios(std::ostream &out, device_t const &device) // the XML output //------------------------------------------------- -void output_rom(std::ostream &out, driver_enumerator *drivlist, const game_driver *driver, device_t &device) +void output_rom(std::ostream &out, machine_config &config, driver_list const *drivlist, const game_driver *driver, device_t &device) { enum class type { BIOS, NORMAL, DISK }; std::map<u32, char const *> biosnames; @@ -840,7 +1108,7 @@ void output_rom(std::ostream &out, driver_enumerator *drivlist, const game_drive // loop until we run out of reloads do { - // loop until we run out of continues/ignores */ + // loop until we run out of continues/ignores u32 curlength(ROM_GETLENGTH(romp++)); while (ROMENTRY_ISCONTINUE(romp) || ROMENTRY_ISIGNORE(romp)) curlength += ROM_GETLENGTH(romp++); @@ -854,12 +1122,14 @@ void output_rom(std::ostream &out, driver_enumerator *drivlist, const game_drive }; // iterate over 3 different ROM "types": BIOS, ROMs, DISKs - bool const do_merge_name = drivlist && dynamic_cast<driver_device *>(&device); + bool const driver_merge = drivlist && dynamic_cast<driver_device *>(&device); for (type pass : { type::BIOS, type::NORMAL, type::DISK }) { tiny_rom_entry const *region(nullptr); for (tiny_rom_entry const *rom = device.rom_region(); rom && !ROMENTRY_ISEND(rom); ++rom) { + using util::xml::normalize_string; + if (ROMENTRY_ISREGION(rom)) region = rom; else if (ROMENTRY_ISSYSTEM_BIOS(rom)) @@ -873,7 +1143,7 @@ void output_rom(std::ostream &out, driver_enumerator *drivlist, const game_drive if ((type::DISK == pass) != is_disk) continue; - // BIOS ROMs only apply to bioses + // BIOS ROMs only apply to BIOSes // FIXME: disk images associated with a system BIOS will never be listed u32 const biosno(ROM_GETBIOSFLAGS(rom)); if ((type::BIOS == pass) != bool(biosno)) @@ -882,7 +1152,10 @@ void output_rom(std::ostream &out, driver_enumerator *drivlist, const game_drive // if we have a valid ROM and we are a clone, see if we can find the parent ROM util::hash_collection const hashes(rom->hashdata); - char const *const merge_name((do_merge_name && !hashes.flag(util::hash_collection::FLAG_NO_DUMP)) ? get_merge_name(*drivlist, *driver, hashes) : nullptr); + char const *const merge_name( + hashes.flag(util::hash_collection::FLAG_NO_DUMP) ? nullptr : + driver_merge ? get_merge_name(*drivlist, *driver, hashes) : + get_merge_name(config, device, hashes)); // opening tag if (is_disk) @@ -890,16 +1163,16 @@ void output_rom(std::ostream &out, driver_enumerator *drivlist, const game_drive else out << "\t\t<rom"; - // add name, merge, bios, and size tags */ + // add name, merge, bios, and size tags char const *const name(rom->name); if (name && name[0]) - out << util::string_format(" name=\"%s\"", normalize_string(name)); + util::stream_format(out, " name=\"%s\"", normalize_string(name)); if (merge_name) - out << util::string_format(" merge=\"%s\"", normalize_string(merge_name)); + util::stream_format(out, " merge=\"%s\"", normalize_string(merge_name)); if (bios_name) - out << util::string_format(" bios=\"%s\"", normalize_string(bios_name)); + util::stream_format(out, " bios=\"%s\"", normalize_string(bios_name)); if (!is_disk) - out << util::string_format(" size=\"%u\"", rom_file_size(rom)); + util::stream_format(out, " size=\"%u\"", rom_file_size(rom)); // dump checksum information only if there is a known dump if (!hashes.flag(util::hash_collection::FLAG_NO_DUMP)) @@ -908,17 +1181,17 @@ void output_rom(std::ostream &out, driver_enumerator *drivlist, const game_drive out << " status=\"nodump\""; // append a region name - out << util::string_format(" region=\"%s\"", region->name); + util::stream_format(out, " region=\"%s\"", region->name); if (!is_disk) { // for non-disk entries, print offset - out << util::string_format(" offset=\"%x\"", ROM_GETOFFSET(rom)); + util::stream_format(out, " offset=\"%x\"", ROM_GETOFFSET(rom)); } else { // for disk entries, add the disk index - out << util::string_format(" index=\"%x\" writable=\"%s\"", DISK_GETINDEX(rom), DISK_ISREADONLY(rom) ? "no" : "yes"); + util::stream_format(out, " index=\"%x\" writable=\"%s\"", DISK_GETINDEX(rom), DISK_ISREADONLY(rom) ? "no" : "yes"); } // add optional flag @@ -940,7 +1213,7 @@ void output_rom(std::ostream &out, driver_enumerator *drivlist, const game_drive void output_sample(std::ostream &out, device_t &device) { // iterate over sample devices - for (samples_device &samples : samples_device_iterator(device)) + for (samples_device &samples : samples_device_enumerator(device)) { samples_iterator iter(samples); std::unordered_set<std::string> already_printed; @@ -951,7 +1224,7 @@ void output_sample(std::ostream &out, device_t &device) continue; // output the sample name - out << util::string_format("\t\t<sample name=\"%s\"/>\n", normalize_string(samplename)); + util::stream_format(out, "\t\t<sample name=\"%s\"/>\n", util::xml::normalize_string(samplename)); } } } @@ -964,8 +1237,10 @@ void output_sample(std::ostream &out, device_t &device) void output_chips(std::ostream &out, device_t &device, const char *root_tag) { + using util::xml::normalize_string; + // iterate over executable devices - for (device_execute_interface &exec : execute_interface_iterator(device)) + for (device_execute_interface &exec : execute_interface_enumerator(device)) { if (strcmp(exec.device().tag(), device.tag())) { @@ -974,27 +1249,27 @@ void output_chips(std::ostream &out, device_t &device, const char *root_tag) out << "\t\t<chip"; out << " type=\"cpu\""; - out << util::string_format(" tag=\"%s\"", normalize_string(newtag.c_str())); - out << util::string_format(" name=\"%s\"", normalize_string(exec.device().name())); - out << util::string_format(" clock=\"%d\"", exec.device().clock()); + util::stream_format(out, " tag=\"%s\"", normalize_string(newtag)); + util::stream_format(out, " name=\"%s\"", normalize_string(exec.device().name())); + util::stream_format(out, " clock=\"%d\"", exec.device().clock()); out << "/>\n"; } } // iterate over sound devices - for (device_sound_interface &sound : sound_interface_iterator(device)) + for (device_sound_interface &sound : sound_interface_enumerator(device)) { - if (strcmp(sound.device().tag(), device.tag()) != 0 && sound.issound()) + if (strcmp(sound.device().tag(), device.tag()) != 0) { std::string newtag(sound.device().tag()), oldtag(":"); newtag = newtag.substr(newtag.find(oldtag.append(root_tag)) + oldtag.length()); out << "\t\t<chip"; out << " type=\"audio\""; - out << util::string_format(" tag=\"%s\"", normalize_string(newtag.c_str())); - out << util::string_format(" name=\"%s\"", normalize_string(sound.device().name())); + util::stream_format(out, " tag=\"%s\"", normalize_string(newtag)); + util::stream_format(out, " name=\"%s\"", normalize_string(sound.device().name())); if (sound.device().clock() != 0) - out << util::string_format(" clock=\"%d\"", sound.device().clock()); + util::stream_format(out, " clock=\"%d\"", sound.device().clock()); out << "/>\n"; } } @@ -1009,14 +1284,14 @@ void output_chips(std::ostream &out, device_t &device, const char *root_tag) void output_display(std::ostream &out, device_t &device, machine_flags::type const *flags, const char *root_tag) { // iterate over screens - for (const screen_device &screendev : screen_device_iterator(device)) + for (const screen_device &screendev : screen_device_enumerator(device)) { if (strcmp(screendev.tag(), device.tag())) { std::string newtag(screendev.tag()), oldtag(":"); newtag = newtag.substr(newtag.find(oldtag.append(root_tag)) + oldtag.length()); - out << util::string_format("\t\t<display tag=\"%s\"", normalize_string(newtag.c_str())); + util::stream_format(out, "\t\t<display tag=\"%s\"", util::xml::normalize_string(newtag)); switch (screendev.screen_type()) { @@ -1060,12 +1335,12 @@ void output_display(std::ostream &out, device_t &device, machine_flags::type con if (screendev.screen_type() != SCREEN_TYPE_VECTOR) { const rectangle &visarea = screendev.visible_area(); - out << util::string_format(" width=\"%d\"", visarea.width()); - out << util::string_format(" height=\"%d\"", visarea.height()); + util::stream_format(out, " width=\"%d\"", visarea.width()); + util::stream_format(out, " height=\"%d\"", visarea.height()); } // output refresh rate - out << util::string_format(" refresh=\"%f\"", ATTOSECONDS_TO_HZ(screendev.refresh_attoseconds())); + util::stream_format(out, " refresh=\"%f\"", ATTOSECONDS_TO_HZ(screendev.refresh_attoseconds())); // output raw video parameters only for games that are not vector // and had raw parameters specified @@ -1073,13 +1348,13 @@ void output_display(std::ostream &out, device_t &device, machine_flags::type con { int pixclock = screendev.width() * screendev.height() * ATTOSECONDS_TO_HZ(screendev.refresh_attoseconds()); - out << util::string_format(" pixclock=\"%d\"", pixclock); - out << util::string_format(" htotal=\"%d\"", screendev.width()); - out << util::string_format(" hbend=\"%d\"", screendev.visible_area().min_x); - out << util::string_format(" hbstart=\"%d\"", screendev.visible_area().max_x+1); - out << util::string_format(" vtotal=\"%d\"", screendev.height()); - out << util::string_format(" vbend=\"%d\"", screendev.visible_area().min_y); - out << util::string_format(" vbstart=\"%d\"", screendev.visible_area().max_y+1); + util::stream_format(out, " pixclock=\"%d\"", pixclock); + util::stream_format(out, " htotal=\"%d\"", screendev.width()); + util::stream_format(out, " hbend=\"%d\"", screendev.visible_area().min_x); + util::stream_format(out, " hbstart=\"%d\"", screendev.visible_area().max_x+1); + util::stream_format(out, " vtotal=\"%d\"", screendev.height()); + util::stream_format(out, " vbend=\"%d\"", screendev.visible_area().min_y); + util::stream_format(out, " vbstart=\"%d\"", screendev.visible_area().max_y+1); } out << " />\n"; } @@ -1094,15 +1369,15 @@ void output_display(std::ostream &out, device_t &device, machine_flags::type con void output_sound(std::ostream &out, device_t &device) { - speaker_device_iterator spkiter(device); + speaker_device_enumerator spkiter(device); int speakers = spkiter.count(); // if we have no sound, zero m_output the speaker count - sound_interface_iterator snditer(device); + sound_interface_enumerator snditer(device); if (snditer.first() == nullptr) speakers = 0; - out << util::string_format("\t\t<sound channels=\"%d\"/>\n", speakers); + util::stream_format(out, "\t\t<sound channels=\"%d\"/>\n", speakers); } @@ -1128,7 +1403,7 @@ void output_ioport_condition(std::ostream &out, const ioport_condition &conditio case ioport_condition::NOTLESSTHAN: rel = "ge"; break; } - out << util::string_format("<condition tag=\"%s\" mask=\"%u\" relation=\"%s\" value=\"%u\"/>\n", normalize_string(condition.tag()), condition.mask(), rel, condition.value()); + util::stream_format(out, "<condition tag=\"%s\" mask=\"%u\" relation=\"%s\" value=\"%u\"/>\n", util::xml::normalize_string(condition.tag()), condition.mask(), rel, condition.value()); } //------------------------------------------------- @@ -1190,14 +1465,14 @@ void output_input(std::ostream &out, const ioport_list &portlist) int player; // player which the input belongs to int nbuttons; // total number of buttons int reqbuttons; // total number of non-optional buttons - int maxbuttons; // max index of buttons (using IPT_BUTTONn) [probably to be removed soonish] + uint32_t maxbuttons; // max index of buttons (using IPT_BUTTONn) [probably to be removed soonish] int ways; // directions for joystick bool analog; // is analog input? - uint8_t helper[3]; // for dual joysticks [possibly to be removed soonish] - int32_t min; // analog minimum value - int32_t max; // analog maximum value - int32_t sensitivity; // default analog sensitivity - int32_t keydelta; // default analog keydelta + uint8_t helper[3]; // for dual joysticks [possibly to be removed soonish] + int32_t min; // analog minimum value + int32_t max; // analog maximum value + int32_t sensitivity; // default analog sensitivity + int32_t keydelta; // default analog keydelta bool reverse; // default analog reverse setting } control_info[CTRL_COUNT * CTRL_PCOUNT]; @@ -1205,7 +1480,7 @@ void output_input(std::ostream &out, const ioport_list &portlist) // tracking info as we iterate int nplayer = 0; - int ncoin = 0; + uint32_t ncoin = 0; bool service = false; bool tilt = false; @@ -1214,7 +1489,7 @@ void output_input(std::ostream &out, const ioport_list &portlist) { int ctrl_type = CTRL_DIGITAL_BUTTONS; bool ctrl_analog = false; - for (ioport_field &field : port.second->fields()) + for (ioport_field const &field : port.second->fields()) { // track the highest player number if (nplayer < field.player() + 1) @@ -1531,37 +1806,39 @@ void output_input(std::ostream &out, const ioport_list &portlist) // Output the input info // First basic info out << "\t\t<input"; - out << util::string_format(" players=\"%d\"", nplayer); + util::stream_format(out, " players=\"%d\"", nplayer); if (ncoin != 0) - out << util::string_format(" coins=\"%d\"", ncoin); + util::stream_format(out, " coins=\"%u\"", ncoin); if (service) - out << util::string_format(" service=\"yes\""); + util::stream_format(out, " service=\"yes\""); if (tilt) - out << util::string_format(" tilt=\"yes\""); + util::stream_format(out, " tilt=\"yes\""); out << ">\n"; // Then controller specific ones for (auto & elem : control_info) if (elem.type != nullptr) { + using util::xml::normalize_string; + //printf("type %s - player %d - buttons %d\n", elem.type, elem.player, elem.nbuttons); if (elem.analog) { - out << util::string_format("\t\t\t<control type=\"%s\"", normalize_string(elem.type)); + util::stream_format(out, "\t\t\t<control type=\"%s\"", normalize_string(elem.type)); if (nplayer > 1) - out << util::string_format(" player=\"%d\"", elem.player); + util::stream_format(out, " player=\"%d\"", elem.player); if (elem.nbuttons > 0) { - out << util::string_format(" buttons=\"%d\"", strcmp(elem.type, "stick") ? elem.nbuttons : elem.maxbuttons); + util::stream_format(out, " buttons=\"%u\"", strcmp(elem.type, "stick") ? elem.nbuttons : elem.maxbuttons); if (elem.reqbuttons < elem.nbuttons) - out << util::string_format(" reqbuttons=\"%d\"", elem.reqbuttons); + util::stream_format(out, " reqbuttons=\"%d\"", elem.reqbuttons); } if (elem.min != 0 || elem.max != 0) - out << util::string_format(" minimum=\"%d\" maximum=\"%d\"", elem.min, elem.max); + util::stream_format(out, " minimum=\"%d\" maximum=\"%d\"", elem.min, elem.max); if (elem.sensitivity != 0) - out << util::string_format(" sensitivity=\"%d\"", elem.sensitivity); + util::stream_format(out, " sensitivity=\"%d\"", elem.sensitivity); if (elem.keydelta != 0) - out << util::string_format(" keydelta=\"%d\"", elem.keydelta); + util::stream_format(out, " keydelta=\"%d\"", elem.keydelta); if (elem.reverse) out << " reverse=\"yes\""; @@ -1573,14 +1850,14 @@ void output_input(std::ostream &out, const ioport_list &portlist) if (elem.helper[0] == 0 && elem.helper[1] != 0) { elem.helper[0] = elem.helper[1]; elem.helper[1] = 0; } if (elem.helper[1] == 0 && elem.helper[2] != 0) { elem.helper[1] = elem.helper[2]; elem.helper[2] = 0; } const char *joys = (elem.helper[2] != 0) ? "triple" : (elem.helper[1] != 0) ? "double" : ""; - out << util::string_format("\t\t\t<control type=\"%s%s\"", joys, normalize_string(elem.type)); + util::stream_format(out, "\t\t\t<control type=\"%s%s\"", joys, normalize_string(elem.type)); if (nplayer > 1) - out << util::string_format(" player=\"%d\"", elem.player); + util::stream_format(out, " player=\"%d\"", elem.player); if (elem.nbuttons > 0) { - out << util::string_format(" buttons=\"%d\"", strcmp(elem.type, "joy") ? elem.nbuttons : elem.maxbuttons); + util::stream_format(out, " buttons=\"%u\"", strcmp(elem.type, "joy") ? elem.nbuttons : elem.maxbuttons); if (elem.reqbuttons < elem.nbuttons) - out << util::string_format(" reqbuttons=\"%d\"", elem.reqbuttons); + util::stream_format(out, " reqbuttons=\"%d\"", elem.reqbuttons); } for (int lp = 0; lp < 3 && elem.helper[lp] != 0; lp++) { @@ -1590,7 +1867,7 @@ void output_input(std::ostream &out, const ioport_list &portlist) switch (elem.helper[lp] & (DIR_UP | DIR_DOWN | DIR_LEFT | DIR_RIGHT)) { case DIR_UP | DIR_DOWN | DIR_LEFT | DIR_RIGHT: - helper = string_format("%d", (elem.ways == 0) ? 8 : elem.ways); + helper = util::string_format(std::locale::classic(), "%d", (elem.ways == 0) ? 8 : elem.ways); ways = helper.c_str(); break; case DIR_LEFT | DIR_RIGHT: @@ -1615,7 +1892,7 @@ void output_input(std::ostream &out, const ioport_list &portlist) ways = "strange2"; break; } - out << util::string_format(" ways%s=\"%s\"", plural, ways); + util::stream_format(out, " ways%s=\"%s\"", plural, ways); } out << "/>\n"; } @@ -1637,20 +1914,23 @@ void output_switches(std::ostream &out, const ioport_list &portlist, const char for (ioport_field const &field : port.second->fields()) if (field.type() == type) { + using util::xml::normalize_string; + std::string newtag(port.second->tag()), oldtag(":"); newtag = newtag.substr(newtag.find(oldtag.append(root_tag)) + oldtag.length()); // output the switch name information - std::string const normalized_field_name(normalize_string(field.name())); - std::string const normalized_newtag(normalize_string(newtag.c_str())); - out << util::string_format("\t\t<%s name=\"%s\" tag=\"%s\" mask=\"%u\">\n", outertag, normalized_field_name.c_str(), normalized_newtag.c_str(), field.mask()); + assert(field.specific_name() != nullptr); + std::string const normalized_field_name(normalize_string(field.specific_name())); + std::string const normalized_newtag(normalize_string(newtag)); + util::stream_format(out, "\t\t<%s name=\"%s\" tag=\"%s\" mask=\"%u\">\n", outertag, normalized_field_name, normalized_newtag, field.mask()); if (!field.condition().none()) output_ioport_condition(out, field.condition(), 3); // loop over locations for (ioport_diplocation const &diploc : field.diplocations()) { - out << util::string_format("\t\t\t<%s name=\"%s\" number=\"%u\"", loctag, normalize_string(diploc.name()), diploc.number()); + util::stream_format(out, "\t\t\t<%s name=\"%s\" number=\"%u\"", loctag, normalize_string(diploc.name()), diploc.number()); if (diploc.inverted()) out << " inverted=\"yes\""; out << "/>\n"; @@ -1659,7 +1939,7 @@ void output_switches(std::ostream &out, const ioport_list &portlist, const char // loop over settings for (ioport_setting const &setting : field.settings()) { - out << util::string_format("\t\t\t<%s name=\"%s\" value=\"%u\"", innertag, normalize_string(setting.name()), setting.value()); + util::stream_format(out, "\t\t\t<%s name=\"%s\" value=\"%u\"", innertag, normalize_string(setting.name()), setting.value()); if (setting.value() == field.defvalue()) out << " default=\"yes\""; if (setting.condition().none()) @@ -1670,12 +1950,12 @@ void output_switches(std::ostream &out, const ioport_list &portlist, const char { out << ">\n"; output_ioport_condition(out, setting.condition(), 4); - out << util::string_format("\t\t\t</%s>\n", innertag); + util::stream_format(out, "\t\t\t</%s>\n", innertag); } } // terminate the switch entry - out << util::string_format("\t\t</%s>\n", outertag); + util::stream_format(out, "\t\t</%s>\n", outertag); } } @@ -1688,13 +1968,13 @@ void output_ports(std::ostream &out, const ioport_list &portlist) // cycle through ports for (auto &port : portlist) { - out << util::string_format("\t\t<port tag=\"%s\">\n", normalize_string(port.second->tag())); + util::stream_format(out, "\t\t<port tag=\"%s\">\n", util::xml::normalize_string(port.second->tag())); for (ioport_field const &field : port.second->fields()) { if (field.is_analog()) - out << util::string_format("\t\t\t<analog mask=\"%u\"/>\n", field.mask()); + util::stream_format(out, "\t\t\t<analog mask=\"%u\"/>\n", field.mask()); } - out << util::string_format("\t\t</port>\n"); + util::stream_format(out, "\t\t</port>\n"); } } @@ -1711,7 +1991,7 @@ void output_adjusters(std::ostream &out, const ioport_list &portlist) for (ioport_field const &field : port.second->fields()) if (field.type() == IPT_ADJUSTER) { - out << util::string_format("\t\t<adjuster name=\"%s\" default=\"%d\"/>\n", normalize_string(field.name()), field.defvalue()); + util::stream_format(out, "\t\t<adjuster name=\"%s\" default=\"%d\"/>\n", util::xml::normalize_string(field.specific_name()), field.defvalue()); } } @@ -1720,7 +2000,12 @@ void output_adjusters(std::ostream &out, const ioport_list &portlist) // output_driver - print driver status //------------------------------------------------- -void output_driver(std::ostream &out, game_driver const &driver, device_t::feature_type unemulated, device_t::feature_type imperfect) +void output_driver( + std::ostream &out, + game_driver const &driver, + device_t::flags_type flags, + device_t::feature_type unemulated, + device_t::feature_type imperfect) { out << "\t\t<driver"; @@ -1733,8 +2018,9 @@ void output_driver(std::ostream &out, game_driver const &driver, device_t::featu emulation problems. */ - u32 const flags = driver.flags; - bool const machine_preliminary(flags & (machine_flags::NOT_WORKING | machine_flags::MECHANICAL)); + u32 const driver_flags = driver.flags; + bool const not_working(driver.type.emulation_flags() & device_t::flags::NOT_WORKING); + bool const machine_preliminary(not_working || (driver_flags & machine_flags::MECHANICAL)); bool const unemulated_preliminary(unemulated & (device_t::feature::PALETTE | device_t::feature::GRAPHICS | device_t::feature::SOUND | device_t::feature::KEYBOARD)); bool const imperfect_preliminary((unemulated | imperfect) & device_t::feature::PROTECTION); @@ -1745,18 +2031,30 @@ void output_driver(std::ostream &out, game_driver const &driver, device_t::featu else out << " status=\"good\""; - if (flags & machine_flags::NOT_WORKING) + if (not_working) out << " emulation=\"preliminary\""; else out << " emulation=\"good\""; - if (flags & machine_flags::NO_COCKTAIL) + if (driver_flags & machine_flags::NO_COCKTAIL) out << " cocktail=\"preliminary\""; - if (flags & machine_flags::SUPPORTS_SAVE) - out << " savestate=\"supported\""; - else + if (flags & device_t::flags::SAVE_UNSUPPORTED) out << " savestate=\"unsupported\""; + else + out << " savestate=\"supported\""; + + if (driver_flags & machine_flags::REQUIRES_ARTWORK) + out << " requiresartwork=\"yes\""; + + if (driver_flags & machine_flags::UNOFFICIAL) + out << " unofficial=\"yes\""; + + if (driver_flags & machine_flags::NO_SOUND_HW) + out << " nosoundhardware=\"yes\""; + + if (driver_flags & machine_flags::IS_INCOMPLETE) + out << " incomplete=\"yes\""; out << "/>\n"; } @@ -1769,35 +2067,12 @@ void output_driver(std::ostream &out, game_driver const &driver, device_t::featu void output_features(std::ostream &out, device_type type, device_t::feature_type unemulated, device_t::feature_type imperfect) { - static constexpr std::pair<device_t::feature_type, char const *> features[] = { - { device_t::feature::PROTECTION, "protection" }, - { device_t::feature::TIMING, "timing" }, - { device_t::feature::GRAPHICS, "graphics" }, - { device_t::feature::PALETTE, "palette" }, - { device_t::feature::SOUND, "sound" }, - { device_t::feature::CAPTURE, "capture" }, - { device_t::feature::CAMERA, "camera" }, - { device_t::feature::MICROPHONE, "microphone" }, - { device_t::feature::CONTROLS, "controls" }, - { device_t::feature::KEYBOARD, "keyboard" }, - { device_t::feature::MOUSE, "mouse" }, - { device_t::feature::MEDIA, "media" }, - { device_t::feature::DISK, "disk" }, - { device_t::feature::PRINTER, "printer" }, - { device_t::feature::TAPE, "tape" }, - { device_t::feature::PUNCH, "punch" }, - { device_t::feature::DRUM, "drum" }, - { device_t::feature::ROM, "rom" }, - { device_t::feature::COMMS, "comms" }, - { device_t::feature::LAN, "lan" }, - { device_t::feature::WAN, "wan" } }; - device_t::feature_type const flags(type.unemulated_features() | type.imperfect_features() | unemulated | imperfect); - for (auto const &feature : features) + for (auto const &feature : f_feature_names) { if (flags & feature.first) { - out << util::string_format("\t\t<feature type=\"%s\"", feature.second); + util::stream_format(out, "\t\t<feature type=\"%s\"", feature.second); if (type.unemulated_features() & feature.first) { out << " status=\"unemulated\""; @@ -1824,20 +2099,23 @@ void output_features(std::ostream &out, device_type type, device_t::feature_type void output_images(std::ostream &out, device_t &device, const char *root_tag) { - for (const device_image_interface &imagedev : image_interface_iterator(device)) + for (const device_image_interface &imagedev : image_interface_enumerator(device)) { if (strcmp(imagedev.device().tag(), device.tag())) { + using util::xml::normalize_string; + bool loadable = imagedev.user_loadable(); std::string newtag(imagedev.device().tag()), oldtag(":"); newtag = newtag.substr(newtag.find(oldtag.append(root_tag)) + oldtag.length()); // print m_output device type - out << util::string_format("\t\t<device type=\"%s\"", normalize_string(imagedev.image_type_name())); + assert(imagedev.image_type_name() != nullptr); + util::stream_format(out, "\t\t<device type=\"%s\"", normalize_string(imagedev.image_type_name())); // does this device have a tag? if (imagedev.device().tag()) - out << util::string_format(" tag=\"%s\"", normalize_string(newtag.c_str())); + util::stream_format(out, " tag=\"%s\"", normalize_string(newtag)); // is this device available as media switch? if (!loadable) @@ -1848,7 +2126,7 @@ void output_images(std::ostream &out, device_t &device, const char *root_tag) out << " mandatory=\"1\""; if (imagedev.image_interface() && imagedev.image_interface()[0]) - out << util::string_format(" interface=\"%s\"", normalize_string(imagedev.image_interface())); + util::stream_format(out, " interface=\"%s\"", normalize_string(imagedev.image_interface())); // close the XML tag out << ">\n"; @@ -1859,8 +2137,8 @@ void output_images(std::ostream &out, device_t &device, const char *root_tag) char const *const shortname = imagedev.brief_instance_name().c_str(); out << "\t\t\t<instance"; - out << util::string_format(" name=\"%s\"", normalize_string(name)); - out << util::string_format(" briefname=\"%s\"", normalize_string(shortname)); + util::stream_format(out, " name=\"%s\"", normalize_string(name)); + util::stream_format(out, " briefname=\"%s\"", normalize_string(shortname)); out << "/>\n"; char const *extensions(imagedev.file_extensions()); @@ -1869,7 +2147,7 @@ void output_images(std::ostream &out, device_t &device, const char *root_tag) char const *end(extensions); while (*end && (',' != *end)) ++end; - out << util::string_format("\t\t\t<extension name=\"%s\"/>\n", normalize_string(std::string(extensions, end).c_str())); + util::stream_format(out, "\t\t\t<extension name=\"%s\"/>\n", normalize_string(std::string_view(extensions, end - extensions))); extensions = *end ? (end + 1) : nullptr; } } @@ -1885,20 +2163,22 @@ void output_images(std::ostream &out, device_t &device, const char *root_tag) void output_slots(std::ostream &out, machine_config &config, device_t &device, const char *root_tag, device_type_set *devtypes) { - for (device_slot_interface &slot : slot_interface_iterator(device)) + for (device_slot_interface &slot : slot_interface_enumerator(device)) { // shall we list fixed slots as non-configurable? bool const listed(!slot.fixed() && strcmp(slot.device().tag(), device.tag())); if (devtypes || listed) { + using util::xml::normalize_string; + machine_config::token const tok(config.begin_configuration(slot.device())); std::string newtag(slot.device().tag()), oldtag(":"); newtag = newtag.substr(newtag.find(oldtag.append(root_tag)) + oldtag.length()); // print m_output device type if (listed) - out << util::string_format("\t\t<slot name=\"%s\">\n", normalize_string(newtag.c_str())); + util::stream_format(out, "\t\t<slot name=\"%s\">\n", normalize_string(newtag)); for (auto &option : slot.option_list()) { @@ -1909,13 +2189,13 @@ void output_slots(std::ostream &out, machine_config &config, device_t &device, c dev->config_complete(); if (devtypes) - for (device_t &subdevice : device_iterator(*dev)) devtypes->insert(&subdevice.type()); + for (device_t &subdevice : device_enumerator(*dev)) devtypes->insert(&subdevice.type()); if (listed && option.second->selectable()) { - out << util::string_format("\t\t\t<slotoption name=\"%s\"", normalize_string(option.second->name())); - out << util::string_format(" devname=\"%s\"", normalize_string(dev->shortname())); - if (slot.default_option() != nullptr && strcmp(slot.default_option(), option.second->name())==0) + util::stream_format(out, "\t\t\t<slotoption name=\"%s\"", normalize_string(option.second->name())); + util::stream_format(out, " devname=\"%s\"", normalize_string(dev->shortname())); + if (slot.default_option() && !strcmp(slot.default_option(), option.second->name())) out << " default=\"yes\""; out << "/>\n"; } @@ -1932,17 +2212,28 @@ void output_slots(std::ostream &out, machine_config &config, device_t &device, c //------------------------------------------------- -// output_software_list - print the information +// output_software_lists - print the information // for all known software lists for this system //------------------------------------------------- -void output_software_list(std::ostream &out, device_t &root) +void output_software_lists(std::ostream &out, device_t &root, const char *root_tag) { - for (const software_list_device &swlist : software_list_device_iterator(root)) + for (const software_list_device &swlist : software_list_device_enumerator(root)) { - out << util::string_format("\t\t<softwarelist name=\"%s\" status=\"%s\"", normalize_string(swlist.list_name().c_str()), (swlist.list_type() == SOFTWARE_LIST_ORIGINAL_SYSTEM) ? "original" : "compatible"); + using util::xml::normalize_string; + + if (&static_cast<const device_t &>(swlist) == &root) + { + assert(swlist.list_name().empty()); + continue; + } + + std::string newtag(swlist.tag()), oldtag(":"); + newtag = newtag.substr(newtag.find(oldtag.append(root_tag)) + oldtag.length()); + util::stream_format(out, "\t\t<softwarelist tag=\"%s\" name=\"%s\" status=\"%s\"", normalize_string(newtag), normalize_string(swlist.list_name()), swlist.is_original() ? "original" : "compatible"); + if (swlist.filter()) - out << util::string_format(" filter=\"%s\"", normalize_string(swlist.filter())); + util::stream_format(out, " filter=\"%s\"", normalize_string(swlist.filter())); out << "/>\n"; } } @@ -1956,7 +2247,7 @@ void output_software_list(std::ostream &out, device_t &root) void output_ramoptions(std::ostream &out, device_t &root) { - for (const ram_device &ram : ram_device_iterator(root, 1)) + for (const ram_device &ram : ram_device_enumerator(root, 1)) { if (!std::strcmp(ram.tag(), ":" RAM_TAG)) { @@ -1968,15 +2259,15 @@ void output_ramoptions(std::ostream &out, device_t &root) { assert(!havedefault); havedefault = true; - out << util::string_format("\t\t<ramoption name=\"%s\" default=\"yes\">%u</ramoption>\n", normalize_string(option.first.c_str()), option.second); + util::stream_format(out, "\t\t<ramoption name=\"%s\" default=\"yes\">%u</ramoption>\n", util::xml::normalize_string(option.first), option.second); } else { - out << util::string_format("\t\t<ramoption name=\"%s\">%u</ramoption>\n", normalize_string(option.first.c_str()), option.second); + util::stream_format(out, "\t\t<ramoption name=\"%s\">%u</ramoption>\n", util::xml::normalize_string(option.first), option.second); } } if (!havedefault) - out << util::string_format("\t\t<ramoption name=\"%s\" default=\"yes\">%u</ramoption>\n", ram.default_size_string(), defsize); + util::stream_format(out, "\t\t<ramoption name=\"%s\" default=\"yes\">%u</ramoption>\n", ram.default_size_string(), defsize); break; } } @@ -1988,21 +2279,51 @@ void output_ramoptions(std::ostream &out, device_t &root) // parent set //------------------------------------------------- -const char *get_merge_name(driver_enumerator &drivlist, const game_driver &driver, util::hash_collection const &romhashes) +char const *get_merge_name(driver_list const &drivlist, game_driver const &driver, util::hash_collection const &romhashes) { + char const *result = nullptr; + // walk the parent chain - for (int clone_of = drivlist.find(driver.parent); 0 <= clone_of; clone_of = drivlist.find(drivlist.driver(clone_of).parent)) + for (int clone_of = drivlist.find(driver.parent); !result && (0 <= clone_of); clone_of = drivlist.find(drivlist.driver(clone_of).parent)) + result = get_merge_name(drivlist.driver(clone_of).rom, romhashes); + + return result; +} + + +char const *get_merge_name(machine_config &config, device_t const &device, util::hash_collection const &romhashes) +{ + char const *result = nullptr; + + // check for a parent type + auto const parenttype(device.type().parent_rom_device_type()); + if (parenttype) { + // instantiate the parent device + machine_config::token const tok(config.begin_configuration(config.root_device())); + device_t *const parent = config.device_add("_parent", *parenttype, 0); + // look in the parent's ROMs - for (romload::region const &pregion : romload::entries(drivlist.driver(clone_of).rom).get_regions()) + result = get_merge_name(parent->rom_region(), romhashes); + + // remember to remove the device + config.device_remove("_parent"); + } + + return result; +} + + +char const *get_merge_name(tiny_rom_entry const *roms, util::hash_collection const &romhashes) +{ + for (romload::region const &pregion : romload::entries(roms).get_regions()) + { + for (romload::file const &prom : pregion.get_files()) { - for (romload::file const &prom : pregion.get_files()) - { - // stop when we find a match - util::hash_collection const phashes(prom.get_hashdata()); - if (!phashes.flag(util::hash_collection::FLAG_NO_DUMP) && (romhashes == phashes)) - return prom.get_name(); - } + // stop when we find a match + util::hash_collection const phashes(prom.get_hashdata()); + if (!phashes.flag(util::hash_collection::FLAG_NO_DUMP) && (romhashes == phashes)) + return prom.get_name(); } } diff --git a/src/frontend/mame/info.h b/src/frontend/mame/infoxml.h index 2867391267c..06141ef83ba 100644 --- a/src/frontend/mame/info.h +++ b/src/frontend/mame/infoxml.h @@ -8,19 +8,19 @@ ***************************************************************************/ -#ifndef MAME_FRONTEND_MAME_INFO_H -#define MAME_FRONTEND_MAME_INFO_H +#ifndef MAME_FRONTEND_MAME_INFOXML_H +#define MAME_FRONTEND_MAME_INFOXML_H #pragma once #include "emuopts.h" +#include <functional> +#include <string> +#include <string_view> #include <vector> -class driver_enumerator; - - //************************************************************************** // FUNCTION PROTOTYPES //************************************************************************** @@ -36,10 +36,13 @@ public: void output(std::ostream &out, const std::vector<std::string> &patterns); void output(std::ostream &out, const std::function<bool(const char *shortname, bool &done)> &filter = { }, bool include_devices = true); + static char const *feature_name(device_t::feature_type feature); + static std::string format_sourcefile(std::string_view path); + private: // internal state emu_options m_lookup_options; bool m_dtd; }; -#endif // MAME_FRONTEND_MAME_INFO_H +#endif // MAME_FRONTEND_MAME_INFOXML_H diff --git a/src/frontend/mame/iptseqpoll.cpp b/src/frontend/mame/iptseqpoll.cpp new file mode 100644 index 00000000000..7808256c190 --- /dev/null +++ b/src/frontend/mame/iptseqpoll.cpp @@ -0,0 +1,514 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb, Aaron Giles + +#include "emu.h" +#include "iptseqpoll.h" + +#include "inputdev.h" + +#include <cassert> +#include <algorithm> + + +input_code_poller::input_code_poller(input_manager &manager) noexcept : + m_manager(manager), + m_axis_memory(), + m_switch_memory() +{ +} + + +input_code_poller::~input_code_poller() +{ +} + + +void input_code_poller::reset() +{ + // iterate over device classes and devices + m_axis_memory.clear(); + m_switch_memory.clear(); + for (input_device_class classno = DEVICE_CLASS_FIRST_VALID; DEVICE_CLASS_LAST_VALID >= classno; ++classno) + { + input_class &devclass(m_manager.device_class(classno)); + if (devclass.enabled()) + { + for (int devnum = 0; devclass.maxindex() >= devnum; ++devnum) + { + // fetch the device; ignore if nullptr + input_device *const device(devclass.device(devnum)); + if (device) + { + // iterate over items within each device + for (input_item_id itemid = ITEM_ID_FIRST_VALID; device->maxitem() >= itemid; ++itemid) + { + // for any non-switch items, set memory to the current value + input_device_item *const item(device->item(itemid)); + if (item && (item->itemclass() != ITEM_CLASS_SWITCH)) + m_axis_memory.emplace_back(item, m_manager.code_value(item->code())); + } + } + } + } + } + std::sort(m_axis_memory.begin(), m_axis_memory.end()); +} + + +bool input_code_poller::code_pressed_once(input_code code, bool moved) +{ + // look for the code in the memory + bool const pressed(m_manager.code_pressed(code)); + auto const found(std::lower_bound(m_switch_memory.begin(), m_switch_memory.end(), code)); + if ((m_switch_memory.end() != found) && (*found == code)) + { + // if no longer pressed, clear entry + if (!pressed) + m_switch_memory.erase(found); + + // always return false + return false; + } + + // if we get here, we were not previously pressed; if still not pressed, return false + if (!pressed || !moved) + return false; + + // otherwise, add the code to the memory and return true + m_switch_memory.emplace(found, code); + return true; +} + + + +axis_code_poller::axis_code_poller(input_manager &manager) noexcept : + input_code_poller(manager), + m_axis_active() +{ +} + + +void axis_code_poller::reset() +{ + input_code_poller::reset(); + m_axis_active.clear(); + m_axis_active.resize(m_axis_memory.size(), false); +} + + +input_code axis_code_poller::poll() +{ + // iterate over the axis items we found + for (std::size_t i = 0; m_axis_memory.size() > i; ++i) + { + auto &memory = m_axis_memory[i]; + input_code code = memory.first->code(); + if (!memory.first->check_axis(code.item_modifier(), memory.second)) + { + m_axis_active[i] = false; + } + else if (!m_axis_active[i]) + { + if (code.item_class() == ITEM_CLASS_ABSOLUTE) + { + m_axis_active[i] = true; + } + else + { + // can only cycle modifiers on a relative item with append + m_axis_memory.erase(m_axis_memory.begin() + i); + m_axis_active.erase(m_axis_active.begin() + i); + } + if (!m_manager.device_class(memory.first->device().devclass()).multi()) + code.set_device_index(0); + return code; + } + } + + // iterate over device classes and devices, skipping disabled classes + for (input_device_class classno = DEVICE_CLASS_FIRST_VALID; DEVICE_CLASS_LAST_VALID >= classno; ++classno) + { + input_class &devclass(m_manager.device_class(classno)); + if (!devclass.enabled()) + continue; + + for (int devnum = 0; devclass.maxindex() >= devnum; ++devnum) + { + // fetch the device; ignore if nullptr + input_device *const device(devclass.device(devnum)); + if (!device) + continue; + + // iterate over items within each device + for (input_item_id itemid = ITEM_ID_FIRST_VALID; device->maxitem() >= itemid; ++itemid) + { + input_device_item *const item(device->item(itemid)); + if (!item) + continue; + + input_code code = item->code(); + if (item->itemclass() == ITEM_CLASS_SWITCH) + { + // item is natively a switch, poll it + if (code_pressed_once(code, true)) + return code; + else + continue; + } + } + } + } + + // if nothing, return an invalid code + return INPUT_CODE_INVALID; +} + + + +switch_code_poller::switch_code_poller(input_manager &manager) noexcept : + input_code_poller(manager) +{ +} + + +input_code switch_code_poller::poll() +{ + // iterate over device classes and devices, skipping disabled classes + for (input_device_class classno = DEVICE_CLASS_FIRST_VALID; DEVICE_CLASS_LAST_VALID >= classno; ++classno) + { + input_class &devclass(m_manager.device_class(classno)); + if (!devclass.enabled()) + continue; + + for (int devnum = 0; devclass.maxindex() >= devnum; ++devnum) + { + // fetch the device; ignore if nullptr + input_device *const device(devclass.device(devnum)); + if (!device) + continue; + + // iterate over items within each device + for (input_item_id itemid = ITEM_ID_FIRST_VALID; device->maxitem() >= itemid; ++itemid) + { + input_device_item *const item(device->item(itemid)); + if (!item) + continue; + + input_code code = item->code(); + if (item->itemclass() == ITEM_CLASS_SWITCH) + { + // item is natively a switch, poll it + if (code_pressed_once(code, true)) + return code; + else + continue; + } + + auto const memory(std::lower_bound( + m_axis_memory.begin(), + m_axis_memory.end(), + item, + [] (auto const &x, auto const &y) { return x.first < y; })); + if ((m_axis_memory.end() == memory) || (item != memory->first)) + continue; + + // poll axes digitally + bool const moved(item->check_axis(code.item_modifier(), memory->second)); + code.set_item_class(ITEM_CLASS_SWITCH); + if ((classno == DEVICE_CLASS_JOYSTICK) && (code.item_id() == ITEM_ID_XAXIS)) + { + // joystick X axis - check with left/right modifiers + code.set_item_modifier(ITEM_MODIFIER_LEFT); + if (code_pressed_once(code, moved)) + return code; + code.set_item_modifier(ITEM_MODIFIER_RIGHT); + if (code_pressed_once(code, moved)) + return code; + } + else if ((classno == DEVICE_CLASS_JOYSTICK) && (code.item_id() == ITEM_ID_YAXIS)) + { + // if this is a joystick Y axis, check with up/down modifiers + code.set_item_modifier(ITEM_MODIFIER_UP); + if (code_pressed_once(code, moved)) + return code; + code.set_item_modifier(ITEM_MODIFIER_DOWN); + if (code_pressed_once(code, moved)) + return code; + } + else + { + // any other axis, check with pos/neg modifiers + code.set_item_modifier(ITEM_MODIFIER_POS); + if (code_pressed_once(code, moved)) + return code; + code.set_item_modifier(ITEM_MODIFIER_NEG); + if (code_pressed_once(code, moved)) + return code; + } + } + } + } + + // if nothing, return an invalid code + return INPUT_CODE_INVALID; +} + + + +keyboard_code_poller::keyboard_code_poller(input_manager &manager) noexcept : + input_code_poller(manager) +{ +} + + +input_code keyboard_code_poller::poll() +{ + // iterate over devices in keyboard class + input_class &devclass = m_manager.device_class(DEVICE_CLASS_KEYBOARD); + for (int devnum = 0; devclass.maxindex() >= devnum; ++devnum) + { + // fetch the device; ignore if nullptr + input_device *const device(devclass.device(devnum)); + if (device) + { + // iterate over items within each device + for (input_item_id itemid = ITEM_ID_FIRST_VALID; itemid <= device->maxitem(); ++itemid) + { + // iterate over items within each device + for (input_item_id itemid = ITEM_ID_FIRST_VALID; device->maxitem() >= itemid; ++itemid) + { + input_device_item *const item = device->item(itemid); + if (item && (item->itemclass() == ITEM_CLASS_SWITCH)) + { + input_code const code = item->code(); + if (code_pressed_once(code, true)) + return code; + } + } + } + } + } + + // if nothing, return an invalid code + return INPUT_CODE_INVALID; +} + + + +input_sequence_poller::input_sequence_poller() noexcept : + m_sequence(), + m_last_ticks(0), + m_modified(false) +{ +} + + +input_sequence_poller::~input_sequence_poller() +{ +} + + +void input_sequence_poller::start() +{ + // start with an empty sequence + m_sequence.reset(); + + // reset the recording count and the clock + m_last_ticks = 0; + m_modified = false; + do_start(); +} + + +void input_sequence_poller::start(input_seq const &startseq) +{ + // grab the starting sequence to append to, and append an OR if it isn't empty + m_sequence = startseq; + if (input_seq::end_code != m_sequence[0]) + m_sequence += input_seq::or_code; + + // reset the recording count and the clock + m_last_ticks = 0; + m_modified = false; + do_start(); +} + + +bool input_sequence_poller::poll() +{ + // if we got a new code to append it, append it and reset the timer + input_code const newcode = do_poll(); + osd_ticks_t const newticks = osd_ticks(); + if (INPUT_CODE_INVALID != newcode) + { + m_sequence += newcode; + m_last_ticks = newticks; + m_modified = true; + } + + // if we've recorded at least one item and one second has passed, we're done + if (m_last_ticks && ((m_last_ticks + osd_ticks_per_second()) < newticks)) + return true; + + // return false to indicate we are still polling + return false; +} + + + +axis_sequence_poller::axis_sequence_poller(input_manager &manager) noexcept : + input_sequence_poller(), + m_code_poller(manager) +{ +} + + +void axis_sequence_poller::do_start() +{ + // wait for any inputs that are already active to be released + m_code_poller.reset(); + for (input_code dummycode = KEYCODE_ENTER; INPUT_CODE_INVALID != dummycode; ) + dummycode = m_code_poller.poll(); +} + + +input_code axis_sequence_poller::do_poll() +{ + // absolute/relative case: see if we have an analog change of sufficient amount + int const curlen = m_sequence.length(); + input_code lastcode = m_sequence[curlen - 1]; + bool const has_or = input_seq::or_code == lastcode; + if (has_or) + lastcode = m_sequence[curlen - 2]; + input_code newcode = m_code_poller.poll(); + + // if not empty, see if it's the same control again to cycle modifiers + if ((INPUT_CODE_INVALID != newcode) && curlen) + { + input_item_class const newclass = newcode.item_class(); + input_code last_nomodifier = lastcode; + last_nomodifier.set_item_modifier(ITEM_MODIFIER_NONE); + if (newcode == last_nomodifier) + { + if (ITEM_CLASS_ABSOLUTE == newclass) + { + // increment the modifier, wrapping back to none + switch (lastcode.item_modifier()) + { + case ITEM_MODIFIER_NONE: + newcode.set_item_modifier(ITEM_MODIFIER_POS); + break; + case ITEM_MODIFIER_POS: + newcode.set_item_modifier(ITEM_MODIFIER_NEG); + break; + case ITEM_MODIFIER_NEG: + newcode.set_item_modifier(ITEM_MODIFIER_REVERSE); + break; + default: + case ITEM_MODIFIER_REVERSE: + newcode.set_item_modifier(ITEM_MODIFIER_NONE); + break; + } + + // back up over the previous code so we can re-append + if (has_or) + m_sequence.backspace(); + m_sequence.backspace(); + } + else if (ITEM_CLASS_RELATIVE == newclass) + { + // increment the modifier, wrapping back to none + switch (lastcode.item_modifier()) + { + case ITEM_MODIFIER_NONE: + newcode.set_item_modifier(ITEM_MODIFIER_REVERSE); + break; + default: + case ITEM_MODIFIER_REVERSE: + newcode.set_item_modifier(ITEM_MODIFIER_NONE); + break; + } + + // back up over the previous code so we can re-append + if (has_or) + m_sequence.backspace(); + m_sequence.backspace(); + } + else if (!has_or && (ITEM_CLASS_SWITCH == newclass)) + { + // back up over the existing code + m_sequence.backspace(); + + // if there was a NOT preceding it, delete it as well, otherwise append a fresh one + if (m_sequence[curlen - 2] == input_seq::not_code) + m_sequence.backspace(); + else + m_sequence += input_seq::not_code; + } + } + else if (!has_or && (ITEM_CLASS_SWITCH == newclass)) + { + // ignore switches following axes + if (ITEM_CLASS_SWITCH != lastcode.item_class()) + { + // hack to stop it timing out so user can cancel + m_sequence.backspace(); + newcode = lastcode; + } + } + } + + // hack to stop it timing out before assigning an axis + if (ITEM_CLASS_SWITCH == newcode.item_class()) + { + m_sequence += newcode; + set_modified(); + return INPUT_CODE_INVALID; + } + else + { + return newcode; + } +} + + + +switch_sequence_poller::switch_sequence_poller(input_manager &manager) noexcept : + input_sequence_poller(), + m_code_poller(manager) +{ +} + + +void switch_sequence_poller::do_start() +{ + // wait for any inputs that are already active to be released + m_code_poller.reset(); + for (input_code dummycode = KEYCODE_ENTER; INPUT_CODE_INVALID != dummycode; ) + dummycode = m_code_poller.poll(); +} + + +input_code switch_sequence_poller::do_poll() +{ + // switch case: see if we have a new code to process + int const curlen = m_sequence.length(); + input_code lastcode = m_sequence[curlen - 1]; + input_code newcode = m_code_poller.poll(); + if (INPUT_CODE_INVALID != newcode) + { + // if code is duplicate, toggle the NOT state on the code + if (curlen && (newcode == lastcode)) + { + // back up over the existing code + m_sequence.backspace(); + + // if there was a NOT preceding it, delete it as well, otherwise append a fresh one + if (m_sequence[curlen - 2] == input_seq::not_code) + m_sequence.backspace(); + else + m_sequence += input_seq::not_code; + } + } + return newcode; +} diff --git a/src/frontend/mame/iptseqpoll.h b/src/frontend/mame/iptseqpoll.h new file mode 100644 index 00000000000..c43dd397705 --- /dev/null +++ b/src/frontend/mame/iptseqpoll.h @@ -0,0 +1,123 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb,Aaron Giles +/*************************************************************************** + + iptseqpoll.h + + Helper for letting the user select input sequences. + +***************************************************************************/ +#ifndef MAME_FRONTEND_IPTSEQPOLL_H +#define MAME_FRONTEND_IPTSEQPOLL_H + +#pragma once + +#include <utility> +#include <vector> + + +class input_code_poller +{ +public: + virtual ~input_code_poller(); + + virtual void reset(); + virtual input_code poll() = 0; + +protected: + input_code_poller(input_manager &manager) noexcept; + + bool code_pressed_once(input_code code, bool moved); + + input_manager &m_manager; + std::vector<std::pair<input_device_item *, s32> > m_axis_memory; + std::vector<input_code> m_switch_memory; +}; + + +class axis_code_poller : public input_code_poller +{ +public: + axis_code_poller(input_manager &manager) noexcept; + + virtual void reset() override; + virtual input_code poll() override; + +private: + std::vector<bool> m_axis_active; +}; + + +class switch_code_poller : public input_code_poller +{ +public: + switch_code_poller(input_manager &manager) noexcept; + + virtual input_code poll() override; +}; + + +class keyboard_code_poller : public input_code_poller +{ +public: + keyboard_code_poller(input_manager &manager) noexcept; + + virtual input_code poll() override; +}; + + +class input_sequence_poller +{ +public: + virtual ~input_sequence_poller(); + + void start(); + void start(input_seq const &startseq); + bool poll(); + + input_seq const &sequence() const noexcept { return m_sequence; } + bool valid() const noexcept { return m_sequence.is_valid(); } + bool modified() const noexcept { return m_modified; } + +protected: + input_sequence_poller() noexcept; + + void set_modified() noexcept { m_modified = true; } + + input_seq m_sequence; + +private: + virtual void do_start() = 0; + virtual input_code do_poll() = 0; + + osd_ticks_t m_last_ticks; + bool m_modified; +}; + + +class axis_sequence_poller : public input_sequence_poller +{ +public: + axis_sequence_poller(input_manager &manager) noexcept; + +private: + virtual void do_start() override; + virtual input_code do_poll() override; + + axis_code_poller m_code_poller; +}; + + +class switch_sequence_poller : public input_sequence_poller +{ +public: + switch_sequence_poller(input_manager &manager) noexcept; + +private: + virtual void do_start() override; + virtual input_code do_poll() override; + + switch_code_poller m_code_poller; +}; + +#endif // MAME_FRONTEND_IPTSEQPOLL_H diff --git a/src/frontend/mame/language.cpp b/src/frontend/mame/language.cpp index 016a32f1811..3e660130ffd 100644 --- a/src/frontend/mame/language.cpp +++ b/src/frontend/mame/language.cpp @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:Miodrag Milanovic +// copyright-holders:Vas Crabb /*************************************************************************** language.cpp @@ -9,71 +9,34 @@ ***************************************************************************/ #include "emu.h" -#include "emuopts.h" - -static std::unordered_map<std::string, std::string> g_translation; +#include "language.h" -const char *lang_translate(const char *word) -{ - if (g_translation.find(word) == g_translation.end()) - { - return word; - } - return g_translation[word].c_str(); -} +#include "emuopts.h" +#include "fileio.h" -const uint32_t MO_MAGIC = 0x950412de; -const uint32_t MO_MAGIC_REVERSED = 0xde120495; +#include "corestr.h" -inline uint32_t endianchange(uint32_t value) { - uint32_t b0 = (value >> 0) & 0xff; - uint32_t b1 = (value >> 8) & 0xff; - uint32_t b2 = (value >> 16) & 0xff; - uint32_t b3 = (value >> 24) & 0xff; +#include <string> - return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; -} void load_translation(emu_options &m_options) { - g_translation.clear(); - emu_file file(m_options.language_path(), OPEN_FLAG_READ); - auto name = std::string(m_options.language()); + util::unload_translation(); + + std::string name = m_options.language(); + if (name.empty()) + return; + strreplace(name, " ", "_"); strreplace(name, "(", ""); strreplace(name, ")", ""); - if (file.open(name.c_str(), PATH_SEPARATOR "strings.mo") == osd_file::error::NONE) + emu_file file(m_options.language_path(), OPEN_FLAG_READ); + if (file.open(name + PATH_SEPARATOR "strings.mo")) { - uint64_t size = file.size(); - uint32_t *buffer = global_alloc_array(uint32_t, size / 4 + 1); - file.read(buffer, size); - file.close(); - - if (buffer[0] != MO_MAGIC && buffer[0] != MO_MAGIC_REVERSED) - { - global_free_array(buffer); - return; - } - if (buffer[0] == MO_MAGIC_REVERSED) - { - for (auto i = 0; i < (size / 4) + 1; ++i) - { - buffer[i] = endianchange(buffer[i]); - } - } - - uint32_t number_of_strings = buffer[2]; - uint32_t original_table_offset = buffer[3] >> 2; - uint32_t translation_table_offset = buffer[4] >> 2; - - const char *data = reinterpret_cast<const char*>(buffer); - - for (auto i = 1; i < number_of_strings; ++i) - { - std::string original = (const char *)data + buffer[original_table_offset + 2 * i + 1]; - std::string translation = (const char *)data + buffer[translation_table_offset + 2 * i + 1]; - g_translation.emplace(std::move(original), std::move(translation)); - } - global_free_array(buffer); + osd_printf_error("Error opening translation file %s\n", name); + return; } + + osd_printf_verbose("Loading translation file %s\n", file.fullpath()); + util::load_translation(file); } diff --git a/src/frontend/mame/language.h b/src/frontend/mame/language.h index 73adbb88269..100d36774ff 100644 --- a/src/frontend/mame/language.h +++ b/src/frontend/mame/language.h @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:Miodrag Milanovic +// copyright-holders:Vas Crabb /*************************************************************************** language.h @@ -12,20 +12,11 @@ #pragma once -#ifndef __EMU_H__ -#error Dont include this file directly; include emu.h instead. -#endif +#include "util/language.h" -//************************************************************************** -// LOCALIZATION SUPPORT -//************************************************************************** -#define _(param) lang_translate(param) -// Fake one to make possible using it in static text definitions, on those -// lang_translate must be called afterwards -#define __(param) param +void load_translation(emu_options &options); -void load_translation(emu_options &option); -const char *lang_translate(const char *word); +using util::lang_translate; #endif // MAME_FRONTEND_MAME_LANGUAGE_H diff --git a/src/frontend/mame/luaengine.cpp b/src/frontend/mame/luaengine.cpp index 4c5f9d0f42d..ece3c0ceccb 100644 --- a/src/frontend/mame/luaengine.cpp +++ b/src/frontend/mame/luaengine.cpp @@ -8,538 +8,483 @@ ***************************************************************************/ -#include <thread> -#include <lua.hpp> #include "emu.h" +#include "luaengine.ipp" + #include "mame.h" +#include "pluginopts.h" +#include "ui/pluginopt.h" +#include "ui/ui.h" + +#include "imagedev/cassette.h" + #include "debugger.h" -#include "debug/debugcon.h" -#include "debug/debugcpu.h" -#include "debug/textbuf.h" #include "drivenum.h" #include "emuopts.h" -#include "ui/ui.h" -#include "ui/pluginopt.h" -#include "luaengine.h" +#include "fileio.h" +#include "inputdev.h" #include "natkeyboard.h" -#include "uiinput.h" -#include "pluginopts.h" +#include "screen.h" #include "softlist.h" -#include "inputdev.h" +#include "speaker.h" +#include "uiinput.h" + +#include "corestr.h" + +#include <algorithm> +#include <condition_variable> +#include <cstring> +#include <mutex> +#include <thread> -#ifdef __clang__ -#pragma clang diagnostic ignored "-Wshift-count-overflow" -#endif -#if defined(_MSC_VER) -#pragma warning(disable:4503) -#endif //************************************************************************** // LUA ENGINE //************************************************************************** -extern "C" { - int luaopen_zlib(lua_State *L); - int luaopen_lfs(lua_State *L); - int luaopen_linenoise(lua_State *L); - int luaopen_lsqlite3(lua_State *L); -} +int luaopen_zlib(lua_State *const L); +extern "C" int luaopen_lfs(lua_State *L); +int luaopen_linenoise(lua_State *L); +int luaopen_lsqlite3(lua_State *L); + + +template <typename T> +struct lua_engine::devenum +{ + template <typename... U> devenum(device_t &d, U &&... args) : device(d), iter(d, std::forward<U>(args)...) { } + + device_t &device; + T iter; + int count = -1; +}; + + +namespace { -namespace sol +struct save_item { - class buffer + void *base; + unsigned int size; + unsigned int count; + unsigned int valcount; + unsigned int blockcount; + unsigned int stride; +}; + +struct thread_context +{ +private: + sol::state m_state; + std::string m_result; + std::mutex m_guard; + std::condition_variable m_sync; + bool m_busy = false; + +public: + bool m_yield = false; + + thread_context() { - public: - // sol does lua_settop(0), save userdata buffer in registry if necessary - buffer(int size, lua_State *L) + m_state.open_libraries(); + m_state["package"]["preload"]["zlib"] = &luaopen_zlib; + m_state["package"]["preload"]["lfs"] = &luaopen_lfs; + m_state["package"]["preload"]["linenoise"] = &luaopen_linenoise; + m_state.set_function("yield", + [this] () + { + std::unique_lock<std::mutex> yield_lock(m_guard); + m_result = m_state["status"]; + m_yield = true; + m_sync.wait(yield_lock); + m_yield = false; + }); + } + + bool start(sol::this_state s, char const *scr) + { + std::unique_lock<std::mutex> caller_lock(m_guard); + if (m_busy) + return false; + + sol::load_result res = m_state.load(scr); + if (!res.valid()) { - ptr = luaL_buffinitsize(L, &buff, size); - len = size; - if(buff.b != buff.initb) - { - lua_pushvalue(L, -1); - lua_setfield(L, LUA_REGISTRYINDEX, "sol::buffer_temp"); - } + sol::error err = res; + luaL_error(s, err.what()); + return false; // unreachable - luaL_error throws } - ~buffer() + + std::thread th( + [this, func = res.get<sol::protected_function>()] () + { + auto ret = func(); + std::unique_lock<std::mutex> result_lock(m_guard); + if (ret.valid()) + { + auto result = ret.get<std::optional<char const *> >(); + if (!result) + osd_printf_error("[LUA ERROR] in thread: return value must be string\n"); + else if (!*result) + m_result.clear(); + else + m_result = *result; + } + else + { + sol::error err = ret; + osd_printf_error("[LUA ERROR] in thread: %s\n", err.what()); + } + m_busy = false; + }); + m_busy = true; + m_yield = false; + th.detach(); // FIXME: this is unsafe as the thread function modifies members of the object + return true; + } + + void resume(char const *val) + { + std::unique_lock<std::mutex> lock(m_guard); + if (m_yield) { - lua_State *L = buff.L; - if(lua_getfield(L, LUA_REGISTRYINDEX, "sol::buffer_temp") != LUA_TNIL) - { - lua_pushnil(L); - lua_setfield(L, LUA_REGISTRYINDEX, "sol::buffer_temp"); - } + if (val) + m_state["status"] = val; else - lua_pop(L, -1); - - luaL_pushresultsize(&buff, len); + m_state["status"] = sol::lua_nil; + m_sync.notify_all(); } - void set_len(int size) { len = size; } - int get_len() { return len; } - char *get_ptr() { return ptr; } - private: - luaL_Buffer buff; - int len; - char *ptr; - }; - template<> - struct is_container<core_options> : std::false_type {}; // don't convert core_optons to a table directly - namespace stack + } + + char const *result() { - template <> - struct pusher<osd_file::error> - { - static int push(lua_State *L, osd_file::error error) - { - const char *strerror; - switch(error) - { - case osd_file::error::NONE: - return stack::push(L, sol::nil); - case osd_file::error::FAILURE: - strerror = "failure"; - break; - case osd_file::error::OUT_OF_MEMORY: - strerror = "out_of_memory"; - break; - case osd_file::error::NOT_FOUND: - strerror = "not_found"; - break; - case osd_file::error::ACCESS_DENIED: - strerror = "access_denied"; - break; - case osd_file::error::ALREADY_OPEN: - strerror = "already_open"; - break; - case osd_file::error::TOO_MANY_FILES: - strerror = "too_many_files"; - break; - case osd_file::error::INVALID_DATA: - strerror = "invalid_data"; - break; - case osd_file::error::INVALID_ACCESS: - strerror = "invalid_access"; - break; - default: - strerror = "unknown_error"; - break; - } - return stack::push(L, strerror); - } - }; - template <> - struct checker<sol::buffer *> - { - template <typename Handler> - static bool check (lua_State* L, int index, Handler&& handler, record& tracking) - { - return stack::check<int>(L, index, handler); - } - }; - template <> - struct getter<sol::buffer *> - { - static sol::buffer *get(lua_State* L, int index, record& tracking) - { - return new sol::buffer(stack::get<int>(L, index), L); - } - }; - template <> - struct pusher<sol::buffer *> - { - static int push(lua_State* L, sol::buffer *buff) - { - delete buff; - return 1; - } - }; - template <> - struct pusher<map_handler_type> - { - static int push(lua_State *L, map_handler_type type) - { - const char *typestr; - switch(type) - { - case AMH_NONE: - typestr = "none"; - break; - case AMH_RAM: - typestr = "ram"; - break; - case AMH_ROM: - typestr = "rom"; - break; - case AMH_NOP: - typestr = "nop"; - break; - case AMH_UNMAP: - typestr = "unmap"; - break; - case AMH_DEVICE_DELEGATE: - case AMH_DEVICE_DELEGATE_M: - case AMH_DEVICE_DELEGATE_S: - case AMH_DEVICE_DELEGATE_SM: - case AMH_DEVICE_DELEGATE_MO: - case AMH_DEVICE_DELEGATE_SMO: - typestr = "delegate"; - break; - case AMH_PORT: - typestr = "port"; - break; - case AMH_BANK: - typestr = "bank"; - break; - case AMH_DEVICE_SUBMAP: - typestr = "submap"; - break; - default: - typestr = "unknown"; - break; - } - return stack::push(L, typestr); - } - }; + std::unique_lock<std::mutex> lock(m_guard); + if (m_busy && !m_yield) + return ""; + else + return m_result.c_str(); } -} + bool busy() const + { + return m_busy; + } +}; -//------------------------------------------------- -// parse_seq_type - parses a string into an input_seq_type -//------------------------------------------------- -static input_seq_type parse_seq_type(const std::string &s) +struct device_state_entries { - input_seq_type result = SEQ_TYPE_STANDARD; - if (s == "increment") - result = SEQ_TYPE_INCREMENT; - else if (s == "decrement") - result = SEQ_TYPE_DECREMENT; - return result; -} + device_state_entries(device_state_interface const &s) : state(s) { } + device_state_interface::entrylist_type const &items() { return state.state_entries(); } + static device_state_entry const &unwrap(device_state_interface::entrylist_type::const_iterator const &it) { return **it; } + static int push_key(lua_State *L, device_state_interface::entrylist_type::const_iterator const &it, std::size_t ix) { return sol::stack::push_reference(L, (*it)->symbol()); } -//------------------------------------------------- -// mem_read - templated memory readers for <sign>,<size> -// -> manager:machine().devices[":maincpu"].spaces["program"]:read_i8(0xC000) -//------------------------------------------------- + device_state_interface const &state; +}; -template <typename T> -T lua_engine::addr_space::mem_read(offs_t address) + +struct image_interface_formats { - T mem_content = 0; - switch(sizeof(mem_content) * 8) { - case 8: - mem_content = space.read_byte(address); - break; - case 16: - if (WORD_ALIGNED(address)) { - mem_content = space.read_word(address); - } else { - mem_content = space.read_word_unaligned(address); - } - break; - case 32: - if (DWORD_ALIGNED(address)) { - mem_content = space.read_dword(address); - } else { - mem_content = space.read_dword_unaligned(address); - } - break; - case 64: - if (QWORD_ALIGNED(address)) { - mem_content = space.read_qword(address); - } else { - mem_content = space.read_qword_unaligned(address); - } - break; - default: - break; - } + image_interface_formats(device_image_interface &i) : image(i) { } + device_image_interface::formatlist_type const &items() { return image.formatlist(); } - return mem_content; -} + static image_device_format const &unwrap(device_image_interface::formatlist_type::const_iterator const &it) { return **it; } + static int push_key(lua_State *L, device_image_interface::formatlist_type::const_iterator const &it, std::size_t ix) { return sol::stack::push_reference(L, (*it)->name()); } + + device_image_interface ℑ +}; + + +struct plugin_options_plugins +{ + plugin_options_plugins(plugin_options &o) : options(o) { } + std::list<plugin_options::plugin> &items() { return options.plugins(); } + + static plugin_options::plugin const &unwrap(std::list<plugin_options::plugin>::const_iterator const &it) { return *it; } + static int push_key(lua_State *L, std::list<plugin_options::plugin>::const_iterator const &it, std::size_t ix) { return sol::stack::push_reference(L, it->m_name); } + + plugin_options &options; +}; -//------------------------------------------------- -// mem_write - templated memory writer for <sign>,<size> -// -> manager:machine().devices[":maincpu"].spaces["program"]:write_u16(0xC000, 0xF00D) -//------------------------------------------------- template <typename T> -void lua_engine::addr_space::mem_write(offs_t address, T val) +void resume_tasks(lua_State *L, T &&tasks, bool status) { - switch(sizeof(val) * 8) { - case 8: - space.write_byte(address, val); - break; - case 16: - if (WORD_ALIGNED(address)) { - space.write_word(address, val); - } else { - space.write_word_unaligned(address, val); - } - break; - case 32: - if (DWORD_ALIGNED(address)) { - space.write_dword(address, val); - } else { - space.write_dword_unaligned(address, val); - } - break; - case 64: - if (QWORD_ALIGNED(address)) { - space.write_qword(address, val); - } else { - space.write_qword_unaligned(address, val); - } - break; - default: - break; + for (int ref : tasks) + { + lua_rawgeti(L, LUA_REGISTRYINDEX, ref); + lua_State *const thread = lua_tothread(L, -1); + lua_pop(L, 1); + lua_pushboolean(thread, status ? 1 : 0); + int nresults = 0; + int const stat = lua_resume(thread, nullptr, 1, &nresults); + if ((stat != LUA_OK) && (stat != LUA_YIELD)) + { + osd_printf_error("[LUA ERROR] in resume: %s\n", lua_tostring(thread, -1)); + lua_pop(thread, 1); + } + else + { + lua_pop(thread, nresults); + } + luaL_unref(L, LUA_REGISTRYINDEX, ref); } } -//------------------------------------------------- -// log_mem_read - templated logical memory readers for <sign>,<size> -// -> manager:machine().devices[":maincpu"].spaces["program"]:read_log_i8(0xC000) -//------------------------------------------------- +} // anonymous namespace + + +namespace sol { + +template <> struct is_container<device_state_entries> : std::true_type { }; +template <> struct is_container<image_interface_formats> : std::true_type { }; +template <> struct is_container<plugin_options_plugins> : std::true_type { }; + template <typename T> -T lua_engine::addr_space::log_mem_read(offs_t address) +struct usertype_container<lua_engine::devenum<T> > : lua_engine::immutable_collection_helper<lua_engine::devenum<T>, T> { - T mem_content = 0; - if(!dev.translate(space.spacenum(), TRANSLATE_READ_DEBUG, address)) - return 0; - - switch(sizeof(mem_content) * 8) { - case 8: - mem_content = space.read_byte(address); - break; - case 16: - if (WORD_ALIGNED(address)) { - mem_content = space.read_word(address); - } else { - mem_content = space.read_word_unaligned(address); - } - break; - case 32: - if (DWORD_ALIGNED(address)) { - mem_content = space.read_dword(address); - } else { - mem_content = space.read_dword_unaligned(address); - } - break; - case 64: - if (QWORD_ALIGNED(address)) { - mem_content = space.read_qword(address); - } else { - mem_content = space.read_qword_unaligned(address); - } - break; - default: - break; +private: + using enumerator = lua_engine::devenum<T>; + + template <bool Indexed> + static int next_pairs(lua_State *L) + { + typename usertype_container::indexed_iterator &i(stack::unqualified_get<user<typename usertype_container::indexed_iterator> >(L, 1)); + if (i.src.end() == i.it) + return stack::push(L, lua_nil); + int result; + if constexpr (Indexed) + result = stack::push(L, i.ix + 1); + else + result = stack::push(L, i.it->tag()); + result += stack::push_reference(L, *i.it); + ++i; + return result; } - return mem_content; -} + template <bool Indexed> + static int start_pairs(lua_State *L) + { + enumerator &self(usertype_container::get_self(L)); + stack::push(L, next_pairs<Indexed>); + stack::push<user<typename usertype_container::indexed_iterator> >(L, self.iter, self.iter.begin()); + stack::push(L, lua_nil); + return 3; + } -//------------------------------------------------- -// log_mem_write - templated logical memory writer for <sign>,<size> -// -> manager:machine().devices[":maincpu"].spaces["program"]:write_log_u16(0xC000, 0xF00D) -//------------------------------------------------- +public: + static int at(lua_State *L) + { + enumerator &self(usertype_container::get_self(L)); + std::ptrdiff_t const index(stack::unqualified_get<std::ptrdiff_t>(L, 2)); + auto const dev(self.iter.byindex(index - 1)); + if (dev) + return stack::push_reference(L, *dev); + else + return stack::push(L, lua_nil); + } -template <typename T> -void lua_engine::addr_space::log_mem_write(offs_t address, T val) -{ - if(!dev.translate(space.spacenum(), TRANSLATE_WRITE_DEBUG, address)) - return; - - switch(sizeof(val) * 8) { - case 8: - space.write_byte(address, val); - break; - case 16: - if (WORD_ALIGNED(address)) { - space.write_word(address, val); - } else { - space.write_word_unaligned(address, val); - } - break; - case 32: - if (DWORD_ALIGNED(address)) { - space.write_dword(address, val); - } else { - space.write_dword_unaligned(address, val); - } - break; - case 64: - if (QWORD_ALIGNED(address)) { - space.write_qword(address, val); - } else { - space.write_qword_unaligned(address, val); - } - break; - default: - break; + static int get(lua_State *L) + { + enumerator &self(usertype_container::get_self(L)); + char const *const tag(stack::unqualified_get<char const *>(L)); + device_t *const dev(self.device.subdevice(tag)); + if (dev) + { + auto *const check(T(*dev, 0).first()); + bool match; + if constexpr (std::is_base_of_v<device_t, decltype(*check)>) + match = check && (static_cast<device_t *>(check) == dev); + else if constexpr (std::is_base_of_v<device_interface, decltype(*check)>) + match = check && (&check->device() == dev); + else + match = check && (dynamic_cast<device_t *>(check) == dev); + if (match) + return stack::push_reference(L, *check); + } + return stack::push(L, lua_nil); } -} -//------------------------------------------------- -// mem_direct_read - templated direct memory readers for <sign>,<size> -// -> manager:machine().devices[":maincpu"].spaces["program"]:read_direct_i8(0xC000) -//------------------------------------------------- + static int index_get(lua_State *L) + { + return get(L); + } -template <typename T> -T lua_engine::addr_space::direct_mem_read(offs_t address) -{ - T mem_content = 0; - offs_t lowmask = space.data_width() / 8 - 1; - for(int i = 0; i < sizeof(T); i++) + static int index_of(lua_State *L) { - int addr = space.endianness() == ENDIANNESS_LITTLE ? address + sizeof(T) - 1 - i : address + i; - uint8_t *base = (uint8_t *)space.get_read_ptr(addr & ~lowmask); - if(!base) - continue; - mem_content <<= 8; - if(space.endianness() == ENDIANNESS_BIG) - mem_content |= base[BYTE8_XOR_BE(addr) & lowmask]; + enumerator &self(usertype_container::get_self(L)); + auto &dev(stack::unqualified_get<decltype(*self.iter.first())>(L, 2)); + std::ptrdiff_t found(self.iter.indexof(dev)); + if (0 > found) + return stack::push(L, lua_nil); else - mem_content |= base[BYTE8_XOR_LE(addr) & lowmask]; + return stack::push(L, found + 1); } - return mem_content; -} + static int size(lua_State *L) + { + enumerator &self(usertype_container::get_self(L)); + if (0 > self.count) + self.count = self.iter.count(); + return stack::push(L, self.count); + } -//------------------------------------------------- -// mem_direct_write - templated memory writer for <sign>,<size> -// -> manager:machine().devices[":maincpu"].spaces["program"]:write_direct_u16(0xC000, 0xF00D) -//------------------------------------------------- + static int empty(lua_State *L) + { + enumerator &self(usertype_container::get_self(L)); + if (0 > self.count) + self.count = self.iter.count(); + return stack::push(L, !self.count); + } -template <typename T> -void lua_engine::addr_space::direct_mem_write(offs_t address, T val) + static int next(lua_State *L) { return stack::push(L, next_pairs<false>); } + static int pairs(lua_State *L) { return start_pairs<false>(L); } + static int ipairs(lua_State *L) { return start_pairs<true>(L); } +}; + + +template <> +struct usertype_container<device_state_entries> : lua_engine::immutable_sequence_helper<device_state_entries, device_state_interface::entrylist_type const, device_state_interface::entrylist_type::const_iterator> { - offs_t lowmask = space.data_width() / 8 - 1; - for(int i = 0; i < sizeof(T); i++) +private: + using entrylist_type = device_state_interface::entrylist_type; + +public: + static int get(lua_State *L) { - int addr = space.endianness() == ENDIANNESS_BIG ? address + sizeof(T) - 1 - i : address + i; - uint8_t *base = (uint8_t *)space.get_read_ptr(addr & ~lowmask); - if(!base) - continue; - if(space.endianness() == ENDIANNESS_BIG) - base[BYTE8_XOR_BE(addr) & lowmask] = val & 0xff; + device_state_entries &self(get_self(L)); + char const *const symbol(stack::unqualified_get<char const *>(L)); + auto const found(std::find_if( + self.state.state_entries().begin(), + self.state.state_entries().end(), + [&symbol] (std::unique_ptr<device_state_entry> const &v) { return !std::strcmp(v->symbol(), symbol); })); + if (self.state.state_entries().end() != found) + return stack::push_reference(L, std::cref(**found)); else - base[BYTE8_XOR_LE(addr) & lowmask] = val & 0xff; - val >>= 8; + return stack::push(L, lua_nil); } -} -//------------------------------------------------- -// region_read - templated region readers for <sign>,<size> -// -> manager:machine():memory().regions[":maincpu"]:read_i8(0xC000) -//------------------------------------------------- + static int index_get(lua_State *L) + { + return get(L); + } +}; -template <typename T> -T lua_engine::region_read(memory_region ®ion, offs_t address) + +template <> +struct usertype_container<image_interface_formats> : lua_engine::immutable_sequence_helper<image_interface_formats, device_image_interface::formatlist_type const, device_image_interface::formatlist_type::const_iterator> { - T mem_content = 0; - offs_t lowmask = region.bytewidth() - 1; - for(int i = 0; i < sizeof(T); i++) +private: + using format_list = device_image_interface::formatlist_type; + +public: + static int get(lua_State *L) { - int addr = region.endianness() == ENDIANNESS_LITTLE ? address + sizeof(T) - 1 - i : address + i; - if(addr >= region.bytes()) - continue; - mem_content <<= 8; - if(region.endianness() == ENDIANNESS_BIG) - mem_content |= region.as_u8((BYTE8_XOR_BE(addr) & lowmask) | (addr & ~lowmask)); + image_interface_formats &self(get_self(L)); + char const *const name(stack::unqualified_get<char const *>(L)); + auto const found(std::find_if( + self.image.formatlist().begin(), + self.image.formatlist().end(), + [&name] (std::unique_ptr<image_device_format> const &v) { return v->name() == name; })); + if (self.image.formatlist().end() != found) + return stack::push_reference(L, std::cref(**found)); else - mem_content |= region.as_u8((BYTE8_XOR_LE(addr) & lowmask) | (addr & ~lowmask)); + return stack::push(L, lua_nil); } - return mem_content; -} + static int index_get(lua_State *L) + { + return get(L); + } +}; -//------------------------------------------------- -// region_write - templated region writer for <sign>,<size> -// -> manager:machine():memory().regions[":maincpu"]:write_u16(0xC000, 0xF00D) -//------------------------------------------------- -template <typename T> -void lua_engine::region_write(memory_region ®ion, offs_t address, T val) +template <> +struct usertype_container<plugin_options_plugins> : lua_engine::immutable_sequence_helper<plugin_options_plugins, std::list<plugin_options::plugin> > { - offs_t lowmask = region.bytewidth() - 1; - for(int i = 0; i < sizeof(T); i++) +private: + using plugin_list = std::list<plugin_options::plugin>; + +public: + static int get(lua_State *L) { - int addr = region.endianness() == ENDIANNESS_BIG ? address + sizeof(T) - 1 - i : address + i; - if(addr >= region.bytes()) - continue; - if(region.endianness() == ENDIANNESS_BIG) - region.base()[(BYTE8_XOR_BE(addr) & lowmask) | (addr & ~lowmask)] = val & 0xff; + plugin_options_plugins &self(get_self(L)); + char const *const name(stack::unqualified_get<char const *>(L)); + auto const found(std::find_if( + self.options.plugins().begin(), + self.options.plugins().end(), + [&name] (plugin_options::plugin const &p) { return p.m_name == name; })); + if (self.options.plugins().end() != found) + return stack::push_reference(L, std::cref(*found)); else - region.base()[(BYTE8_XOR_LE(addr) & lowmask) | (addr & ~lowmask)] = val & 0xff; - val >>= 8; + return stack::push(L, lua_nil); + } + + static int index_get(lua_State *L) + { + return get(L); } +}; + +} // namespace sol + + +int sol_lua_push(sol::types<std::error_condition>, lua_State *L, std::error_condition &&value) +{ + if (!value) + return sol::stack::push(L, sol::lua_nil); + else + return sol::stack::push(L, value.message()); } -//------------------------------------------------- -// share_read - templated share readers for <sign>,<size> -// -> manager:machine():memory().shares[":maincpu"]:read_i8(0xC000) -//------------------------------------------------- -template <typename T> -T lua_engine::share_read(memory_share &share, offs_t address) +int sol_lua_push(sol::types<screen_type_enum>, lua_State *L, screen_type_enum &&value) { - T mem_content = 0; - offs_t lowmask = share.bytewidth() - 1; - uint8_t* ptr = (uint8_t*)share.ptr(); - for(int i = 0; i < sizeof(T); i++) + switch (value) { - int addr = share.endianness() == ENDIANNESS_LITTLE ? address + sizeof(T) - 1 - i : address + i; - if(addr >= share.bytes()) - continue; - mem_content <<= 8; - if(share.endianness() == ENDIANNESS_BIG) - mem_content |= ptr[(BYTE8_XOR_BE(addr) & lowmask) | (addr & ~lowmask)]; - else - mem_content |= ptr[(BYTE8_XOR_LE(addr) & lowmask) | (addr & ~lowmask)]; + case SCREEN_TYPE_INVALID: return sol::stack::push(L, "invalid"); + case SCREEN_TYPE_RASTER: return sol::stack::push(L, "raster"); + case SCREEN_TYPE_VECTOR: return sol::stack::push(L, "vector"); + case SCREEN_TYPE_LCD: return sol::stack::push(L, "lcd"); + case SCREEN_TYPE_SVG: return sol::stack::push(L, "svg"); } - - return mem_content; + return sol::stack::push(L, "unknown"); } + //------------------------------------------------- -// share_write - templated share writer for <sign>,<size> -// -> manager:machine():memory().shares[":maincpu"]:write_u16(0xC000, 0xF00D) +// process_snapshot_filename - processes a snapshot +// filename //------------------------------------------------- -template <typename T> -void lua_engine::share_write(memory_share &share, offs_t address, T val) +static std::string process_snapshot_filename(running_machine &machine, const char *s) { - offs_t lowmask = share.bytewidth() - 1; - uint8_t* ptr = (uint8_t*)share.ptr(); - for(int i = 0; i < sizeof(T); i++) + std::string result(s); + if (!osd_is_absolute_path(s)) { - int addr = share.endianness() == ENDIANNESS_BIG ? address + sizeof(T) - 1 - i : address + i; - if(addr >= share.bytes()) - continue; - if(share.endianness() == ENDIANNESS_BIG) - ptr[(BYTE8_XOR_BE(addr) & lowmask) | (addr & ~lowmask)] = val & 0xff; - else - ptr[(BYTE8_XOR_LE(addr) & lowmask) | (addr & ~lowmask)] = val & 0xff; - val >>= 8; + strreplace(result, "/", PATH_SEPARATOR); + strreplace(result, "%g", machine.basename()); } + return result; } + //------------------------------------------------- // lua_engine - constructor //------------------------------------------------- lua_engine::lua_engine() + : m_lua_state(nullptr) + , m_machine(nullptr) + , m_timer(nullptr) { - m_machine = nullptr; - m_lua_state = luaL_newstate(); /* create state */ + m_lua_state = luaL_newstate(); // create state m_sol_state = std::make_unique<sol::state_view>(m_lua_state); // create sol view + m_notifiers.emplace(); luaL_checkversion(m_lua_state); - lua_gc(m_lua_state, LUA_GCSTOP, 0); /* stop collector during initialization */ + lua_gc(m_lua_state, LUA_GCSTOP, 0); // stop collector during initialization sol().open_libraries(); // Get package.preload so we can store builtins in it. @@ -564,69 +509,86 @@ sol::object lua_engine::call_plugin(const std::string &name, sol::object in) { std::string field = "cb_" + name; sol::object obj = sol().registry()[field]; - if(obj.is<sol::protected_function>()) + if (obj.is<sol::protected_function>()) { auto res = invoke(obj.as<sol::protected_function>(), in); - if(!res.valid()) + if (!res.valid()) { sol::error err = res; osd_printf_error("[LUA ERROR] in call_plugin: %s\n", err.what()); } else + { return res.get<sol::object>(); + } } - return sol::make_object(sol(), sol::nil); + return sol::lua_nil; } -void lua_engine::menu_populate(const std::string &menu, std::vector<std::tuple<std::string, std::string, std::string>> &menu_list) +std::optional<long> lua_engine::menu_populate(const std::string &menu, std::vector<std::tuple<std::string, std::string, std::string> > &menu_list, std::string &flags) { std::string field = "menu_pop_" + menu; sol::object obj = sol().registry()[field]; - if(obj.is<sol::protected_function>()) + if (obj.is<sol::protected_function>()) { auto res = invoke(obj.as<sol::protected_function>()); - if(!res.valid()) + if (!res.valid()) { sol::error err = res; osd_printf_error("[LUA ERROR] in menu_populate: %s\n", err.what()); } else { - sol::table table = res; - for(auto &entry : table) + std::tuple<sol::table, std::optional<long>, std::optional<std::string> > table = res; + for (auto &entry : std::get<0>(table)) { - if(entry.second.is<sol::table>()) + if (entry.second.is<sol::table>()) { sol::table enttable = entry.second.as<sol::table>(); menu_list.emplace_back(enttable.get<std::string, std::string, std::string>(1, 2, 3)); } } + if (std::get<2>(table)) + flags = *std::get<2>(table); + else + flags.clear(); + return std::get<1>(table); } } + flags.clear(); + return std::nullopt; } -bool lua_engine::menu_callback(const std::string &menu, int index, const std::string &event) +std::pair<bool, std::optional<long> > lua_engine::menu_callback(const std::string &menu, int index, const std::string &event) { std::string field = "menu_cb_" + menu; - bool ret = false; + std::pair<std::optional<bool>, std::optional<long> > ret(false, std::nullopt); sol::object obj = sol().registry()[field]; - if(obj.is<sol::protected_function>()) + if (obj.is<sol::protected_function>()) { auto res = invoke(obj.as<sol::protected_function>(), index, event); - if(!res.valid()) + if (!res.valid()) { sol::error err = res; osd_printf_error("[LUA ERROR] in menu_callback: %s\n", err.what()); } else + { ret = res; + } } - return ret; + return std::make_pair(std::get<0>(ret) && *std::get<0>(ret), std::get<1>(ret)); +} + +void lua_engine::set_machine(running_machine *machine) +{ + m_machine = machine; } -int lua_engine::enumerate_functions(const char *id, std::function<bool(const sol::protected_function &func)> &&callback) +template <typename T> +size_t lua_engine::enumerate_functions(const char *id, T &&callback) { - int count = 0; + size_t count = 0; sol::object functable = sol().registry()[id]; if (functable.is<sol::table>()) { @@ -640,23 +602,24 @@ int lua_engine::enumerate_functions(const char *id, std::function<bool(const sol break; } } - return true; } return count; } -bool lua_engine::execute_function(const char *id) +template <typename... Params> bool lua_engine::execute_function(const char *id, Params&&... args) { - int count = enumerate_functions(id, [this](const sol::protected_function &func) - { - auto ret = invoke(func); - if(!ret.valid()) - { - sol::error err = ret; - osd_printf_error("[LUA ERROR] in execute_function: %s\n", err.what()); - } - return true; - }); + size_t count = enumerate_functions( + id, + [this, args...] (const sol::protected_function &func) + { + auto ret = invoke(func, args...); + if (!ret.valid()) + { + sol::error err = ret; + osd_printf_error("[LUA ERROR] in execute_function: %s\n", err.what()); + } + return true; + }); return count > 0; } @@ -674,13 +637,25 @@ void lua_engine::on_machine_prestart() execute_function("LUA_ON_PRESTART"); } -void lua_engine::on_machine_start() +void lua_engine::on_machine_reset() { + m_notifiers->on_reset(); execute_function("LUA_ON_START"); } void lua_engine::on_machine_stop() { + // clear waiting tasks + m_timer = nullptr; + std::vector<int> expired; + expired.reserve(m_waiting_tasks.size()); + for (auto const &waiting : m_waiting_tasks) + expired.emplace_back(waiting.second); + m_waiting_tasks.clear(); + resume_tasks(m_lua_state, expired, false); + expired.clear(); + + m_notifiers->on_stop(); execute_function("LUA_ON_STOP"); } @@ -691,27 +666,64 @@ void lua_engine::on_machine_before_load_settings() void lua_engine::on_machine_pause() { + m_notifiers->on_pause(); execute_function("LUA_ON_PAUSE"); } void lua_engine::on_machine_resume() { + m_notifiers->on_resume(); execute_function("LUA_ON_RESUME"); } void lua_engine::on_machine_frame() { + std::vector<int> tasks = std::move(m_frame_tasks); + m_frame_tasks.clear(); + resume_tasks(m_lua_state, tasks, true); // TODO: doesn't need to return anything + + m_notifiers->on_frame(); + execute_function("LUA_ON_FRAME"); } -void lua_engine::on_frame_done() +void lua_engine::on_machine_presave() { - execute_function("LUA_ON_FRAME_DONE"); + m_notifiers->on_presave(); } -void lua_engine::on_sound_update() +void lua_engine::on_machine_postload() { - execute_function("LUA_ON_SOUND_UPDATE"); + // clear waiting tasks + m_timer->reset(); + std::vector<int> expired; + expired.reserve(m_waiting_tasks.size()); + for (auto const &waiting : m_waiting_tasks) + expired.emplace_back(waiting.second); + m_waiting_tasks.clear(); + resume_tasks(m_lua_state, expired, false); + expired.clear(); + + m_notifiers->on_postload(); +} + +void lua_engine::on_sound_update(const std::map<std::string, std::vector<std::pair<const float *, int>>> &sound) +{ + auto stable = sol().create_table(); + for(const auto &e : sound) { + auto dtable = sol().create_table(); + u32 channels = e.second.size(); + for(u32 channel = 0; channel != channels; channel ++) { + const auto &info = e.second[channel]; + auto ctable = sol().create_table(sol::new_table(info.second)); + for(u32 i=0; i != info.second; i++) + ctable[i+1] = info.first[i]; + dtable[channel+1] = ctable; + } + stable[e.first] = dtable; + } + + execute_function("LUA_ON_SOUND_UPDATE", stable); } void lua_engine::on_periodic() @@ -722,32 +734,38 @@ void lua_engine::on_periodic() bool lua_engine::on_missing_mandatory_image(const std::string &instance_name) { bool handled = false; - enumerate_functions("LUA_ON_MANDATORY_FILE_MANAGER_OVERRIDE", [this, &instance_name, &handled](const sol::protected_function &func) - { - auto ret = invoke(func, instance_name); + enumerate_functions( + "LUA_ON_MANDATORY_FILE_MANAGER_OVERRIDE", + [this, &instance_name, &handled] (const sol::protected_function &func) + { + auto ret = invoke(func, instance_name); - if(!ret.valid()) - { - sol::error err = ret; - osd_printf_error("[LUA ERROR] in on_missing_mandatory_image: %s\n", err.what()); - } - else if (ret.get<bool>()) - { - handled = true; - } - return !handled; - }); + if (!ret.valid()) + { + sol::error err = ret; + osd_printf_error("[LUA ERROR] in on_missing_mandatory_image: %s\n", err.what()); + } + else if (ret.get<bool>()) + { + handled = true; + } + return !handled; + }); return handled; } void lua_engine::attach_notifiers() { machine().add_notifier(MACHINE_NOTIFY_RESET, machine_notify_delegate(&lua_engine::on_machine_prestart, this), true); - machine().add_notifier(MACHINE_NOTIFY_RESET, machine_notify_delegate(&lua_engine::on_machine_start, this)); + machine().add_notifier(MACHINE_NOTIFY_RESET, machine_notify_delegate(&lua_engine::on_machine_reset, this)); machine().add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(&lua_engine::on_machine_stop, this)); machine().add_notifier(MACHINE_NOTIFY_PAUSE, machine_notify_delegate(&lua_engine::on_machine_pause, this)); machine().add_notifier(MACHINE_NOTIFY_RESUME, machine_notify_delegate(&lua_engine::on_machine_resume, this)); machine().add_notifier(MACHINE_NOTIFY_FRAME, machine_notify_delegate(&lua_engine::on_machine_frame, this)); + machine().save().register_presave(save_prepost_delegate(FUNC(lua_engine::on_machine_presave), this)); + machine().save().register_postload(save_prepost_delegate(FUNC(lua_engine::on_machine_postload), this)); + + m_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(lua_engine::resume), this)); } //------------------------------------------------- @@ -757,6 +775,21 @@ void lua_engine::attach_notifiers() void lua_engine::initialize() { + static const enum_parser<movie_recording::format, 2> s_movie_recording_format_parser = + { + { "avi", movie_recording::format::AVI }, + { "mng", movie_recording::format::MNG } + }; + + + static const enum_parser<int, 3> s_seek_parser = + { + { "set", SEEK_SET }, + { "cur", SEEK_CUR }, + { "end", SEEK_END } + }; + + /* emu library * * emu.app_name() - return application name @@ -773,8 +806,6 @@ void lua_engine::initialize() * emu.unpause() - unpause emulation * emu.step() - advance one frame * emu.keypost(keys) - post keys to natural keyboard - * emu.wait(len) - wait for len within coroutine - * emu.lang_translate(str) - get translation for str if available * * emu.register_prestart(callback) - register callback before reset * emu.register_start(callback) - register callback after reset @@ -791,21 +822,97 @@ void lua_engine::initialize() * emu.register_before_load_settings(callback) - register callback to be run before settings are loaded * emu.show_menu(menu_name) - show menu by name and pause the machine * - * emu.print_verbose(str) - output to stderr at verbose level - * emu.print_error(str) - output to stderr at error level - * emu.print_info(str) - output to stderr at info level - * emu.print_debug(str) - output to stderr at debug level + * emu.device_enumerator(dev) - get device enumerator starting at arbitrary point in tree + * emu.screen_enumerator(dev) - get screen device enumerator starting at arbitrary point in tree + * emu.image_enumerator(dev) - get image interface enumerator starting at arbitrary point in tree + * emu.image_enumerator(dev) - get image interface enumerator starting at arbitrary point in tree */ sol::table emu = sol().create_named_table("emu"); + emu["wait"] = sol::yielding( + [this] (sol::this_state s, sol::object duration, sol::variadic_args args) + { + attotime delay; + if (!duration) + { + luaL_error(s, "waiting duration expected"); + } + else if (duration.is<attotime>()) + { + delay = duration.as<attotime>(); + } + else + { + auto seconds = duration.as<std::optional<double> >(); + if (!seconds) + luaL_error(s, "waiting duration must be attotime or number"); + delay = attotime::from_double(*seconds); + } + attotime const expiry = machine().time() + delay; + + int const ret = lua_pushthread(s); + if (ret == 1) + luaL_error(s, "cannot wait from outside coroutine"); + int const ref = luaL_ref(s, LUA_REGISTRYINDEX); + + auto const pos = std::upper_bound( + m_waiting_tasks.begin(), + m_waiting_tasks.end(), + expiry, + [] (auto const &a, auto const &b) { return a < b.first; }); + if (m_waiting_tasks.begin() == pos) + m_timer->reset(delay); + m_waiting_tasks.emplace(pos, expiry, ref); + + return sol::variadic_results(args.begin(), args.end()); + }); + emu["wait_next_update"] = sol::yielding( + [this] (sol::this_state s, sol::variadic_args args) + { + int const ret = lua_pushthread(s); + if (ret == 1) + luaL_error(s, "cannot wait from outside coroutine"); + m_update_tasks.emplace_back(luaL_ref(s, LUA_REGISTRYINDEX)); + return sol::variadic_results(args.begin(), args.end()); + }); + emu["wait_next_frame"] = sol::yielding( + [this] (sol::this_state s, sol::variadic_args args) + { + int const ret = lua_pushthread(s); + if (ret == 1) + luaL_error(s, "cannot wait from outside coroutine"); + m_frame_tasks.emplace_back(luaL_ref(s, LUA_REGISTRYINDEX)); + return sol::variadic_results(args.begin(), args.end()); + }); + emu.set_function("add_machine_reset_notifier", make_notifier_adder(m_notifiers->on_reset, "machine reset")); + emu.set_function("add_machine_stop_notifier", make_notifier_adder(m_notifiers->on_stop, "machine stop")); + emu.set_function("add_machine_pause_notifier", make_notifier_adder(m_notifiers->on_pause, "machine pause")); + emu.set_function("add_machine_resume_notifier", make_notifier_adder(m_notifiers->on_resume, "machine resume")); + emu.set_function("add_machine_frame_notifier", make_notifier_adder(m_notifiers->on_frame, "machine frame")); + emu.set_function("add_machine_pre_save_notifier", make_notifier_adder(m_notifiers->on_presave, "machine pre-save")); + emu.set_function("add_machine_post_load_notifier", make_notifier_adder(m_notifiers->on_postload, "machine post-load")); + emu.set_function("print_error", [] (const char *str) { osd_printf_error("%s\n", str); }); + emu.set_function("print_warning", [] (const char *str) { osd_printf_warning("%s\n", str); }); + emu.set_function("print_info", [] (const char *str) { osd_printf_info("%s\n", str); }); + emu.set_function("print_verbose", [] (const char *str) { osd_printf_verbose("%s\n", str); }); + emu.set_function("print_debug", [] (const char *str) { osd_printf_debug("%s\n", str); }); + emu["lang_translate"] = sol::overload( + static_cast<char const *(*)(char const *)>(&lang_translate), + static_cast<char const *(*)(char const *, char const *)>(&lang_translate)); + emu.set_function("subst_env", &osd_subst_env); + + // TODO: stuff below here needs to be rationalised emu["app_name"] = &emulator_info::get_appname_lower; emu["app_version"] = &emulator_info::get_bare_build_version; - emu["gamename"] = [this](){ return machine().system().type.fullname(); }; - emu["romname"] = [this](){ return machine().basename(); }; - emu["softname"] = [this]() { return machine().options().software_name(); }; - emu["keypost"] = [this](const char *keys){ machine().ioport().natkeyboard().post_utf8(keys); }; - emu["time"] = [this](){ return machine().time().as_double(); }; - emu["start"] = [this](const char *driver) { + emu["app_build"] = &emulator_info::get_build_version; + emu["gamename"] = [this] () { return machine().system().type.fullname(); }; + emu["romname"] = [this] () { return machine().basename(); }; + emu["softname"] = [this] () { return machine().options().software_name(); }; + emu["keypost"] = [this] (const char *keys) { machine().natkeyboard().post_utf8(keys); }; + emu["time"] = [this] () { return machine().time().as_double(); }; + emu["start"] = + [this](const char *driver) + { int i = driver_list::find(driver); if (i != -1) { @@ -814,64 +921,111 @@ void lua_engine::initialize() } return 1; }; - emu["pause"] = [this](){ return machine().pause(); }; - emu["unpause"] = [this](){ return machine().resume(); }; - emu["step"] = [this]() { + emu["pause"] = [this] () { return machine().pause(); }; + emu["unpause"] = [this] () { return machine().resume(); }; + emu["step"] = + [this] () + { mame_machine_manager::instance()->ui().set_single_step(true); machine().resume(); }; - emu["register_prestart"] = [this](sol::function func){ register_function(func, "LUA_ON_PRESTART"); }; - emu["register_start"] = [this](sol::function func){ register_function(func, "LUA_ON_START"); }; - emu["register_stop"] = [this](sol::function func){ register_function(func, "LUA_ON_STOP"); }; - emu["register_pause"] = [this](sol::function func){ register_function(func, "LUA_ON_PAUSE"); }; - emu["register_resume"] = [this](sol::function func){ register_function(func, "LUA_ON_RESUME"); }; - emu["register_frame"] = [this](sol::function func){ register_function(func, "LUA_ON_FRAME"); }; - emu["register_frame_done"] = [this](sol::function func){ register_function(func, "LUA_ON_FRAME_DONE"); }; - emu["register_sound_update"] = [this](sol::function func){ register_function(func, "LUA_ON_SOUND_UPDATE"); }; - emu["register_periodic"] = [this](sol::function func){ register_function(func, "LUA_ON_PERIODIC"); }; - emu["register_mandatory_file_manager_override"] = [this](sol::function func) { register_function(func, "LUA_ON_MANDATORY_FILE_MANAGER_OVERRIDE"); }; + emu["register_prestart"] = [this] (sol::function func) { register_function(func, "LUA_ON_PRESTART"); }; + emu["register_start"] = [this] (sol::function func) { osd_printf_warning("[LUA] emu.register_start is deprecated - please use emu.add_machine_reset_notifier\n"); register_function(func, "LUA_ON_START"); }; + emu["register_stop"] = [this] (sol::function func) { osd_printf_warning("[LUA] emu.register_stop is deprecated - please use emu.add_machine_stop_notifier\n"); register_function(func, "LUA_ON_STOP"); }; + emu["register_pause"] = [this] (sol::function func) { osd_printf_warning("[LUA] emu.register_pause is deprecated - please use emu.add_machine_pause_notifier\n"); register_function(func, "LUA_ON_PAUSE"); }; + emu["register_resume"] = [this] (sol::function func) { osd_printf_warning("[LUA] emu.register_resume is deprecated - please use emu.add_machine_resume_notifier\n"); register_function(func, "LUA_ON_RESUME"); }; + emu["register_frame"] = [this] (sol::function func) { osd_printf_warning("[LUA] emu.register_frame is deprecated - please use emu.add_machine_frame_notifier\n"); register_function(func, "LUA_ON_FRAME"); }; + emu["register_frame_done"] = [this] (sol::function func) { register_function(func, "LUA_ON_FRAME_DONE"); }; + emu["register_sound_update"] = [this] (sol::function func) { register_function(func, "LUA_ON_SOUND_UPDATE"); }; + emu["register_periodic"] = [this] (sol::function func) { register_function(func, "LUA_ON_PERIODIC"); }; + emu["register_mandatory_file_manager_override"] = [this] (sol::function func) { register_function(func, "LUA_ON_MANDATORY_FILE_MANAGER_OVERRIDE"); }; emu["register_before_load_settings"] = [this](sol::function func) { register_function(func, "LUA_ON_BEFORE_LOAD_SETTINGS"); }; - emu["register_menu"] = [this](sol::function cb, sol::function pop, const std::string &name) { + emu["register_menu"] = + [this] (sol::function cb, sol::function pop, const std::string &name) + { std::string cbfield = "menu_cb_" + name; std::string popfield = "menu_pop_" + name; sol().registry()[cbfield] = cb; sol().registry()[popfield] = pop; m_menu.push_back(name); }; - emu["show_menu"] = [this](const char *name) { + emu["show_menu"] = + [this](const char *name) + { mame_ui_manager &mui = mame_machine_manager::instance()->ui(); render_container &container = machine().render().ui_container(); - ui::menu_plugin::show_menu(mui, container, (char *)name); + ui::menu_plugin::show_menu(mui, container, name); }; - emu["register_callback"] = [this](sol::function cb, const std::string &name) { + emu["register_callback"] = + [this] (sol::function cb, const std::string &name) + { std::string field = "cb_" + name; sol().registry()[field] = cb; }; - emu["print_verbose"] = [](const char *str) { osd_printf_verbose("%s\n", str); }; - emu["print_error"] = [](const char *str) { osd_printf_error("%s\n", str); }; - emu["print_info"] = [](const char *str) { osd_printf_info("%s\n", str); }; - emu["print_debug"] = [](const char *str) { osd_printf_debug("%s\n", str); }; - emu["driver_find"] = [this](const char *driver) -> sol::object { - int i = driver_list::find(driver); - if(i == -1) - return sol::make_object(sol(), sol::nil); - return sol::make_object(sol(), driver_list::driver(i)); + emu["osd_ticks"] = &osd_ticks; + emu["osd_ticks_per_second"] = &osd_ticks_per_second; + emu["driver_find"] = + [] (sol::this_state s, const char *driver) -> sol::object + { + const int i = driver_list::find(driver); + if (i < 0) + return sol::lua_nil; + return sol::make_object(s, driver_list::driver(i)); }; - emu["wait"] = lua_CFunction([](lua_State *L) { - lua_engine *engine = mame_machine_manager::instance()->lua(); - luaL_argcheck(L, lua_isnumber(L, 1), 1, "waiting duration expected"); - int ret = lua_pushthread(L); - if(ret == 1) - return luaL_error(L, "cannot wait from outside coroutine"); - int ref = luaL_ref(L, LUA_REGISTRYINDEX); - engine->machine().scheduler().timer_set(attotime::from_double(lua_tonumber(L, 1)), timer_expired_delegate(FUNC(lua_engine::resume), engine), ref, nullptr); - return lua_yield(L, 0); - }); - emu["lang_translate"] = &lang_translate; emu["pid"] = &osd_getpid; - - -/* emu_file library + emu["device_enumerator"] = sol::overload( + [] (device_t &dev) { return devenum<device_enumerator>(dev); }, + [] (device_t &dev, int maxdepth) { return devenum<device_enumerator>(dev, maxdepth); }); + emu["palette_enumerator"] = sol::overload( + [] (device_t &dev) { return devenum<palette_interface_enumerator>(dev); }, + [] (device_t &dev, int maxdepth) { return devenum<palette_interface_enumerator>(dev, maxdepth); }); + emu["screen_enumerator"] = sol::overload( + [] (device_t &dev) { return devenum<screen_device_enumerator>(dev); }, + [] (device_t &dev, int maxdepth) { return devenum<screen_device_enumerator>(dev, maxdepth); }); + emu["cassette_enumerator"] = sol::overload( + [] (device_t &dev) { return devenum<cassette_device_enumerator>(dev); }, + [] (device_t &dev, int maxdepth) { return devenum<cassette_device_enumerator>(dev, maxdepth); }); + emu["image_enumerator"] = sol::overload( + [] (device_t &dev) { return devenum<image_interface_enumerator>(dev); }, + [] (device_t &dev, int maxdepth) { return devenum<image_interface_enumerator>(dev, maxdepth); }); + emu["slot_enumerator"] = sol::overload( + [] (device_t &dev) { return devenum<slot_interface_enumerator>(dev); }, + [] (device_t &dev, int maxdepth) { return devenum<slot_interface_enumerator>(dev, maxdepth); }); + + + auto notifier_subscription_type = sol().registry().new_usertype<util::notifier_subscription>("notifier_subscription", sol::no_constructor); + notifier_subscription_type["unsubscribe"] = &util::notifier_subscription::reset; + notifier_subscription_type["is_active"] = sol::property(&util::notifier_subscription::operator bool); + + auto attotime_type = emu.new_usertype<attotime>( + "attotime", + sol::call_constructor, sol::constructors<attotime(), attotime(seconds_t, attoseconds_t), attotime(attotime const &)>()); + attotime_type["from_double"] = &attotime::from_double; + attotime_type["from_ticks"] = static_cast<attotime (*)(u64, u32)>(&attotime::from_ticks); + attotime_type["from_seconds"] = &attotime::from_seconds; + attotime_type["from_msec"] = &attotime::from_msec; + attotime_type["from_usec"] = &attotime::from_usec; + attotime_type["from_nsec"] = &attotime::from_nsec; + attotime_type["as_double"] = &attotime::as_double; + attotime_type["as_hz"] = &attotime::as_hz; + attotime_type["as_khz"] = &attotime::as_khz; + attotime_type["as_mhz"] = &attotime::as_mhz; + attotime_type["as_ticks"] = static_cast<u64 (attotime::*)(u32) const>(&attotime::as_ticks); + attotime_type["is_zero"] = sol::property(&attotime::is_zero); + attotime_type["is_never"] = sol::property(&attotime::is_never); + attotime_type["attoseconds"] = sol::property(&attotime::attoseconds); + attotime_type["seconds"] = sol::property(&attotime::seconds); + attotime_type["msec"] = sol::property([] (attotime const &t) { return t.attoseconds() / ATTOSECONDS_PER_MILLISECOND; }); + attotime_type["usec"] = sol::property([] (attotime const &t) { return t.attoseconds() / ATTOSECONDS_PER_MICROSECOND; }); + attotime_type["nsec"] = sol::property([] (attotime const &t) { return t.attoseconds() / ATTOSECONDS_PER_NANOSECOND; }); + attotime_type[sol::meta_function::to_string] = &attotime::to_string; + attotime_type[sol::meta_function::addition] = static_cast<attotime (*)(attotime const &, attotime const &)>(&operator+); + attotime_type[sol::meta_function::subtraction] = static_cast<attotime (*)(attotime const &, attotime const &)>(&operator-); + attotime_type[sol::meta_function::multiplication] = static_cast<attotime (*)(attotime const &, u32)>(&operator*); + attotime_type[sol::meta_function::division] = static_cast<attotime (*)(attotime const &, u32)>(&operator/); + + +/* emu_file library * * emu.file([opt] searchpath, flags) - flags can be as in osdcore "OPEN_FLAG_*" or lua style * with 'rwc' with addtional c for create *and truncate* @@ -889,7 +1043,8 @@ void lua_engine::initialize() * file:fullpath() - */ - emu.new_usertype<emu_file>("file", sol::call_constructor, sol::initializers([](emu_file &file, u32 flags) { new (&file) emu_file(flags); }, + auto file_type = emu.new_usertype<emu_file>("file", sol::call_constructor, sol::initializers( + [](emu_file &file, u32 flags) { new (&file) emu_file(flags); }, [](emu_file &file, const char *path, u32 flags) { new (&file) emu_file(path, flags); }, [](emu_file &file, const char *mode) { int flags = 0; @@ -928,55 +1083,48 @@ void lua_engine::initialize() } } new (&file) emu_file(path, flags); - }), - "read", [](emu_file &file, sol::buffer *buff) { buff->set_len(file.read(buff->get_ptr(), buff->get_len())); return buff; }, - "write", [](emu_file &file, const std::string &data) { return file.write(data.data(), data.size()); }, - "open", static_cast<osd_file::error (emu_file::*)(const std::string &)>(&emu_file::open), - "open_next", &emu_file::open_next, - "seek", sol::overload([](emu_file &file) { return file.tell(); }, - [this](emu_file &file, s64 offset, int whence) -> sol::object { - if(file.seek(offset, whence)) - return sol::make_object(sol(), sol::nil); - else - return sol::make_object(sol(), file.tell()); - }, - [this](emu_file &file, const char* whence) -> sol::object { - int wval = -1; - const char *seekdirs[] = {"set", "cur", "end"}; - for(int i = 0; i < 3; i++) - { - if(!strncmp(whence, seekdirs[i], 3)) - { - wval = i; - break; - } - } - if(wval < 0 || wval >= 3) - return sol::make_object(sol(), sol::nil); - if(file.seek(0, wval)) - return sol::make_object(sol(), sol::nil); - return sol::make_object(sol(), file.tell()); - }, - [this](emu_file &file, const char* whence, s64 offset) -> sol::object { - int wval = -1; - const char *seekdirs[] = {"set", "cur", "end"}; - for(int i = 0; i < 3; i++) - { - if(!strncmp(whence, seekdirs[i], 3)) - { - wval = i; - break; - } - } - if(wval < 0 || wval >= 3) - return sol::make_object(sol(), sol::nil); - if(file.seek(offset, wval)) - return sol::make_object(sol(), sol::nil); + })); + file_type.set("read", + [] (emu_file &file, sol::this_state s, size_t len) + { + buffer_helper buf(s); + auto space = buf.prepare(len); + space.add(file.read(space.get(), len)); + buf.push(); + return sol::make_reference(s, sol::stack_reference(s, -1)); + }); + file_type.set("write", [](emu_file &file, const std::string &data) { return file.write(data.data(), data.size()); }); + file_type.set("puts", &emu_file::puts); + file_type.set("open", static_cast<std::error_condition (emu_file::*)(std::string_view)>(&emu_file::open)); + file_type.set("open_next", &emu_file::open_next); + file_type.set("close", &emu_file::close); + file_type.set("seek", sol::overload( + [](emu_file &file) { return file.tell(); }, + [this] (emu_file &file, s64 offset, int whence) -> sol::object { + if(file.seek(offset, whence)) + return sol::lua_nil; + else return sol::make_object(sol(), file.tell()); - }), - "size", &emu_file::size, - "filename", &emu_file::filename, - "fullpath", &emu_file::fullpath); + }, + [this](emu_file &file, const char* whence) -> sol::object { + int wval = s_seek_parser(whence); + if(wval < 0 || wval >= 3) + return sol::lua_nil; + if(file.seek(0, wval)) + return sol::lua_nil; + return sol::make_object(sol(), file.tell()); + }, + [this](emu_file &file, const char* whence, s64 offset) -> sol::object { + int wval = s_seek_parser(whence); + if(wval < 0 || wval >= 3) + return sol::lua_nil; + if(file.seek(offset, wval)) + return sol::lua_nil; + return sol::make_object(sol(), file.tell()); + })); + file_type.set("size", &emu_file::size); + file_type.set("filename", &emu_file::filename); + file_type.set("fullpath", &emu_file::fullpath); /* thread library @@ -988,64 +1136,17 @@ void lua_engine::initialize() * thread runs until yield() and/or terminates on return. * thread:continue(val) - resume thread that has yielded and pass val to it * - * thread.result - get result of a terminated thread as string - * thread.busy - check if thread is running - * thread.yield - check if thread is yielded + * thread.result - get result of a terminated or yielding thread as string + * thread.busy - check if thread is running or yielding + * thread.yield - check if thread is yielding */ - emu.new_usertype<context>("thread", sol::call_constructor, sol::constructors<sol::types<>>(), - "start", [](context &ctx, const char *scr) { - std::string script(scr); - if(ctx.busy) - return false; - std::thread th([&ctx, script]() { - sol::state thstate; - thstate.open_libraries(); - thstate["package"]["preload"]["zlib"] = &luaopen_zlib; - thstate["package"]["preload"]["lfs"] = &luaopen_lfs; - thstate["package"]["preload"]["linenoise"] = &luaopen_linenoise; - sol::load_result res = thstate.load(script); - if(res.valid()) - { - sol::protected_function func = res.get<sol::protected_function>(); - thstate["yield"] = [&ctx, &thstate]() { - std::mutex m; - std::unique_lock<std::mutex> lock(m); - ctx.result = thstate["status"]; - ctx.yield = true; - ctx.sync.wait(lock); - ctx.yield = false; - thstate["status"] = ctx.result; - }; - auto ret = func(); - if (ret.valid()) { - const char *tmp = ret.get<const char *>(); - if (tmp != nullptr) - ctx.result = tmp; - else - exit(0); - } - } - ctx.busy = false; - }); - ctx.busy = true; - ctx.yield = false; - th.detach(); - return true; - }, - "continue", [](context &ctx, const char *val) { - if(!ctx.yield) - return; - ctx.result = val; - ctx.sync.notify_all(); - }, - "result", sol::property([](context &ctx) -> std::string { - if(ctx.busy && !ctx.yield) - return ""; - return ctx.result; - }), - "busy", sol::readonly(&context::busy), - "yield", sol::readonly(&context::yield)); + auto thread_type = emu.new_usertype<thread_context>("thread", sol::call_constructor, sol::constructors<sol::types<>>()); + thread_type.set_function("start", &thread_context::start); + thread_type.set_function("continue", &thread_context::resume); + thread_type["result"] = sol::property(&thread_context::result); + thread_type["busy"] = sol::property(&thread_context::busy); + thread_type["yield"] = sol::readonly(&thread_context::m_yield); /* save_item library @@ -1060,68 +1161,105 @@ void lua_engine::initialize() * item:write(offset, value) - write entry value by index */ - emu.new_usertype<save_item>("item", sol::call_constructor, sol::initializers([this](save_item &item, int index) { - if(!machine().save().indexed_item(index, item.base, item.size, item.count)) + auto item_type = emu.new_usertype<save_item>("item", sol::call_constructor, sol::initializers([this](save_item &item, int index) { + if(machine().save().indexed_item(index, item.base, item.size, item.valcount, item.blockcount, item.stride)) { - item.base = nullptr; - item.size = 0; - item.count= 0; + item.count = item.valcount * item.blockcount; } - }), - "size", sol::readonly(&save_item::size), - "count", sol::readonly(&save_item::count), - "read", [this](save_item &item, int offset) -> sol::object { - uint64_t ret = 0; - if(!item.base || (offset > item.count)) - return sol::make_object(sol(), sol::nil); - switch(item.size) - { - case 1: - default: - ret = ((uint8_t *)item.base)[offset]; - break; - case 2: - ret = ((uint16_t *)item.base)[offset]; - break; - case 4: - ret = ((uint32_t *)item.base)[offset]; - break; - case 8: - ret = ((uint64_t *)item.base)[offset]; - break; - } - return sol::make_object(sol(), ret); - }, - "read_block", [](save_item &item, int offset, sol::buffer *buff) { - if(!item.base || ((offset + buff->get_len()) > (item.size * item.count))) - buff->set_len(0); else - memcpy(buff->get_ptr(), (uint8_t *)item.base + offset, buff->get_len()); - return buff; - }, - "write", [](save_item &item, int offset, uint64_t value) { - if(!item.base || (offset > item.count)) - return; - switch(item.size) { - case 1: - default: - ((uint8_t *)item.base)[offset] = (uint8_t)value; - break; - case 2: - ((uint16_t *)item.base)[offset] = (uint16_t)value; - break; - case 4: - ((uint32_t *)item.base)[offset] = (uint32_t)value; - break; - case 8: - ((uint64_t *)item.base)[offset] = (uint64_t)value; - break; + item.base = nullptr; + item.size = 0; + item.count = 0; + item.valcount = 0; + item.blockcount = 0; + item.stride = 0; } - }); + })); + item_type.set("size", sol::readonly(&save_item::size)); + item_type.set("count", sol::readonly(&save_item::count)); + item_type.set("read", + [this] (save_item &item, int offset) -> sol::object + { + if (!item.base || (offset >= item.count)) + return sol::lua_nil; + const void *const data = reinterpret_cast<const uint8_t *>(item.base) + (item.stride * (offset / item.valcount)); + uint64_t ret = 0; + switch (item.size) + { + case 1: + default: + ret = reinterpret_cast<const uint8_t *>(data)[offset % item.valcount]; + break; + case 2: + ret = reinterpret_cast<const uint16_t *>(data)[offset % item.valcount]; + break; + case 4: + ret = reinterpret_cast<const uint32_t *>(data)[offset % item.valcount]; + break; + case 8: + ret = reinterpret_cast<const uint64_t *>(data)[offset % item.valcount]; + break; + } + return sol::make_object(sol(), ret); + }); + item_type.set("read_block", + [] (save_item &item, sol::this_state s, uint32_t offset, size_t len) -> sol::object + { + if (!item.base) + { + luaL_error(s, "Invalid save item"); + return sol::lua_nil; + } + if ((offset + len) > (item.size * item.count)) + { + luaL_error(s, "Range extends beyond end of save item"); + return sol::lua_nil; + } -/* core_options library + luaL_Buffer buff; + uint8_t *dest = reinterpret_cast<uint8_t *>(luaL_buffinitsize(s, &buff, len)); + const uint32_t blocksize = item.size * item.valcount; + size_t remaining = len; + while (remaining) + { + const uint32_t blockno = offset / blocksize; + const uint32_t available = blocksize - (offset % blocksize); + const uint32_t chunk = (available < remaining) ? available : remaining; + const void *const source = reinterpret_cast<const uint8_t *>(item.base) + (blockno * item.stride) + (offset % blocksize); + std::memcpy(dest, source, chunk); + offset += chunk; + remaining -= chunk; + dest += chunk; + } + luaL_pushresultsize(&buff, len); + return sol::make_reference(s, sol::stack_reference(s, -1)); + }); + item_type.set("write", [](save_item &item, int offset, uint64_t value) { + if(!item.base || (offset >= item.count)) + return; + void *const data = reinterpret_cast<uint8_t *>(item.base) + (item.stride * (offset / item.valcount)); + switch(item.size) + { + case 1: + default: + reinterpret_cast<uint8_t *>(data)[offset % item.valcount] = uint8_t(value); + break; + case 2: + reinterpret_cast<uint16_t *>(data)[offset % item.valcount] = uint16_t(value); + break; + case 4: + reinterpret_cast<uint32_t *>(data)[offset % item.valcount] = uint32_t(value); + break; + case 8: + reinterpret_cast<uint64_t *>(data)[offset % item.valcount] = uint64_t(value); + break; + } + }); + + +/* core_options library * * manager:options() * manager:machine():options() @@ -1134,1567 +1272,956 @@ void lua_engine::initialize() * options.entries[] - get table of option entries (k=name, v=core_options::entry) */ - sol().registry().new_usertype<core_options>("core_options", "new", sol::no_constructor, - "help", &core_options::output_help, - "command", &core_options::command, - "entries", sol::property([this](core_options &options) { - sol::table table = sol().create_table(); - int unadorned_index = 0; - for (auto &curentry : options.entries()) + auto core_options_type = sol().registry().new_usertype<core_options>("core_options", "new", sol::no_constructor); + core_options_type.set("help", &core_options::output_help); + core_options_type.set("command", &core_options::command); + core_options_type.set("entries", sol::property([this](core_options &options) { + sol::table table = sol().create_table(); + int unadorned_index = 0; + for (auto &curentry : options.entries()) + { + const char *name = curentry->names().size() > 0 + ? curentry->name().c_str() + : nullptr; + bool is_unadorned = false; + // check if it's unadorned + if (name && strlen(name) && !strcmp(name, options.unadorned(unadorned_index))) { - const char *name = curentry->names().size() > 0 - ? curentry->name().c_str() - : nullptr; - bool is_unadorned = false; - // check if it's unadorned - if (name && strlen(name) && !strcmp(name, options.unadorned(unadorned_index))) - { - unadorned_index++; - is_unadorned = true; - } - if (curentry->type() != core_options::option_type::HEADER && curentry->type() != core_options::option_type::COMMAND && !is_unadorned) - table[name] = &*curentry; + unadorned_index++; + is_unadorned = true; } - return table; - })); - - -/* core_options::entry library - * - * options.entries[entry_name] - * - * entry:value() - get value of entry - * entry:value(val) - set entry to val - * entry:description() - get info about entry - * entry:default_value() - get default for entry - * entry:minimum() - get min value for entry - * entry:maximum() - get max value for entry - * entry:has_range() - are min and max valid for entry - */ - - sol().registry().new_usertype<core_options::entry>("core_options_entry", "new", sol::no_constructor, - "value", sol::overload([this](core_options::entry &e, bool val) { - if(e.type() != OPTION_BOOLEAN) - luaL_error(m_lua_state, "Cannot set option to wrong type"); - else - e.set_value(val ? "1" : "0", OPTION_PRIORITY_CMDLINE); - }, - [this](core_options::entry &e, float val) { - if(e.type() != OPTION_FLOAT) - luaL_error(m_lua_state, "Cannot set option to wrong type"); - else - e.set_value(string_format("%f", val).c_str(), OPTION_PRIORITY_CMDLINE); - }, - [this](core_options::entry &e, int val) { - if(e.type() != OPTION_INTEGER) - luaL_error(m_lua_state, "Cannot set option to wrong type"); - else - e.set_value(string_format("%d", val).c_str(), OPTION_PRIORITY_CMDLINE); - }, - [this](core_options::entry &e, const char *val) { - if(e.type() != OPTION_STRING) - luaL_error(m_lua_state, "Cannot set option to wrong type"); - else - e.set_value(val, OPTION_PRIORITY_CMDLINE); - }, - [this](core_options::entry &e) -> sol::object { - if (e.type() == core_options::option_type::INVALID) - return sol::make_object(sol(), sol::nil); - switch(e.type()) - { - case core_options::option_type::BOOLEAN: - return sol::make_object(sol(), atoi(e.value()) != 0); - case core_options::option_type::INTEGER: - return sol::make_object(sol(), atoi(e.value())); - case core_options::option_type::FLOAT: - return sol::make_object(sol(), atof(e.value())); - default: - return sol::make_object(sol(), e.value()); - } - }), - "description", &core_options::entry::description, - "default_value", &core_options::entry::default_value, - "minimum", &core_options::entry::minimum, - "maximum", &core_options::entry::maximum, - "has_range", &core_options::entry::has_range); - - -/* running_machine library - * - * manager:machine() - * - * machine:exit() - close program - * machine:hard_reset() - hard reset emulation - * machine:soft_reset() - soft reset emulation - * machine:save(filename) - save state to filename - * machine:load(filename) - load state from filename - * machine:popmessage(str) - print str as popup - * machine:popmessage() - clear displayed popup message - * machine:logerror(str) - print str to log - * machine:system() - get game_driver for running driver - * machine:video() - get video_manager - * machine:sound() - get sound_manager - * machine:render() - get render_manager - * machine:ioport() - get ioport_manager - * machine:parameters() - get parameter_manager - * machine:memory() - get memory_manager - * machine:options() - get machine core_options - * machine:outputs() - get output_manager - * machine:input() - get input_manager - * machine:uiinput() - get ui_input_manager - * machine:debugger() - get debugger_manager - * - * machine.paused - get paused state - * machine.samplerate - get audio sample rate - * machine.exit_pending - * machine.hard_reset_pending - * - * machine.devices[] - get device table (k=tag, v=device_t) - * machine.screens[] - get screens table (k=tag, v=screen_device) - * machine.images[] - get available image devices table (k=type, v=device_image_interface) - */ - - sol().registry().new_usertype<running_machine> ("machine", "new", sol::no_constructor, - "exit", &running_machine::schedule_exit, - "hard_reset", &running_machine::schedule_hard_reset, - "soft_reset", &running_machine::schedule_soft_reset, - "save", &running_machine::schedule_save, - "load", &running_machine::schedule_load, - "system", &running_machine::system, - "video", &running_machine::video, - "sound", &running_machine::sound, - "render", &running_machine::render, - "ioport", &running_machine::ioport, - "parameters", &running_machine::parameters, - "memory", &running_machine::memory, - "options", [](running_machine &m) { return static_cast<core_options *>(&m.options()); }, - "outputs", &running_machine::output, - "input", &running_machine::input, - "uiinput", &running_machine::ui_input, - "debugger", [this](running_machine &m) -> sol::object { - if(!(m.debug_flags & DEBUG_FLAG_ENABLED)) - return sol::make_object(sol(), sol::nil); - return sol::make_object(sol(), &m.debugger()); - }, - "paused", sol::property(&running_machine::paused), - "samplerate", sol::property(&running_machine::sample_rate), - "exit_pending", sol::property(&running_machine::exit_pending), - "hard_reset_pending", sol::property(&running_machine::hard_reset_pending), - "devices", sol::property([this](running_machine &m) { - std::function<void(device_t &, sol::table)> tree; - sol::table table = sol().create_table(); - tree = [&tree](device_t &root, sol::table table) { - for(device_t &dev : root.subdevices()) - { - if(dev.configured() && dev.started()) - { - table[dev.tag()] = &dev; - tree(dev, table); - } - } - }; - tree(m.root_device(), table); - return table; - }), - "screens", sol::property([this](running_machine &r) { - sol::table table = sol().create_table(); - for (screen_device &sc : screen_device_iterator(r.root_device())) - { - if (sc.configured() && sc.started() && sc.type()) - table[sc.tag()] = ≻ - } - return table; - }), - "images", sol::property([this](running_machine &r) { - sol::table image_table = sol().create_table(); - for(device_image_interface &image : image_interface_iterator(r.root_device())) - { - image_table[image.brief_instance_name()] = ℑ - image_table[image.instance_name()] = ℑ - } - return image_table; - }), - "popmessage", sol::overload([](running_machine &m, const char *str) { m.popmessage("%s", str); }, - [](running_machine &m) { m.popmessage(); }), - "logerror", [](running_machine &m, const char *str) { m.logerror("[luaengine] %s\n", str); } ); + if (curentry->type() != core_options::option_type::HEADER && curentry->type() != core_options::option_type::COMMAND && !is_unadorned) + table[name] = &*curentry; + } + return table; + })); -/* game_driver library - * - * emu.driver_find(driver_name) +/* emu_options library * - * driver.source_file - relative path to the source file - * driver.parent - * driver.name - * driver.description - * driver.year - * driver.manufacturer - * driver.compatible_with - * driver.default_layout - * driver.orientation - screen rotation degree (rot0/90/180/270) - * driver.type - machine type (arcade/console/computer/other) - * driver.not_working - not considered working - * driver.supports_save - supports save states - * driver.no_cocktail - screen flip support is missing - * driver.is_bios_root - this driver entry is a BIOS root - * driver.requires_artwork - requires external artwork for key game elements - * driver.clickable_artwork - artwork is clickable and requires mouse cursor - * driver.unofficial - unofficial hardware modification - * driver.no_sound_hw - system has no sound output - * driver.mechanical - contains mechanical parts (pinball, redemption games, ...) - * driver.is_incomplete - official system with blatantly incomplete hardware/software - */ - - sol().registry().new_usertype<game_driver>("game_driver", "new", sol::no_constructor, - "source_file", sol::property([] (game_driver const &driver) { return &driver.type.source()[0]; }), - "parent", sol::readonly(&game_driver::parent), - "name", sol::property([] (game_driver const &driver) { return &driver.name[0]; }), - "description", sol::property([] (game_driver const &driver) { return &driver.type.fullname()[0]; }), - "year", sol::readonly(&game_driver::year), - "manufacturer", sol::readonly(&game_driver::manufacturer), - "compatible_with", sol::readonly(&game_driver::compatible_with), - "default_layout", sol::readonly(&game_driver::default_layout), - "orientation", sol::property([](game_driver const &driver) { - std::string rot; - switch (driver.flags & machine_flags::MASK_ORIENTATION) - { - case machine_flags::ROT0: - rot = "rot0"; - break; - case machine_flags::ROT90: - rot = "rot90"; - break; - case machine_flags::ROT180: - rot = "rot180"; - break; - case machine_flags::ROT270: - rot = "rot270"; - break; - default: - rot = "undefined"; - break; - } - return rot; - }), - "type", sol::property([](game_driver const &driver) { - std::string type; - switch (driver.flags & machine_flags::MASK_TYPE) - { - case machine_flags::TYPE_ARCADE: - type = "arcade"; - break; - case machine_flags::TYPE_CONSOLE: - type = "console"; - break; - case machine_flags::TYPE_COMPUTER: - type = "computer"; - break; - default: - type = "other"; - break; - } - return type; - }), - "not_working", sol::property([](game_driver const &driver) { return (driver.flags & machine_flags::NOT_WORKING) > 0; }), - "supports_save", sol::property([](game_driver const &driver) { return (driver.flags & machine_flags::SUPPORTS_SAVE) > 0; }), - "no_cocktail", sol::property([](game_driver const &driver) { return (driver.flags & machine_flags::NO_COCKTAIL) > 0; }), - "is_bios_root", sol::property([](game_driver const &driver) { return (driver.flags & machine_flags::IS_BIOS_ROOT) > 0; }), - "requires_artwork", sol::property([](game_driver const &driver) { return (driver.flags & machine_flags::REQUIRES_ARTWORK) > 0; }), - "clickable_artwork", sol::property([](game_driver const &driver) { return (driver.flags & machine_flags::CLICKABLE_ARTWORK) > 0; }), - "unofficial", sol::property([](game_driver const &driver) { return (driver.flags & machine_flags::UNOFFICIAL) > 0; }), - "no_sound_hw", sol::property([](game_driver const &driver) { return (driver.flags & machine_flags::NO_SOUND_HW) > 0; }), - "mechanical", sol::property([](game_driver const &driver) { return (driver.flags & machine_flags::MECHANICAL) > 0; }), - "is_incomplete", sol::property([](game_driver const &driver) { return (driver.flags & machine_flags::IS_INCOMPLETE) > 0; } - )); - - -/* debugger_manager library (requires debugger to be active) - * - * manager:machine():debugger() - * - * debugger:command(command_string) - run command_string in debugger console + * manager:options() + * manager:machine():options() * - * debugger.consolelog[] - get consolelog text buffer (wrap_textbuf) - * debugger.errorlog[] - get errorlog text buffer (wrap_textbuf) - * debugger.visible_cpu - accessor for debugger active cpu for commands, affects debug views - * debugger.execution_state - accessor for active cpu run state + * options:slot_option(tag) - retrieves a specific slot option */ - struct wrap_textbuf { wrap_textbuf(text_buffer *buf) { textbuf = buf; }; text_buffer *textbuf; }; + auto emu_options_type = sol().registry().new_usertype<emu_options>("emu_options", sol::no_constructor, sol::base_classes, sol::bases<core_options>()); + emu_options_type["slot_option"] = [] (emu_options &opts, std::string const &name) { return opts.find_slot_option(name); }; - sol().registry().new_usertype<debugger_manager>("debugger", "new", sol::no_constructor, - "command", [](debugger_manager &debug, const std::string &cmd) { debug.console().execute_command(cmd, false); }, - "consolelog", sol::property([](debugger_manager &debug) { return wrap_textbuf(debug.console().get_console_textbuf()); }), - "errorlog", sol::property([](debugger_manager &debug) { return wrap_textbuf(debug.console().get_errorlog_textbuf()); }), - "visible_cpu", sol::property([](debugger_manager &debug) { debug.cpu().get_visible_cpu(); }, - [](debugger_manager &debug, device_t &dev) { debug.cpu().set_visible_cpu(&dev); }), - "execution_state", sol::property([](debugger_manager &debug) { - return debug.cpu().is_stopped() ? "stop" : "run"; - }, - [](debugger_manager &debug, const std::string &state) { - if(state == "stop") - debug.cpu().set_execution_stopped(); - else - debug.cpu().set_execution_running(); - })); - -/* wrap_textbuf library (requires debugger to be active) - * - * manager:machine():debugger().consolelog - * manager:machine():debugger().errorlog +/* slot_option library * - * log[index] - get log entry - * #log - entry count - */ - - sol().registry().new_usertype<wrap_textbuf>("text_buffer", "new", sol::no_constructor, - "__metatable", [](){}, - "__newindex", [](){}, - "__index", [](wrap_textbuf &buf, int index) { return text_buffer_get_seqnum_line(buf.textbuf, index - 1); }, - "__len", [](wrap_textbuf &buf) { return text_buffer_num_lines(buf.textbuf) + text_buffer_line_index_to_seqnum(buf.textbuf, 0) - 1; }); - - -/* device_debug library (requires debugger to be active) + * manager:options():slot_option("name") + * manager:machine():options():slot_option("name") * - * manager:machine().devices[device_tag]:debug() + * slot_option:specify(card, bios) - specifies the value of the slot, potentially causing a recalculation * - * debug:step([opt] steps) - run cpu steps, default 1 - * debug:go() - run cpu - * debug:bpset(addr, [opt] cond, [opt] act) - set breakpoint on addr, cond and act are debugger - * expressions. returns breakpoint index - * debug:bpclr(idx) - clear break - * debug:bplist()[] - table of breakpoints (k=index, v=device_debug::breakpoint) - * debug:wpset(space, type, addr, len, [opt] cond, [opt] act) - set watchpoint, cond and act - * are debugger expressions. - * returns watchpoint index - * debug:wpclr(idx) - clear watch - * debug:wplist(space)[] - table of watchpoints (k=index, v=watchpoint) + * slot_option.value - the actual value of the option, after being interpreted + * slot_option.specified_value - the value of the option, as specified from outside + * slot_option.bios - the bios, if any, associated with the slot + * slot_option.default_card_software - the software list item that is associated with this option, by default */ - sol().registry().new_usertype<device_debug>("device_debug", "new", sol::no_constructor, - "step", [](device_debug &dev, sol::object num) { - int steps = 1; - if(num.is<int>()) - steps = num.as<int>(); - dev.single_step(steps); - }, - "go", &device_debug::go, - "bpset", [](device_debug &dev, offs_t addr, const char *cond, const char *act) { return dev.breakpoint_set(addr, cond, act); }, - "bpclr", &device_debug::breakpoint_clear, - "bplist", [this](device_debug &dev) { - sol::table table = sol().create_table(); - for(const device_debug::breakpoint &bpt : dev.breakpoint_list()) - { - sol::table bp = sol().create_table(); - bp["enabled"] = bpt.enabled(); - bp["address"] = bpt.address(); - bp["condition"] = bpt.condition(); - bp["action"] = bpt.action(); - table[bpt.index()] = bp; - } - return table; - }, - "wpset", [](device_debug &dev, addr_space &sp, const std::string &type, offs_t addr, offs_t len, const char *cond, const char *act) { - read_or_write wptype = read_or_write::READ; - if(type == "w") - wptype = read_or_write::WRITE; - else if((type == "rw") || (type == "wr")) - wptype = read_or_write::READWRITE; - return dev.watchpoint_set(sp.space, wptype, addr, len, cond, act); - }, - "wpclr", &device_debug::watchpoint_clear, - "wplist", [this](device_debug &dev, addr_space &sp) { - sol::table table = sol().create_table(); - for(auto &wpp : dev.watchpoint_vector(sp.space.spacenum())) - { - sol::table wp = sol().create_table(); - wp["enabled"] = wpp->enabled(); - wp["address"] = wpp->address(); - wp["length"] = wpp->length(); - switch(wpp->type()) - { - case read_or_write::READ: - wp["type"] = "r"; - break; - case read_or_write::WRITE: - wp["type"] = "w"; - break; - case read_or_write::READWRITE: - wp["type"] = "rw"; - break; - default: // huh? - wp["type"] = ""; - break; - } - wp["condition"] = wpp->condition(); - wp["action"] = wpp->action(); - table[wpp->index()] = wp; - } - return table; - }); + auto slot_option_type = sol().registry().new_usertype<slot_option>("slot_option", sol::no_constructor); + slot_option_type["specify"] = + [] (slot_option &opt, std::string &&text, char const *bios) + { + opt.specify(std::move(text)); + if (bios) + opt.set_bios(bios); + }; + slot_option_type["value"] = sol::property(&slot_option::value); + slot_option_type["specified_value"] = sol::property(&slot_option::specified_value); + slot_option_type["bios"] = sol::property(&slot_option::bios); + slot_option_type["default_card_software"] = sol::property(&slot_option::default_card_software); -/* device_t library +/* core_options::entry library * - * manager:machine().devices[device_tag] - * - * device:name() - device long name - * device:shortname() - device short name - * device:tag() - device tree tag - * device:owner() - device parent tag - * device:debug() - debug interface, cpus only + * options.entries[entry_name] * - * device.spaces[] - device address spaces table (k=name, v=addr_space) - * device.state[] - device state entries table (k=name, v=device_state_entry) - * device.items[] - device save state items table (k=name, v=index) + * entry:value() - get value of entry + * entry:value(val) - set entry to val + * entry:description() - get info about entry + * entry:default_value() - get default for entry + * entry:minimum() - get min value for entry + * entry:maximum() - get max value for entry + * entry:has_range() - are min and max valid for entry */ - sol().registry().new_usertype<device_t>("device", "new", sol::no_constructor, - "name", &device_t::name, - "shortname", &device_t::shortname, - "tag", &device_t::tag, - "owner", &device_t::owner, - "debug", [this](device_t &dev) -> sol::object { - if(!(dev.machine().debug_flags & DEBUG_FLAG_ENABLED) || !dynamic_cast<cpu_device *>(&dev)) // debugger not enabled or not cpu - return sol::make_object(sol(), sol::nil); - return sol::make_object(sol(), dev.debug()); - }, - "spaces", sol::property([this](device_t &dev) { - device_memory_interface *memdev = dynamic_cast<device_memory_interface *>(&dev); - sol::table sp_table = sol().create_table(); - if(!memdev) - return sp_table; - for(int sp = 0; sp < memdev->max_space_count(); ++sp) - { - if(memdev->has_space(sp)) - sp_table[memdev->space(sp).name()] = addr_space(memdev->space(sp), *memdev); - } + auto core_options_entry_type = sol().registry().new_usertype<core_options::entry>("core_options_entry", "new", sol::no_constructor); + core_options_entry_type.set("value", sol::overload( + [this](core_options::entry &e, bool val) { + if(e.type() != core_options::option_type::BOOLEAN) + luaL_error(m_lua_state, "Cannot set option to wrong type"); + else + e.set_value(val ? "1" : "0", OPTION_PRIORITY_CMDLINE); + }, + [this](core_options::entry &e, float val) { + if(e.type() != core_options::option_type::FLOAT) + luaL_error(m_lua_state, "Cannot set option to wrong type"); + else + e.set_value(string_format("%f", val), OPTION_PRIORITY_CMDLINE); + }, + [this](core_options::entry &e, int val) { + if(e.type() != core_options::option_type::INTEGER) + luaL_error(m_lua_state, "Cannot set option to wrong type"); + else + e.set_value(string_format("%d", val), OPTION_PRIORITY_CMDLINE); + }, + [this](core_options::entry &e, const char *val) { + if(e.type() != core_options::option_type::STRING && e.type() != core_options::option_type::PATH && e.type() != core_options::option_type::MULTIPATH) + luaL_error(m_lua_state, "Cannot set option to wrong type"); + else + e.set_value(val, OPTION_PRIORITY_CMDLINE); + }, + [this](core_options::entry &e) -> sol::object { + if (e.type() == core_options::option_type::INVALID) + return sol::lua_nil; + switch(e.type()) + { + case core_options::option_type::BOOLEAN: + return sol::make_object(sol(), atoi(e.value()) != 0); + case core_options::option_type::INTEGER: + return sol::make_object(sol(), atoi(e.value())); + case core_options::option_type::FLOAT: + return sol::make_object(sol(), atof(e.value())); + default: + return sol::make_object(sol(), e.value()); + } + })); + core_options_entry_type.set("description", &core_options::entry::description); + core_options_entry_type.set("default_value", &core_options::entry::default_value); + core_options_entry_type.set("minimum", &core_options::entry::minimum); + core_options_entry_type.set("maximum", &core_options::entry::maximum); + core_options_entry_type.set("has_range", &core_options::entry::has_range); + + + auto machine_type = sol().registry().new_usertype<running_machine>("machine", sol::no_constructor); + machine_type.set_function("exit", &running_machine::schedule_exit); + machine_type.set_function("hard_reset", &running_machine::schedule_hard_reset); + machine_type.set_function("soft_reset", &running_machine::schedule_soft_reset); + machine_type.set_function("save", &running_machine::schedule_save); // TODO: some kind of completion notification? + machine_type.set_function("load", &running_machine::schedule_load); // TODO: some kind of completion notification? + machine_type.set_function("buffer_save", + [] (running_machine &m, sol::this_state s) + { + // FIXME: this needs to schedule saving to a buffer and return asynchronously somehow + // right now it's broken by anonymous timers, synchronize, etc. + lua_State *L = s; + luaL_Buffer buff; + int size = ram_state::get_size(m.save()); + u8 *ptr = (u8 *)luaL_buffinitsize(L, &buff, size); + save_error error = m.save().write_buffer(ptr, size); + if (error == STATERR_NONE) + { + luaL_pushresultsize(&buff, size); + return sol::make_reference(L, sol::stack_reference(L, -1)); + } + luaL_error(L, "State save error."); + return sol::make_reference(L, nullptr); + }); + machine_type.set_function("buffer_load", + [] (running_machine &m, sol::this_state s, std::string str) + { + // FIXME: this needs to schedule loading from the buffer and return asynchronously somehow + // right now it's broken by anonymous timers, synchronize, etc. + save_error error = m.save().read_buffer((u8 *)str.data(), str.size()); + if (error == STATERR_NONE) + { + return true; + } + else + { + luaL_error(s,"State load error."); + return false; + } + }); + machine_type.set_function("popmessage", + [] (running_machine &m, std::optional<const char *> str) + { + if (str) + m.popmessage("%s", *str); + else + m.popmessage(); + }); + machine_type.set_function("logerror", [] (running_machine &m, char const *str) { m.logerror("[luaengine] %s\n", str); }); + machine_type["time"] = sol::property(&running_machine::time); + machine_type["system"] = sol::property(&running_machine::system); + machine_type["parameters"] = sol::property(&running_machine::parameters); + machine_type["video"] = sol::property(&running_machine::video); + machine_type["sound"] = sol::property(&running_machine::sound); + machine_type["output"] = sol::property(&running_machine::output); + machine_type["memory"] = sol::property(&running_machine::memory); + machine_type["ioport"] = sol::property(&running_machine::ioport); + machine_type["input"] = sol::property(&running_machine::input); + machine_type["natkeyboard"] = sol::property(&running_machine::natkeyboard); + machine_type["uiinput"] = sol::property(&running_machine::ui_input); + machine_type["render"] = sol::property(&running_machine::render); + machine_type["debugger"] = sol::property( + [] (running_machine &m, sol::this_state s) -> sol::object + { + if (m.debug_flags & DEBUG_FLAG_ENABLED) + return sol::make_object(s, &m.debugger()); + else + return sol::lua_nil; + }); + machine_type["options"] = sol::property(&running_machine::options); + machine_type["samplerate"] = sol::property(&running_machine::sample_rate); + machine_type["paused"] = sol::property(&running_machine::paused); + machine_type["exit_pending"] = sol::property(&running_machine::exit_pending); + machine_type["hard_reset_pending"] = sol::property(&running_machine::hard_reset_pending); + machine_type["devices"] = sol::property([] (running_machine &m) { return devenum<device_enumerator>(m.root_device()); }); + machine_type["palettes"] = sol::property([] (running_machine &m) { return devenum<palette_interface_enumerator>(m.root_device()); }); + machine_type["screens"] = sol::property([] (running_machine &m) { return devenum<screen_device_enumerator>(m.root_device()); }); + machine_type["cassettes"] = sol::property([] (running_machine &m) { return devenum<cassette_device_enumerator>(m.root_device()); }); + machine_type["images"] = sol::property([] (running_machine &m) { return devenum<image_interface_enumerator>(m.root_device()); }); + machine_type["slots"] = sol::property([](running_machine &m) { return devenum<slot_interface_enumerator>(m.root_device()); }); + machine_type["sounds"] = sol::property([](running_machine &m) { return devenum<sound_interface_enumerator>(m.root_device()); }); + machine_type["phase"] = sol::property( + [] (running_machine const &m) -> char const * + { + switch (m.phase()) + { + case machine_phase::PREINIT: return "preinit"; + case machine_phase::INIT: return "init"; + case machine_phase::RESET: return "reset"; + case machine_phase::RUNNING: return "running"; + case machine_phase::EXIT: return "exit"; + } + return nullptr; + }); + + + auto game_driver_type = sol().registry().new_usertype<game_driver>("game_driver", sol::no_constructor); + game_driver_type["name"] = sol::property([] (game_driver const &driver) { return &driver.name[0]; }); + game_driver_type["description"] = sol::property([] (game_driver const &driver) { return &driver.type.fullname()[0]; }); + game_driver_type["year"] = sol::readonly(&game_driver::year); + game_driver_type["manufacturer"] = sol::readonly(&game_driver::manufacturer); + game_driver_type["parent"] = sol::readonly(&game_driver::parent); + game_driver_type["compatible_with"] = sol::property([] (game_driver const &driver) { return strcmp(driver.compatible_with, "0") ? driver.compatible_with : nullptr; }); + game_driver_type["source_file"] = sol::property([] (game_driver const &driver) { return driver.type.source(); }); + game_driver_type["orientation"] = sol::property( + [] (game_driver const &driver) + { + // FIXME: this works differently to the screen orientation function and the render target orientation property + // it should probably be made consistent with one of them + std::string rot; + switch (driver.flags & machine_flags::MASK_ORIENTATION) + { + case machine_flags::ROT0: + rot = "rot0"; + break; + case machine_flags::ROT90: + rot = "rot90"; + break; + case machine_flags::ROT180: + rot = "rot180"; + break; + case machine_flags::ROT270: + rot = "rot270"; + break; + default: + rot = "undefined"; + break; + } + return rot; + }); + game_driver_type["not_working"] = sol::property([] (game_driver const &driver) { return (driver.type.emulation_flags() & device_t::flags::NOT_WORKING) != 0; }); + game_driver_type["supports_save"] = sol::property([] (game_driver const &driver) { return (driver.type.emulation_flags() & device_t::flags::SAVE_UNSUPPORTED) == 0; }); + game_driver_type["no_cocktail"] = sol::property([] (game_driver const &driver) { return (driver.flags & machine_flags::NO_COCKTAIL) != 0; }); + game_driver_type["is_bios_root"] = sol::property([] (game_driver const &driver) { return (driver.flags & machine_flags::IS_BIOS_ROOT) != 0; }); + game_driver_type["requires_artwork"] = sol::property([] (game_driver const &driver) { return (driver.flags & machine_flags::REQUIRES_ARTWORK) != 0; }); + game_driver_type["unofficial"] = sol::property([] (game_driver const &driver) { return (driver.flags & machine_flags::UNOFFICIAL) != 0; }); + game_driver_type["no_sound_hw"] = sol::property([] (game_driver const &driver) { return (driver.flags & machine_flags::NO_SOUND_HW) != 0; }); + game_driver_type["mechanical"] = sol::property([] (game_driver const &driver) { return (driver.flags & machine_flags::MECHANICAL) != 0; }); + game_driver_type["is_incomplete"] = sol::property([] (game_driver const &driver) { return (driver.flags & machine_flags::IS_INCOMPLETE) != 0; }); + + + auto device_type = sol().registry().new_usertype<device_t>("device", sol::no_constructor); + device_type.set_function(sol::meta_function::to_string, [] (device_t &d) { return util::string_format("%s(%s)", d.shortname(), d.tag()); }); + device_type.set_function("subtag", &device_t::subtag); + device_type.set_function("siblingtag", &device_t::siblingtag); + device_type.set_function("memregion", &device_t::memregion); + device_type.set_function("memshare", &device_t::memshare); + device_type.set_function("membank", &device_t::membank); + device_type.set_function("ioport", &device_t::ioport); + device_type.set_function("subdevice", static_cast<device_t *(device_t::*)(std::string_view) const>(&device_t::subdevice)); + device_type.set_function("siblingdevice", static_cast<device_t *(device_t::*)(std::string_view) const>(&device_t::siblingdevice)); + device_type.set_function("parameter", &device_t::parameter); + device_type["tag"] = sol::property(&device_t::tag); + device_type["basetag"] = sol::property(&device_t::basetag); + device_type["name"] = sol::property(&device_t::name); + device_type["shortname"] = sol::property(&device_t::shortname); + device_type["owner"] = sol::property(&device_t::owner); + device_type["configured"] = sol::property(&device_t::configured); + device_type["started"] = sol::property(&device_t::started); + device_type["debug"] = sol::property( + [] (device_t &dev, sol::this_state s) -> sol::object + { + if (!(dev.machine().debug_flags & DEBUG_FLAG_ENABLED) || !dynamic_cast<cpu_device *>(&dev)) // debugger not enabled or not CPU + return sol::lua_nil; + return sol::make_object(s, dev.debug()); + }); + device_type["spaces"] = sol::property( + [this] (device_t &dev) + { + device_memory_interface *const memdev = dynamic_cast<device_memory_interface *>(&dev); + sol::table sp_table = sol().create_table(); + if (!memdev) return sp_table; - }), - "state", sol::property([this](device_t &dev) { - sol::table st_table = sol().create_table(); - if(!dynamic_cast<device_state_interface *>(&dev)) - return st_table; - // XXX: refrain from exporting non-visible entries? - for(auto &s : dev.state().state_entries()) - st_table[s->symbol()] = s.get(); - return st_table; - }), - "items", sol::property([this](device_t &dev) { - sol::table table = sol().create_table(); - std::string tag = dev.tag(); - // 10000 is enough? - for(int i = 0; i < 10000; i++) - { - std::string name; - const char *item; - unsigned int size, count; - void *base; - item = dev.machine().save().indexed_item(i, base, size, count); - if(!item) - break; - name = &(strchr(item, '/')[1]); - if(name.substr(0, name.find("/")) == tag) - { - name = name.substr(name.find("/") + 1, std::string::npos); - table[name] = i; - } - } - return table; - })); - - -/* addr_space library - * - * manager:machine().devices[device_tag].spaces[space] - * - * read/write by signedness u/i and bit-width 8/16/32/64: - * space:read_*(addr) - * space:write_*(addr, val) - * space:read_log_*(addr) - * space:write_log_*(addr, val) - * space:read_direct_*(addr) - * space:write_direct_*(addr, val) - * - * space.name - address space name - * space.shift - address bus shift, bitshift required for a bytewise address - * to map onto this space's addess resolution (addressing granularity). - * positive value means leftshift, negative means rightshift. - * space.index - * - * space.map[] - table of address map entries (k=index, v=address_map_entry) - */ - - sol().registry().new_usertype<addr_space>("addr_space", sol::call_constructor, sol::constructors<sol::types<address_space &, device_memory_interface &>>(), - "read_i8", &addr_space::mem_read<int8_t>, - "read_u8", &addr_space::mem_read<uint8_t>, - "read_i16", &addr_space::mem_read<int16_t>, - "read_u16", &addr_space::mem_read<uint16_t>, - "read_i32", &addr_space::mem_read<int32_t>, - "read_u32", &addr_space::mem_read<uint32_t>, - "read_i64", &addr_space::mem_read<int64_t>, - "read_u64", &addr_space::mem_read<uint64_t>, - "write_i8", &addr_space::mem_write<int8_t>, - "write_u8", &addr_space::mem_write<uint8_t>, - "write_i16", &addr_space::mem_write<int16_t>, - "write_u16", &addr_space::mem_write<uint16_t>, - "write_i32", &addr_space::mem_write<int32_t>, - "write_u32", &addr_space::mem_write<uint32_t>, - "write_i64", &addr_space::mem_write<int64_t>, - "write_u64", &addr_space::mem_write<uint64_t>, - "read_log_i8", &addr_space::log_mem_read<int8_t>, - "read_log_u8", &addr_space::log_mem_read<uint8_t>, - "read_log_i16", &addr_space::log_mem_read<int16_t>, - "read_log_u16", &addr_space::log_mem_read<uint16_t>, - "read_log_i32", &addr_space::log_mem_read<int32_t>, - "read_log_u32", &addr_space::log_mem_read<uint32_t>, - "read_log_i64", &addr_space::log_mem_read<int64_t>, - "read_log_u64", &addr_space::log_mem_read<uint64_t>, - "write_log_i8", &addr_space::log_mem_write<int8_t>, - "write_log_u8", &addr_space::log_mem_write<uint8_t>, - "write_log_i16", &addr_space::log_mem_write<int16_t>, - "write_log_u16", &addr_space::log_mem_write<uint16_t>, - "write_log_i32", &addr_space::log_mem_write<int32_t>, - "write_log_u32", &addr_space::log_mem_write<uint32_t>, - "write_log_i64", &addr_space::log_mem_write<int64_t>, - "write_log_u64", &addr_space::log_mem_write<uint64_t>, - "read_direct_i8", &addr_space::direct_mem_read<int8_t>, - "read_direct_u8", &addr_space::direct_mem_read<uint8_t>, - "read_direct_i16", &addr_space::direct_mem_read<int16_t>, - "read_direct_u16", &addr_space::direct_mem_read<uint16_t>, - "read_direct_i32", &addr_space::direct_mem_read<int32_t>, - "read_direct_u32", &addr_space::direct_mem_read<uint32_t>, - "read_direct_i64", &addr_space::direct_mem_read<int64_t>, - "read_direct_u64", &addr_space::direct_mem_read<uint64_t>, - "write_direct_i8", &addr_space::direct_mem_write<int8_t>, - "write_direct_u8", &addr_space::direct_mem_write<uint8_t>, - "write_direct_i16", &addr_space::direct_mem_write<int16_t>, - "write_direct_u16", &addr_space::direct_mem_write<uint16_t>, - "write_direct_i32", &addr_space::direct_mem_write<int32_t>, - "write_direct_u32", &addr_space::direct_mem_write<uint32_t>, - "write_direct_i64", &addr_space::direct_mem_write<int64_t>, - "write_direct_u64", &addr_space::direct_mem_write<uint64_t>, - "name", sol::property([](addr_space &sp) { return sp.space.name(); }), - "shift", sol::property([](addr_space &sp) { return sp.space.addr_shift(); }), - "index", sol::property([](addr_space &sp) { return sp.space.spacenum(); }), - -/* address_map_entry library - * - * manager:machine().devices[device_tag].spaces[space].map[entry_index] - * - * mapentry.offset - address start - * mapentry.endoff - address end - * mapentry.readtype - * mapentry.writetype - */ - "map", sol::property([this](addr_space &sp) { - address_space &space = sp.space; - sol::table map = sol().create_table(); - for (address_map_entry &entry : space.map()->m_entrylist) - { - sol::table mapentry = sol().create_table(); - mapentry["offset"] = entry.m_addrstart & space.addrmask(); - mapentry["endoff"] = entry.m_addrend & space.addrmask(); - mapentry["readtype"] = entry.m_read.m_type; - mapentry["writetype"] = entry.m_write.m_type; - map.add(mapentry); - } - return map; - })); - - -/* ioport_manager library - * - * manager:machine():ioport() - * - * ioport:count_players() - get count of player controllers - * ioport:type_group(type, player) - * - * ioport.ports[] - ioports table (k=tag, v=ioport_port) - */ - - sol().registry().new_usertype<ioport_manager>("ioport", "new", sol::no_constructor, - "count_players", &ioport_manager::count_players, - "natkeyboard", &ioport_manager::natkeyboard, - "type_group", [](ioport_manager &im, ioport_type type, int player) { - return im.type_group(type, player); - }, - "ports", sol::property([this](ioport_manager &im) { - sol::table port_table = sol().create_table(); - for (auto &port : im.ports()) - port_table[port.second->tag()] = port.second.get(); - return port_table; - })); - - -/* natural_keyboard library - * - * manager:machine():ioport():natkeyboard() - * - * natkeyboard:paste() - paste clipboard data - * natkeyboard:post() - post data to natural keyboard - * natkeyboard:post_coded() - post data to natural keyboard - * - * natkeyboard.empty - is the natural keyboard buffer empty? - * natkeyboard.in_use - is the natural keyboard in use? - */ - - sol().registry().new_usertype<natural_keyboard>("natkeyboard", "new", sol::no_constructor, - "empty", sol::property(&natural_keyboard::empty), - "in_use", sol::property(&natural_keyboard::in_use, &natural_keyboard::set_in_use), - "paste", &natural_keyboard::paste, - "post", [](natural_keyboard &nat, const std::string &text) { nat.post_utf8(text); }, - "post_coded", [](natural_keyboard &nat, const std::string &text) { nat.post_coded(text); }); - - -/* ioport_port library - * - * manager:machine():ioport().ports[port_tag] - * - * port:tag() - get port tag - * port:active() - get port status - * port:live() - get port ioport_port_live (TODO: not usable from lua as of now) - * port:read() - get port value - * port:write(val, mask) - set port to value & mask (output fields only, for other fields use field:set_value(val)) - * port:field(mask) - get ioport_field for port and mask - * - * port.fields[] - get ioport_field table (k=name, v=ioport_field) - */ - - sol().registry().new_usertype<ioport_port>("ioport_port", "new", sol::no_constructor, - "tag", &ioport_port::tag, - "active", &ioport_port::active, - "live", &ioport_port::live, - "read", &ioport_port::read, - "write", &ioport_port::write, - "field", &ioport_port::field, - "fields", sol::property([this](ioport_port &p){ - sol::table f_table = sol().create_table(); - // parse twice for custom and default names, default has priority - for(ioport_field &field : p.fields()) - { - if (field.type_class() != INPUT_CLASS_INTERNAL) - f_table[field.name()] = &field; - } - for(ioport_field &field : p.fields()) - { - if (field.type_class() != INPUT_CLASS_INTERNAL) - { - if(field.specific_name()) - f_table[field.specific_name()] = &field; - else - f_table[field.manager().type_name(field.type(), field.player())] = &field; - } - } - return f_table; - })); - + for (int sp = 0; sp < memdev->max_space_count(); ++sp) + { + if (memdev->has_space(sp)) + sp_table[memdev->space(sp).name()] = addr_space(memdev->space(sp), *memdev); + } + return sp_table; + }); + device_type["state"] = sol::property( + [] (device_t &dev, sol::this_state s) -> sol::object + { + device_state_interface const *state; + if (!dev.interface(state)) + return sol::lua_nil; + return sol::make_object(s, device_state_entries(*state)); + }); + // FIXME: turn into a wrapper - it's stupid slow to walk on every property access + // also, this mixes up things like RAM areas with stuff saved by the device itself, so there's potential for key conflicts + device_type["items"] = sol::property( + [this] (device_t &dev) + { + sol::table table = sol().create_table(); + std::string const tag = dev.tag(); + for (int i = 0; ; i++) + { + char const *item; + void *base; + uint32_t size, valcount, blockcount, stride; + item = dev.machine().save().indexed_item(i, base, size, valcount, blockcount, stride); + if (!item) + break; -/* ioport_field library - * - * manager:machine():ioport().ports[port_tag].fields[field_name] - * - * field:set_value(value) - * field:set_input_seq(seq_type, seq) - * field:input_seq(seq_type) - * field:set_default_input_seq(seq_type, seq) - * field:default_input_seq(seq_type) - * field:keyboard_codes(which) - * - * field.device - get associated device_t - * field.port - get associated ioport_port - * field.live - get ioport_field_live - * field.name - * field.default_name - * field.player - * field.mask - * field.defvalue - * field.sensitivity - * field.way - amount of available directions - * field.type_class - * field.is_analog - * field.is_digital_joystick - * field.enabled - * field.optional - * field.cocktail - * field.toggle - whether field is a toggle - * field.rotated - * field.analog_reverse - * field.analog_reset - * field.analog_wraps - * field.analog_invert - * field.impulse - * field.type - * field.crosshair_scale - * field.crosshair_offset - * field.user_value - */ + char const *name = &strchr(item, '/')[1]; + if (!strncmp(tag.c_str(), name, tag.length()) && (name[tag.length()] == '/')) + table[name + tag.length() + 1] = i; + } + return table; + }); + // FIXME: this is useless in its current form + device_type["roms"] = sol::property( + [this] (device_t &dev) + { + sol::table table = sol().create_table(); + for (auto rom : dev.rom_region_vector()) + if (!rom.name().empty()) + table[rom.name()] = rom; + return table; + }); - sol().registry().new_usertype<ioport_field>("ioport_field", "new", sol::no_constructor, - "set_value", &ioport_field::set_value, - "set_input_seq", [](ioport_field &f, const std::string &seq_type_string, sol::user<input_seq> seq) { - input_seq_type seq_type = parse_seq_type(seq_type_string); - ioport_field::user_settings settings; - f.get_user_settings(settings); - settings.seq[seq_type] = seq; - f.set_user_settings(settings); - }, - "input_seq", [](ioport_field &f, const std::string &seq_type_string) { - input_seq_type seq_type = parse_seq_type(seq_type_string); - return sol::make_user(f.seq(seq_type)); - }, - "set_default_input_seq", [](ioport_field &f, const std::string &seq_type_string, sol::user<input_seq> seq) { - input_seq_type seq_type = parse_seq_type(seq_type_string); - f.set_defseq(seq_type, seq); - }, - "default_input_seq", [](ioport_field &f, const std::string &seq_type_string) { - input_seq_type seq_type = parse_seq_type(seq_type_string); - return sol::make_user(f.defseq(seq_type)); + auto dipalette_type = sol().registry().new_usertype<device_palette_interface>("dipalette", sol::no_constructor); + dipalette_type.set_function("pen", &device_palette_interface::pen); + dipalette_type.set_function( + "pen_color", + [] (device_palette_interface const &pal, pen_t pen) + { + return uint32_t(pal.pen_color(pen)); + }); + dipalette_type.set_function("pen_contrast", &device_palette_interface::pen_contrast); + dipalette_type.set_function("pen_indirect", &device_palette_interface::pen_indirect); + dipalette_type.set_function( + "indirect_color", + [] (device_palette_interface const &pal, int index) + { + return uint32_t(pal.indirect_color(index)); + }); + dipalette_type["set_pen_color"] = sol::overload( + [] (device_palette_interface &pal, pen_t pen, uint32_t color) + { + pal.set_pen_color(pen, rgb_t(color)); }, - "keyboard_codes", [this](ioport_field &f, int which) + static_cast<void (device_palette_interface::*)(pen_t, uint8_t, uint8_t, uint8_t)>(&device_palette_interface::set_pen_color)); + dipalette_type.set_function("set_pen_red_level", &device_palette_interface::set_pen_red_level); + dipalette_type.set_function("set_pen_green_level", &device_palette_interface::set_pen_green_level); + dipalette_type.set_function("set_pen_blue_level", &device_palette_interface::set_pen_blue_level); + dipalette_type.set_function("set_pen_contrast", &device_palette_interface::set_pen_contrast); + dipalette_type.set_function("set_pen_indirect", &device_palette_interface::set_pen_indirect); + dipalette_type["set_indirect_color"] = sol::overload( + [] (device_palette_interface &pal, int index, uint32_t color) { - sol::table result = sol().create_table(); - int index = 1; - for (char32_t code : f.keyboard_codes(which)) - result[index++] = code; - return result; + pal.set_indirect_color(index, rgb_t(color)); }, - "device", sol::property(&ioport_field::device), - "port", sol::property(&ioport_field::port), - "name", sol::property(&ioport_field::name), - "default_name", sol::property([](ioport_field &f) { - return f.specific_name() ? f.specific_name() : f.manager().type_name(f.type(), f.player()); - }), - "player", sol::property(&ioport_field::player, &ioport_field::set_player), - "mask", sol::property(&ioport_field::mask), - "defvalue", sol::property(&ioport_field::defvalue), - "sensitivity", sol::property(&ioport_field::sensitivity), - "way", sol::property(&ioport_field::way), - "type_class", sol::property([](ioport_field &f) { - switch (f.type_class()) - { - case INPUT_CLASS_KEYBOARD: return "keyboard"; - case INPUT_CLASS_CONTROLLER: return "controller"; - case INPUT_CLASS_CONFIG: return "config"; - case INPUT_CLASS_DIPSWITCH: return "dipswitch"; - case INPUT_CLASS_MISC: return "misc"; - default: break; - } - throw false; - }), - "is_analog", sol::property(&ioport_field::is_analog), - "is_digital_joystick", sol::property(&ioport_field::is_digital_joystick), - "enabled", sol::property(&ioport_field::enabled), - "optional", sol::property(&ioport_field::optional), - "cocktail", sol::property(&ioport_field::cocktail), - "toggle", sol::property(&ioport_field::toggle), - "rotated", sol::property(&ioport_field::rotated), - "analog_reverse", sol::property(&ioport_field::analog_reverse), - "analog_reset", sol::property(&ioport_field::analog_reset), - "analog_wraps", sol::property(&ioport_field::analog_wraps), - "analog_invert", sol::property(&ioport_field::analog_invert), - "impulse", sol::property(&ioport_field::impulse), - "type", sol::property(&ioport_field::type), - "live", sol::property(&ioport_field::live), - "crosshair_scale", sol::property(&ioport_field::crosshair_scale, &ioport_field::set_crosshair_scale), - "crosshair_offset", sol::property(&ioport_field::crosshair_offset, &ioport_field::set_crosshair_offset), - "user_value", sol::property([](ioport_field &f) { - ioport_field::user_settings settings; - f.get_user_settings(settings); - return settings.value; - }, [](ioport_field &f, ioport_value val) { - ioport_field::user_settings settings; - f.get_user_settings(settings); - settings.value = val; - f.set_user_settings(settings); - })); - - -/* ioport_field_live library - * - * manager:machine():ioport().ports[port_tag].fields[field_name].live - * - * live.name - */ - - sol().registry().new_usertype<ioport_field_live>("ioport_field_live", "new", sol::no_constructor, - "name", &ioport_field_live::name); - - -/* parameters_manager library - * - * manager:machine():parameters() - * - * parameters:add(tag, val) - add tag = val parameter - * parameters:lookup(tag) - get val for tag - */ - - sol().registry().new_usertype<parameters_manager>("parameters", "new", sol::no_constructor, - "add", ¶meters_manager::add, - "lookup", ¶meters_manager::lookup); - - -/* video_manager library - * - * manager:machine():video() - * - * video:begin_recording([opt] filename) - start AVI recording to filename if given or default - * video:end_recording() - stop AVI recording - * video:is_recording() - get recording status - * video:snapshot() - save shot of all screens - * video:skip_this_frame() - is current frame going to be skipped - * video:speed_factor() - get speed factor - * video:speed_percent() - get percent from realtime - * video:frame_update() - render a frame - * video:size() - get width and height of snapshot bitmap in pixels - * video:pixels() - get binary bitmap of all screens as string - * - * video.frameskip - current frameskip - * video.throttled - throttle state - * video.throttle_rate - throttle rate - */ - - sol().registry().new_usertype<video_manager>("video", "new", sol::no_constructor, - "begin_recording", sol::overload([this](video_manager &vm, const char *filename) { - std::string fn = filename; - strreplace(fn, "/", PATH_SEPARATOR); - strreplace(fn, "%g", machine().basename()); - vm.begin_recording(fn.c_str(), video_manager::MF_AVI); - }, - [](video_manager &vm) { vm.begin_recording(nullptr, video_manager::MF_AVI); }), - "end_recording", [this](video_manager &vm) { - if(!vm.is_recording()) + [] (device_palette_interface &pal, int index, uint8_t r, uint8_t g, uint8_t b) + { + pal.set_indirect_color(index, rgb_t(r, g, b)); + }); + dipalette_type.set_function("set_shadow_factor", &device_palette_interface::set_shadow_factor); + dipalette_type.set_function("set_highlight_factor", &device_palette_interface::set_highlight_factor); + dipalette_type.set_function("set_shadow_mode", &device_palette_interface::set_shadow_mode); + dipalette_type["palette"] = sol::property( + [] (device_palette_interface &pal) + { + return pal.palette() + ? std::optional<palette_wrapper>(std::in_place, *pal.palette()) + : std::optional<palette_wrapper>(); + }); + dipalette_type["entries"] = sol::property(&device_palette_interface::entries); + dipalette_type["indirect_entries"] = sol::property(&device_palette_interface::indirect_entries); + dipalette_type["black_pen"] = sol::property(&device_palette_interface::black_pen); + dipalette_type["white_pen"] = sol::property(&device_palette_interface::white_pen); + dipalette_type["shadows_enabled"] = sol::property(&device_palette_interface::shadows_enabled); + dipalette_type["highlights_enabled"] = sol::property(&device_palette_interface::hilights_enabled); + dipalette_type["device"] = sol::property(static_cast<device_t & (device_palette_interface::*)()>(&device_palette_interface::device)); + + + auto disound_type = sol().registry().new_usertype<device_sound_interface>("disound", sol::no_constructor); + disound_type["inputs"] = sol::property(&device_sound_interface::inputs); + disound_type["outputs"] = sol::property(&device_sound_interface::outputs); + disound_type["microphone"] = sol::property( + [] (device_sound_interface &dev) + { + return dev.device().type() == MICROPHONE; + }); + disound_type["speaker"] = sol::property( + [] (device_sound_interface &dev) + { + return dev.device().type() == SPEAKER; + }); + disound_type["io_positions"] = sol::property( + [this] (device_sound_interface &dev) + { + auto pos_table = sol().create_table(); + auto *iodev = dynamic_cast<sound_io_device *>(&dev.device()); + if (iodev) + for(int channel=0; channel != iodev->channels(); channel++) { - machine().logerror("[luaengine] No active recording to stop\n"); - return; + auto pos = iodev->get_position(channel); + auto table = sol().create_table(); + table[1] = pos[0]; + table[2] = pos[1]; + table[3] = pos[2]; + pos_table[channel+1] = table; } - vm.end_recording(video_manager::MF_AVI); - }, - "snapshot", &video_manager::save_active_screen_snapshots, - "is_recording", &video_manager::is_recording, - "skip_this_frame", &video_manager::skip_this_frame, - "speed_factor", &video_manager::speed_factor, - "speed_percent", &video_manager::speed_percent, - "effective_frameskip", &video_manager::effective_frameskip, - "frame_update", &video_manager::frame_update, - "size", [](video_manager &vm) { - s32 width, height; - vm.compute_snapshot_size(width, height); - return std::tuple<s32, s32>(width, height); - }, - "pixels", [](video_manager &vm, sol::this_state s) { - lua_State *L = s; - luaL_Buffer buff; - s32 width, height; - vm.compute_snapshot_size(width, height); - int size = width * height * 4; - u32 *ptr = (u32 *)luaL_buffinitsize(L, &buff, size); - vm.pixels(ptr); - luaL_pushresultsize(&buff, size); - return sol::make_reference(L, sol::stack_reference(L, -1)); - }, - "frameskip", sol::property(&video_manager::frameskip, &video_manager::set_frameskip), - "throttled", sol::property(&video_manager::throttled, &video_manager::set_throttled), - "throttle_rate", sol::property(&video_manager::throttle_rate, &video_manager::set_throttle_rate)); - - -/* sound_manager library - * - * manager:machine():sound() - * - * sound:start_recording() - begin audio recording - * sound:stop_recording() - end audio recording - * sound:ui_mute(turn_off) - turns on/off UI sound - * sound:system_mute() - turns on/off system sound - * sound:samples() - get current audio buffer contents in binary form as string (updates 50 times per second) - * - * sound.attenuation - sound attenuation - */ - - sol().registry().new_usertype<sound_manager>("sound", "new", sol::no_constructor, - "start_recording", &sound_manager::start_recording, - "stop_recording", &sound_manager::stop_recording, - "ui_mute", &sound_manager::ui_mute, - "debugger_mute", &sound_manager::debugger_mute, - "system_mute", &sound_manager::system_mute, - "samples", [](sound_manager &sm, sol::this_state s) { - lua_State *L = s; - luaL_Buffer buff; - s32 count = sm.sample_count() * 2 * 2; // 2 channels, 2 bytes per sample - s16 *ptr = (s16 *)luaL_buffinitsize(L, &buff, count); - sm.samples(ptr); - luaL_pushresultsize(&buff, count); - return sol::make_reference(L, sol::stack_reference(L, -1)); - }, - "attenuation", sol::property(&sound_manager::attenuation, &sound_manager::set_attenuation)); - - -/* input_manager library - * - * manager:machine():input() - * - * input:code_from_token(token) - get input_code for KEYCODE_* string token - * input:code_pressed(code) - get pressed state for input_code - * input:code_to_token(code) - get KEYCODE_* string token for code - * input:code_name(code) - get code friendly name - * input:seq_from_tokens(tokens) - get input_seq for multiple space separated KEYCODE_* string tokens - * input:seq_pressed(seq) - get pressed state for input_seq - * input:seq_to_tokens(seq) - get KEYCODE_* string tokens for seq - * input:seq_name(seq) - get seq friendly name - * input:seq_clean(seq) - clean the seq and remove invalid elements - * input:seq_poll_start(class, [opt] start_seq) - start polling for input_item_class passed as string - * (switch/abs[olute]/rel[ative]/max[imum]) - * input:seq_poll() - poll once, returns true if input was fetched - * input:seq_poll_final() - get final input_seq - * input.device_classes - returns device classes - */ - - sol().registry().new_usertype<input_manager>("input", "new", sol::no_constructor, - "code_from_token", [](input_manager &input, const char *token) { return sol::make_user(input.code_from_token(token)); }, - "code_pressed", [](input_manager &input, sol::user<input_code> code) { return input.code_pressed(code); }, - "code_to_token", [](input_manager &input, sol::user<input_code> code) { return input.code_to_token(code); }, - "code_name", [](input_manager &input, sol::user<input_code> code) { return input.code_name(code); }, - "seq_from_tokens", [](input_manager &input, const char *tokens) { input_seq seq; input.seq_from_tokens(seq, tokens); return sol::make_user(seq); }, - "seq_pressed", [](input_manager &input, sol::user<input_seq> seq) { return input.seq_pressed(seq); }, - "seq_to_tokens", [](input_manager &input, sol::user<input_seq> seq) { return input.seq_to_tokens(seq); }, - "seq_name", [](input_manager &input, sol::user<input_seq> seq) { return input.seq_name(seq); }, - "seq_clean", [](input_manager &input, sol::user<input_seq> seq) { input_seq cleaned_seq = input.seq_clean(seq); return sol::make_user(cleaned_seq); }, - "seq_poll_start", [](input_manager &input, const char *cls_string, sol::object seq) { - input_item_class cls; - if (!strcmp(cls_string, "switch")) - cls = ITEM_CLASS_SWITCH; - else if (!strcmp(cls_string, "absolute") || !strcmp(cls_string, "abs")) - cls = ITEM_CLASS_ABSOLUTE; - else if (!strcmp(cls_string, "relative") || !strcmp(cls_string, "rel")) - cls = ITEM_CLASS_RELATIVE; - else if (!strcmp(cls_string, "maximum") || !strcmp(cls_string, "max")) - cls = ITEM_CLASS_MAXIMUM; + return pos_table; + }); + disound_type["io_names"] = sol::property( + [this] (device_sound_interface &dev) + { + auto pos_table = sol().create_table(); + auto *iodev = dynamic_cast<sound_io_device *>(&dev.device()); + if (iodev) + for(int channel=0; channel != iodev->channels(); channel++) + pos_table[channel+1] = iodev->get_position_name(channel); + return pos_table; + }); + + disound_type["hook"] = sol::property(&device_sound_interface::get_sound_hook, &device_sound_interface::set_sound_hook); + disound_type["device"] = sol::property(static_cast<device_t & (device_sound_interface::*)()>(&device_sound_interface::device)); + + + + auto screen_dev_type = sol().registry().new_usertype<screen_device>( + "screen_dev", + sol::no_constructor, + sol::base_classes, sol::bases<device_t>()); + screen_dev_type.set_function( + "draw_box", + [] (screen_device &sdev, float x1, float y1, float x2, float y2, std::optional<uint32_t> fgcolor, std::optional<uint32_t> bgcolor) + { + float const sc_width(sdev.visible_area().width()); + float const sc_height(sdev.visible_area().height()); + x1 = std::clamp(x1, 0.0f, sc_width) / sc_width; + y1 = std::clamp(y1, 0.0f, sc_height) / sc_height; + x2 = std::clamp(x2, 0.0f, sc_width) / sc_width; + y2 = std::clamp(y2, 0.0f, sc_height) / sc_height; + mame_ui_manager &ui(mame_machine_manager::instance()->ui()); + if (!fgcolor) + fgcolor = ui.colors().text_color(); + if (!bgcolor) + bgcolor = ui.colors().background_color(); + ui.draw_outlined_box(sdev.container(), x1, y1, x2, y2, *fgcolor, *bgcolor); + }); + screen_dev_type.set_function( + "draw_line", + [] (screen_device &sdev, float x1, float y1, float x2, float y2, std::optional<uint32_t> color) + { + float const sc_width(sdev.visible_area().width()); + float const sc_height(sdev.visible_area().height()); + x1 = std::clamp(x1, 0.0f, sc_width) / sc_width; + y1 = std::clamp(y1, 0.0f, sc_height) / sc_height; + x2 = std::clamp(x2, 0.0f, sc_width) / sc_width; + y2 = std::clamp(y2, 0.0f, sc_height) / sc_height; + if (!color) + color = mame_machine_manager::instance()->ui().colors().text_color(); + sdev.container().add_line(x1, y1, x2, y2, UI_LINE_WIDTH, rgb_t(*color), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + }); + screen_dev_type.set_function( + "draw_text", + [this] (screen_device &sdev, sol::object xobj, float y, char const *msg, std::optional<uint32_t> fgcolor, std::optional<uint32_t> bgcolor) + { + float const sc_width(sdev.visible_area().width()); + float const sc_height(sdev.visible_area().height()); + auto justify = ui::text_layout::text_justify::LEFT; + float x = 0; + if (xobj.is<float>()) + { + x = std::clamp(xobj.as<float>(), 0.0f, sc_width) / sc_width; + } + else if (xobj.is<char const *>()) + { + char const *const justifystr(xobj.as<char const *>()); + if (!strcmp(justifystr, "left")) + justify = ui::text_layout::text_justify::LEFT; + else if (!strcmp(justifystr, "right")) + justify = ui::text_layout::text_justify::RIGHT; + else if (!strcmp(justifystr, "center")) + justify = ui::text_layout::text_justify::CENTER; + } else - cls = ITEM_CLASS_INVALID; - - input_seq *start = nullptr; - if(seq.is<sol::user<input_seq>>()) - start = &seq.as<sol::user<input_seq>>(); - input.seq_poll_start(cls, start); - }, - "seq_poll", &input_manager::seq_poll, - "seq_poll_final", [](input_manager &input) { return sol::make_user(input.seq_poll_final()); }, - "device_classes", sol::property([this](input_manager &input) + { + luaL_error(m_lua_state, "Error in param 1 to draw_text"); + return; + } + y = std::clamp(y, 0.0f, sc_height) / sc_height; + mame_ui_manager &ui(mame_machine_manager::instance()->ui()); + if (!fgcolor) + fgcolor = ui.colors().text_color(); + if (!bgcolor) + bgcolor = 0; + ui.draw_text_full( + sdev.container(), + msg, + x, y, (1.0f - x), + justify, ui::text_layout::word_wrapping::WORD, + mame_ui_manager::OPAQUE_, *fgcolor, *bgcolor); + }); + screen_dev_type.set_function( + "orientation", + [] (screen_device &sdev) { - sol::table result = sol().create_table(); - for (input_device_class devclass_id = DEVICE_CLASS_FIRST_VALID; devclass_id <= DEVICE_CLASS_LAST_VALID; devclass_id++) + uint32_t flags = sdev.orientation(); + int rotation_angle = 0; + switch (flags) { - input_class &devclass = input.device_class(devclass_id); - result[devclass.name()] = &devclass; + case ORIENTATION_SWAP_XY: + case ORIENTATION_SWAP_XY | ORIENTATION_FLIP_X: + rotation_angle = 90; + flags ^= ORIENTATION_FLIP_X; + break; + case ORIENTATION_FLIP_Y: + case ORIENTATION_FLIP_X | ORIENTATION_FLIP_Y: + rotation_angle = 180; + flags ^= ORIENTATION_FLIP_X | ORIENTATION_FLIP_Y; + break; + case ORIENTATION_SWAP_XY | ORIENTATION_FLIP_Y: + case ORIENTATION_SWAP_XY | ORIENTATION_FLIP_X | ORIENTATION_FLIP_Y: + rotation_angle = 270; + flags ^= ORIENTATION_FLIP_Y; + break; } - return result; - })); - - -/* input_class library - * - * manager:machine():input().device_classes[devclass] - * - * devclass.name - * devclass.enabled - * devclass.multi - * devclass.devices[] - */ - - sol().registry().new_usertype<input_class>("input_class", "new", sol::no_constructor, - "name", sol::property(&input_class::name), - "enabled", sol::property(&input_class::enabled, &input_class::enable), - "multi", sol::property(&input_class::multi, &input_class::set_multi), - "devices", sol::property([this](input_class &devclass) + return std::tuple<int, bool, bool>(rotation_angle, flags & ORIENTATION_FLIP_X, flags & ORIENTATION_FLIP_Y); + }); + screen_dev_type["time_until_pos"] = sol::overload( + [] (screen_device &sdev, int vpos) { return sdev.time_until_pos(vpos).as_double(); }, + [] (screen_device &sdev, int vpos, int hpos) { return sdev.time_until_pos(vpos, hpos).as_double(); }); + screen_dev_type.set_function("time_until_vblank_start", &screen_device::time_until_vblank_start); + screen_dev_type.set_function("time_until_vblank_end", &screen_device::time_until_vblank_end); + screen_dev_type.set_function( + "snapshot", + [this] (screen_device &sdev, char const *filename) -> sol::object { - sol::table result = sol().create_table(); - int index = 1; - for (int devindex = 0; devindex <= devclass.maxindex(); devindex++) + // FIXME: this shouldn't be a member of the screen device + // the screen is only used as a hint when configured for native snapshots and may be ignored + std::string snapstr; + bool is_absolute_path = false; + if (filename) { - input_device *dev = devclass.device(devindex); - if (dev) - result[index++] = dev; + // a filename was specified; if it isn't absolute post-process it + snapstr = process_snapshot_filename(machine(), filename); + is_absolute_path = osd_is_absolute_path(snapstr); } - return result; - })); - -/* input_device library - * - * manager:machine():input().device_classes[devclass].devices[index] - * device.name - * device.id - * device.devindex - * device.items[] - */ - - sol().registry().new_usertype<input_device>("input_device", "new", sol::no_constructor, - "name", sol::property(&input_device::name), - "id", sol::property(&input_device::id), - "devindex", sol::property(&input_device::devindex), - "items", sol::property([this](input_device &dev) - { - sol::table result = sol().create_table(); - for (input_item_id id = ITEM_ID_FIRST_VALID; id < dev.maxitem(); id++) + // open the file + emu_file file(is_absolute_path ? "" : machine().options().snapshot_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); + std::error_condition filerr; + if (!snapstr.empty()) + filerr = file.open(snapstr); + else + filerr = machine().video().open_next(file, "png"); + if (filerr) + return sol::make_object(sol(), filerr); + + // and save the snapshot + machine().video().save_snapshot(&sdev, file); + return sol::lua_nil; + }); + screen_dev_type.set_function("pixel", &screen_device::pixel); + screen_dev_type.set_function( + "pixels", + [] (screen_device &sdev, sol::this_state s) { - input_device_item *item = dev.item(id); - if (item) - result[id] = dev.item(id); - } - return result; - })); - - -/* input_device_item library - * - * manager:machine():input().device_classes[devclass].devices[index].items[item_id] - * item.name - * item.token - * item:code() - */ - - sol().registry().new_usertype<input_device_item>("input_device_item", "new", sol::no_constructor, - "name", sol::property(&input_device_item::name), - "token", sol::property(&input_device_item::token), - "code", [](input_device_item &item) - { - input_code code(item.device().devclass(), item.device().devindex(), item.itemclass(), ITEM_MODIFIER_NONE, item.itemid()); - return sol::make_user(code); - }); - - -/* ui_input_manager library - * - * manager:machine():uiinput() - * - * uiinput:find_mouse() - return x, y, button state, ui render target - * uiinput:pressed(key) - get pressed state for ui key - * uiinput.presses_enabled - enable/disable ui key presses - */ - - sol().registry().new_usertype<ui_input_manager>("uiinput", "new", sol::no_constructor, - "find_mouse", [](ui_input_manager &ui) { - int32_t x, y; - bool button; - render_target *rt = ui.find_mouse(&x, &y, &button); - return std::tuple<int32_t, int32_t, bool, render_target *>(x, y, button, rt); - }, - "pressed", &ui_input_manager::pressed, - "presses_enabled", sol::property(&ui_input_manager::presses_enabled, &ui_input_manager::set_presses_enabled)); - - -/* render_target library - * - * manager:machine():render().targets[target_index] - * manager:machine():render():ui_target() - * - * target:view_bounds() - get x0, x1, y0, y1 bounds for target - * target:width() - get target width - * target:height() - get target height - * target:pixel_aspect() - get target aspect - * target:hidden() - is target hidden - * target:is_ui_target() - is ui render target - * target:index() - target index - * target:view_name([opt] index) - current target layout view name - * - * target.max_update_rate - - * target.view - current target layout view - * target.orientation - current target orientation - * target.screen_overlay - enable overlays - * target.zoom - enable zoom - */ - - sol().registry().new_usertype<render_target>("target", "new", sol::no_constructor, - "view_bounds", [](render_target &rt) { - const render_bounds b = rt.current_view()->bounds(); - return std::tuple<float, float, float, float>(b.x0, b.x1, b.y0, b.y1); - }, - "width", &render_target::width, - "height", &render_target::height, - "pixel_aspect", &render_target::pixel_aspect, - "hidden", &render_target::hidden, - "is_ui_target", &render_target::is_ui_target, - "index", &render_target::index, - "view_name", &render_target::view_name, - "max_update_rate", sol::property(&render_target::max_update_rate, &render_target::set_max_update_rate), - "view", sol::property(&render_target::view, &render_target::set_view), - "orientation", sol::property(&render_target::orientation, &render_target::set_orientation), - "screen_overlay", sol::property(&render_target::screen_overlay_enabled, &render_target::set_screen_overlay_enabled), - "zoom", sol::property(&render_target::zoom_to_screen, &render_target::set_zoom_to_screen)); - - -/* render_container library - * - * manager:machine():render():ui_container() - * - * container:orientation() - * container:xscale() - * container:yscale() - * container:xoffset() - * container:yoffset() - * container:is_empty() - */ - - sol().registry().new_usertype<render_container>("render_container", "new", sol::no_constructor, - "orientation", &render_container::orientation, - "xscale", &render_container::xscale, - "yscale", &render_container::yscale, - "xoffset", &render_container::xoffset, - "yoffset", &render_container::yoffset, - "is_empty", &render_container::is_empty); - - -/* render_manager library - * - * manager:machine():render() - * - * render:max_update_rate() - - * render:ui_target() - render_target for ui drawing - * render:ui_container() - render_container for ui drawing - * - * render.targets[] - render_target table - */ - - sol().registry().new_usertype<render_manager>("render", "new", sol::no_constructor, - "max_update_rate", &render_manager::max_update_rate, - "ui_target", &render_manager::ui_target, - "ui_container", &render_manager::ui_container, - "targets", sol::property([this](render_manager &r) { - sol::table target_table = sol().create_table(); - int tc = 0; - for(render_target &curr_rt : r.targets()) - target_table[tc++] = &curr_rt; - return target_table; - })); - - -/* screen_device library - * - * manager:machine().screens[screen_tag] - * - * screen:draw_box(x1, y1, x2, y2, fillcol, linecol) - draw box from (x1, y1)-(x2, y2) colored linecol - * filled with fillcol, color is 32bit argb - * screen:draw_line(x1, y1, x2, y2, linecol) - draw line from (x1, y1)-(x2, y2) colored linecol - * screen:draw_text(x || justify, y, message, [opt] fgcolor, [opt] bgcolor) - draw message at (x, y) or at line y - * with left/right/center justification - * screen:height() - screen height - * screen:width() - screen width - * screen:orientation() - screen angle, flipx, flipy - * screen:refresh() - screen refresh rate in Hz - * screen:refresh_attoseconds() - screen refresh rate in attoseconds - * screen:snapshot([opt] filename) - save snap shot - * screen:type() - screen drawing type - * screen:frame_number() - screen frame count - * screen:name() - screen device full name - * screen:shortname() - screen device short name - * screen:tag() - screen device tag - * screen:xscale() - screen x scale factor - * screen:yscale() - screen y scale factor - * screen:pixel(x, y) - get pixel at (x, y) as packed RGB in a u32 - * screen:pixels() - get whole screen binary bitmap as string - */ - - sol().registry().new_usertype<screen_device>("screen_dev", "new", sol::no_constructor, - "draw_box", [](screen_device &sdev, float x1, float y1, float x2, float y2, uint32_t bgcolor, uint32_t fgcolor) { - int sc_width = sdev.visible_area().width(); - int sc_height = sdev.visible_area().height(); - x1 = std::min(std::max(0.0f, x1), float(sc_width-1)) / float(sc_width); - y1 = std::min(std::max(0.0f, y1), float(sc_height-1)) / float(sc_height); - x2 = std::min(std::max(0.0f, x2), float(sc_width-1)) / float(sc_width); - y2 = std::min(std::max(0.0f, y2), float(sc_height-1)) / float(sc_height); - mame_machine_manager::instance()->ui().draw_outlined_box(sdev.container(), x1, y1, x2, y2, fgcolor, bgcolor); - }, - "draw_line", [](screen_device &sdev, float x1, float y1, float x2, float y2, uint32_t color) { - int sc_width = sdev.visible_area().width(); - int sc_height = sdev.visible_area().height(); - x1 = std::min(std::max(0.0f, x1), float(sc_width-1)) / float(sc_width); - y1 = std::min(std::max(0.0f, y1), float(sc_height-1)) / float(sc_height); - x2 = std::min(std::max(0.0f, x2), float(sc_width-1)) / float(sc_width); - y2 = std::min(std::max(0.0f, y2), float(sc_height-1)) / float(sc_height); - sdev.container().add_line(x1, y1, x2, y2, UI_LINE_WIDTH, rgb_t(color), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); - }, - "draw_text", [this](screen_device &sdev, sol::object xobj, float y, const char *msg, sol::object color, sol::object bcolor) { - int sc_width = sdev.visible_area().width(); - int sc_height = sdev.visible_area().height(); - auto justify = ui::text_layout::LEFT; - float x = 0; - if(xobj.is<float>()) - { - x = std::min(std::max(0.0f, xobj.as<float>()), float(sc_width-1)) / float(sc_width); - y = std::min(std::max(0.0f, y), float(sc_height-1)) / float(sc_height); - } - else if(xobj.is<const char *>()) - { - std::string just_str = xobj.as<const char *>(); - if(just_str == "right") - justify = ui::text_layout::RIGHT; - else if(just_str == "center") - justify = ui::text_layout::CENTER; - } - else - { - luaL_error(m_lua_state, "Error in param 1 to draw_text"); - return; - } - rgb_t textcolor = mame_machine_manager::instance()->ui().colors().text_color(); - rgb_t bgcolor = 0; - if(color.is<uint32_t>()) - textcolor = rgb_t(color.as<uint32_t>()); - if(bcolor.is<uint32_t>()) - bgcolor = rgb_t(bcolor.as<uint32_t>()); - mame_machine_manager::instance()->ui().draw_text_full(sdev.container(), msg, x, y, (1.0f - x), - justify, ui::text_layout::WORD, mame_ui_manager::OPAQUE_, textcolor, bgcolor); - }, - "height", [](screen_device &sdev) { return sdev.visible_area().height(); }, - "width", [](screen_device &sdev) { return sdev.visible_area().width(); }, - "orientation", [](screen_device &sdev) { - uint32_t flags = sdev.orientation(); - int rotation_angle = 0; - switch (flags) - { - case ORIENTATION_FLIP_X: - rotation_angle = 0; - break; - case ORIENTATION_SWAP_XY: - case ORIENTATION_SWAP_XY|ORIENTATION_FLIP_X: - rotation_angle = 90; - break; - case ORIENTATION_FLIP_Y: - case ORIENTATION_FLIP_X|ORIENTATION_FLIP_Y: - rotation_angle = 180; - break; - case ORIENTATION_SWAP_XY|ORIENTATION_FLIP_Y: - case ORIENTATION_SWAP_XY|ORIENTATION_FLIP_X|ORIENTATION_FLIP_Y: - rotation_angle = 270; - break; - } - return std::tuple<int, bool, bool>(rotation_angle, flags & ORIENTATION_FLIP_X, flags & ORIENTATION_FLIP_Y); - }, - "refresh", [](screen_device &sdev) { return ATTOSECONDS_TO_HZ(sdev.refresh_attoseconds()); }, - "refresh_attoseconds", [](screen_device &sdev) { return sdev.refresh_attoseconds(); }, - "snapshot", [this](screen_device &sdev, sol::object filename) -> sol::object { - std::string snapstr; - bool is_absolute_path = false; - if (filename.is<const char *>()) - { - // a filename was specified; if it isn't absolute postprocess it - snapstr = filename.as<const char *>(); - is_absolute_path = osd_is_absolute_path(snapstr); - if (!is_absolute_path) + const rectangle &visarea = sdev.visible_area(); + luaL_Buffer buff; + int size = visarea.height() * visarea.width() * 4; + u32 *const ptr = reinterpret_cast<u32 *>(luaL_buffinitsize(s, &buff, size)); + sdev.pixels(ptr); + luaL_pushresultsize(&buff, size); + return std::make_tuple(sol::make_reference(s, sol::stack_reference(s, -1)), visarea.width(), visarea.height()); + }); + screen_dev_type["screen_type"] = sol::property(&screen_device::screen_type); + screen_dev_type["width"] = sol::property([] (screen_device &sdev) { return sdev.visible_area().width(); }); + screen_dev_type["height"] = sol::property([] (screen_device &sdev) { return sdev.visible_area().height(); }); + screen_dev_type["refresh"] = sol::property([] (screen_device &sdev) { return ATTOSECONDS_TO_HZ(sdev.refresh_attoseconds()); }); + screen_dev_type["refresh_attoseconds"] = sol::property([] (screen_device &sdev) { return sdev.refresh_attoseconds(); }); + screen_dev_type["xoffset"] = sol::property(&screen_device::xoffset); + screen_dev_type["yoffset"] = sol::property(&screen_device::yoffset); + screen_dev_type["xscale"] = sol::property(&screen_device::xscale); + screen_dev_type["yscale"] = sol::property(&screen_device::yscale); + screen_dev_type["pixel_period"] = sol::property([] (screen_device &sdev) { return sdev.pixel_period().as_double(); }); + screen_dev_type["scan_period"] = sol::property([] (screen_device &sdev) { return sdev.scan_period().as_double(); }); + screen_dev_type["frame_period"] = sol::property([] (screen_device &sdev) { return sdev.frame_period().as_double(); }); + screen_dev_type["frame_number"] = &screen_device::frame_number; + screen_dev_type["container"] = sol::property(&screen_device::container); + screen_dev_type["palette"] = sol::property([] (screen_device const &sdev) { return sdev.has_palette() ? &sdev.palette() : nullptr; }); + + + auto cass_type = sol().registry().new_usertype<cassette_image_device>( + "cassette", + sol::no_constructor, + sol::base_classes, sol::bases<device_t, device_image_interface>()); + cass_type["stop"] = [] (cassette_image_device &c) { c.change_state(CASSETTE_STOPPED, CASSETTE_MASK_UISTATE); }; + cass_type["play"] = [] (cassette_image_device &c) { c.change_state(CASSETTE_PLAY, CASSETTE_MASK_UISTATE); }; + cass_type["record"] = [] (cassette_image_device &c) { c.change_state(CASSETTE_RECORD, CASSETTE_MASK_UISTATE); }; + cass_type["forward"] = &cassette_image_device::go_forward; + cass_type["reverse"] = &cassette_image_device::go_reverse; + cass_type["seek"] = [] (cassette_image_device &c, double time, const char* origin) { if (c.exists()) c.seek(time, s_seek_parser(origin)); }; + cass_type["is_stopped"] = sol::property(&cassette_image_device::is_stopped); + cass_type["is_playing"] = sol::property(&cassette_image_device::is_playing); + cass_type["is_recording"] = sol::property(&cassette_image_device::is_recording); + cass_type["motor_state"] = sol::property(&cassette_image_device::motor_on, &cassette_image_device::set_motor); + cass_type["speaker_state"] = sol::property(&cassette_image_device::speaker_on, &cassette_image_device::set_speaker); + cass_type["position"] = sol::property(&cassette_image_device::get_position); + cass_type["length"] = sol::property([] (cassette_image_device &c) { return c.exists() ? c.get_length() : 0.0; }); + + + auto image_type = sol().registry().new_usertype<device_image_interface>("image", sol::no_constructor); + image_type.set_function("load", + [] (device_image_interface &di, sol::this_state s, std::string_view path) -> sol::object + { + auto [err, message] = di.load(path); + if (!err) + return sol::lua_nil; + else if (!message.empty()) + return sol::make_object(s, message); + else + return sol::make_object(s, err.message()); + }); + image_type.set_function("load_software", + [] (device_image_interface &di, sol::this_state s, std::string_view identifier) -> sol::object + { + auto [err, message] = di.load_software(identifier); + if (!err) + return sol::lua_nil; + else if (!message.empty()) + return sol::make_object(s, message); + else + return sol::make_object(s, err.message()); + }); + image_type.set_function("unload", &device_image_interface::unload); + image_type.set_function("create", + [] (device_image_interface &di, sol::this_state s, std::string_view path) -> sol::object + { + auto [err, message] = di.create(path); + if (!err) + return sol::lua_nil; + else if (!message.empty()) + return sol::make_object(s, message); + else + return sol::make_object(s, err.message()); + }); + image_type.set_function("display", &device_image_interface::call_display); + image_type.set_function("add_media_change_notifier", + [this] (device_image_interface &di, sol::protected_function cb) + { + return di.add_media_change_notifier( + [this, cbfunc = sol::protected_function(m_lua_state, cb)] (device_image_interface::media_change_event ev) { - strreplace(snapstr, "/", PATH_SEPARATOR); - strreplace(snapstr, "%g", machine().basename()); - } - } + char const *evstr(nullptr); + switch (ev) + { + case device_image_interface::media_change_event::LOADED: + evstr = "loaded"; + break; + case device_image_interface::media_change_event::UNLOADED: + evstr = "unloaded"; + break; + } + auto status(invoke(cbfunc, evstr)); + if (!status.valid()) + { + auto err(status.template get<sol::error>()); + osd_printf_error("[LUA ERROR] error in media change callback: %s\n", err.what()); + } + }); + }); + image_type["is_readable"] = sol::property(&device_image_interface::is_readable); + image_type["is_writeable"] = sol::property(&device_image_interface::is_writeable); + image_type["is_creatable"] = sol::property(&device_image_interface::is_creatable); + image_type["must_be_loaded"] = sol::property(&device_image_interface::must_be_loaded); + image_type["is_reset_on_load"] = sol::property(&device_image_interface::is_reset_on_load); + image_type["image_type_name"] = sol::property(&device_image_interface::image_type_name); + image_type["instance_name"] = sol::property(&device_image_interface::instance_name); + image_type["brief_instance_name"] = sol::property(&device_image_interface::brief_instance_name); + image_type["formatlist"] = sol::property([] (device_image_interface &image) { return image_interface_formats(image); }); + image_type["exists"] = sol::property(&device_image_interface::exists); + image_type["readonly"] = sol::property(&device_image_interface::is_readonly); + image_type["filename"] = sol::property(&device_image_interface::filename); + image_type["crc"] = sol::property(&device_image_interface::crc); + image_type["loaded_through_softlist"] = sol::property(&device_image_interface::loaded_through_softlist); + image_type["software_list_name"] = sol::property(&device_image_interface::software_list_name); + image_type["software_longname"] = sol::property( + [] (device_image_interface &di) + { + software_info const *const si(di.software_entry()); + return si ? si->longname().c_str() : nullptr; + }); + image_type["software_publisher"] = sol::property( + [] (device_image_interface &di) + { + software_info const *const si(di.software_entry()); + return si ? si->publisher().c_str() : nullptr; + }); + image_type["software_year"] = sol::property( + [] (device_image_interface &di) + { + software_info const *const si(di.software_entry()); + return si ? si->year().c_str() : nullptr; + }); + image_type["software_parent"] = sol::property( + [] (device_image_interface &di) + { + software_info const *const si(di.software_entry()); + return si ? si->parentname().c_str() : nullptr; + }); + image_type["device"] = sol::property(static_cast<device_t & (device_image_interface::*)()>(&device_image_interface::device)); - // open the file - emu_file file(is_absolute_path ? "" : machine().options().snapshot_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); - osd_file::error filerr; - if (!snapstr.empty()) - filerr = file.open(snapstr); - else - filerr = machine().video().open_next(file, "png"); - if (filerr != osd_file::error::NONE) - return sol::make_object(sol(), filerr); - // and save the snapshot - machine().video().save_snapshot(&sdev, file); - return sol::make_object(sol(), sol::nil); - }, - "type", [](screen_device &sdev) { - switch (sdev.screen_type()) - { - case SCREEN_TYPE_RASTER: return "raster"; break; - case SCREEN_TYPE_VECTOR: return "vector"; break; - case SCREEN_TYPE_LCD: return "lcd"; break; - case SCREEN_TYPE_SVG: return "svg"; break; - default: break; - } - return "unknown"; - }, - "frame_number", &screen_device::frame_number, - "name", &screen_device::name, - "shortname", &screen_device::shortname, - "tag", &screen_device::tag, - "xscale", &screen_device::xscale, - "yscale", &screen_device::yscale, - "pixel", [](screen_device &sdev, float x, float y) { - return sdev.pixel((s32)x, (s32)y); - }, - "pixels", [](screen_device &sdev, sol::this_state s) { - lua_State *L = s; - const rectangle &visarea = sdev.visible_area(); - luaL_Buffer buff; - int size = visarea.height() * visarea.width() * 4; - u32 *ptr = (u32 *)luaL_buffinitsize(L, &buff, size); - sdev.pixels(ptr); - luaL_pushresultsize(&buff, size); - return sol::make_reference(L, sol::stack_reference(L, -1)); - } - ); + auto state_entry_type = sol().registry().new_usertype<device_state_entry>("state_entry", sol::no_constructor); + state_entry_type["value"] = sol::property( + [] (device_state_entry const &entry, sol::this_state s) -> sol::object + { + if (entry.is_float()) + return sol::make_object(s, entry.dvalue()); + else + return sol::make_object(s, entry.value()); + }, + [] (device_state_entry const &entry, sol::this_state s, sol::object value) + { + if (!entry.writeable()) + luaL_error(s, "cannot set value of read-only device state entry"); + else if (entry.is_float()) + entry.set_dvalue(value.as<double>()); + else + entry.set_value(value.as<u64>()); + }); + state_entry_type["symbol"] = sol::property(&device_state_entry::symbol); + state_entry_type["visible"] = sol::property(&device_state_entry::visible); + state_entry_type["writeable"] = sol::property(&device_state_entry::writeable); + state_entry_type["is_float"] = sol::property(&device_state_entry::is_float); + state_entry_type["datamask"] = sol::property( + [] (device_state_entry const &entry) + { + return entry.is_float() ? std::optional<u64>() : std::optional<u64>(entry.datamask()); + }); + state_entry_type["datasize"] = sol::property(&device_state_entry::datasize); + state_entry_type["max_length"] = sol::property(&device_state_entry::max_length); + state_entry_type[sol::meta_function::to_string] = &device_state_entry::to_string; + + + auto format_type = sol().registry().new_usertype<image_device_format>("image_format", sol::no_constructor); + format_type["name"] = sol::property(&image_device_format::name); + format_type["description"] = sol::property(&image_device_format::description); + format_type["extensions"] = sol::property( + [this] (image_device_format const &format) + { + int index = 1; + sol::table option_table = sol().create_table(); + for (std::string const &ext : format.extensions()) + option_table[index++] = ext; + return option_table; + }); + format_type["option_spec"] = sol::property(&image_device_format::optspec); -/* mame_ui_manager library - * - * manager:ui() - * - * ui:is_menu_active() - ui menu state - * ui:options() - ui core_options - * ui:get_line_height() - current ui font height - * ui:get_string_width(str, scale) - get str width with ui font at scale factor of current font size - * ui:get_char_width(char) - get width of utf8 glyph char with ui font - * ui:set_aggressive_input_focus(bool) - * - * ui.single_step - * ui.show_fps - fps display enabled - * ui.show_profiler - profiler display enabled - */ + auto slot_type = sol().registry().new_usertype<device_slot_interface>("slot", sol::no_constructor); + slot_type["fixed"] = sol::property(&device_slot_interface::fixed); + slot_type["has_selectable_options"] = sol::property(&device_slot_interface::has_selectable_options); + slot_type["default_option"] = sol::property(&device_slot_interface::default_option); + slot_type["options"] = sol::property([] (device_slot_interface const &slot) { return standard_tag_object_ptr_map<device_slot_interface::slot_option>(slot.option_list()); }); + slot_type["device"] = sol::property(static_cast<device_t & (device_slot_interface::*)()>(&device_slot_interface::device)); - sol().registry().new_usertype<mame_ui_manager>("ui", "new", sol::no_constructor, - "is_menu_active", &mame_ui_manager::is_menu_active, - "options", [](mame_ui_manager &m) { return static_cast<core_options *>(&m.options()); }, - "show_fps", sol::property(&mame_ui_manager::show_fps, &mame_ui_manager::set_show_fps), - "show_profiler", sol::property(&mame_ui_manager::show_profiler, &mame_ui_manager::set_show_profiler), - "single_step", sol::property(&mame_ui_manager::single_step, &mame_ui_manager::set_single_step), - "get_line_height", &mame_ui_manager::get_line_height, - "get_string_width", &mame_ui_manager::get_string_width, - // sol converts char32_t to a string - "get_char_width", [](mame_ui_manager &m, uint32_t utf8char) { return m.get_char_width(utf8char); }, - "set_aggressive_input_focus", [](mame_ui_manager &m, bool aggressive_focus) { osd_set_aggressive_input_focus(aggressive_focus); }); + auto dislot_option_type = sol().registry().new_usertype<device_slot_interface::slot_option>("dislot_option", sol::no_constructor); + dislot_option_type["name"] = sol::property(&device_slot_interface::slot_option::name); + dislot_option_type["device_fullname"] = sol::property([] (device_slot_interface::slot_option &opt) { return opt.devtype().fullname(); }); + dislot_option_type["device_shortname"] = sol::property([] (device_slot_interface::slot_option &opt) { return opt.devtype().shortname(); }); + dislot_option_type["selectable"] = sol::property(&device_slot_interface::slot_option::selectable); + dislot_option_type["default_bios"] = sol::property(static_cast<char const * (device_slot_interface::slot_option::*)() const>(&device_slot_interface::slot_option::default_bios)); + dislot_option_type["clock"] = sol::property(static_cast<u32 (device_slot_interface::slot_option::*)() const>(&device_slot_interface::slot_option::clock)); -/* device_state_entry library - * - * manager:machine().devices[device_tag].state[state_name] - * - * state:name() - get device state name - * state:is_visible() - is state visible in debugger - * state:is_divider() - is state a divider - * - * state.value - get device state value - */ - sol().registry().new_usertype<device_state_entry>("dev_space", "new", sol::no_constructor, - "name", &device_state_entry::symbol, - "value", sol::property([this](device_state_entry &entry) -> uint64_t { - device_state_interface *state = entry.parent_state(); - if(state) - { - machine().save().dispatch_presave(); - return state->state_int(entry.index()); - } - return 0; - }, - [this](device_state_entry &entry, uint64_t val) { - device_state_interface *state = entry.parent_state(); - if(state) - { - state->set_state_int(entry.index(), val); - machine().save().dispatch_presave(); - } - }), - "is_visible", &device_state_entry::visible, - "is_divider", &device_state_entry::divider); + auto parameters_type = sol().registry().new_usertype<parameters_manager>("parameters", sol::no_constructor); + parameters_type["add"] = ¶meters_manager::add; + parameters_type["lookup"] = ¶meters_manager::lookup; -/* memory_manager library - * - * manager:machine():memory() - * - * memory.banks[] - table of memory banks - * memory.regions[] - table of memory regions - * memory.shares[] - table of memory shares + auto video_type = sol().registry().new_usertype<video_manager>("video", sol::no_constructor); + video_type["frame_update"] = [] (video_manager &vm) { vm.frame_update(true); }; + video_type["snapshot"] = &video_manager::save_active_screen_snapshots; + video_type["begin_recording"] = + [this] (video_manager &vm, const char *filename, const char *format_string) + { + // FIXME: the filename substitution shouldn't be done here + std::string fn; + movie_recording::format format = movie_recording::format::AVI; + if (filename) + fn = process_snapshot_filename(machine(), filename); + if (format_string) + format = s_movie_recording_format_parser(format_string); + vm.begin_recording(filename ? fn.c_str() : nullptr, format); + }; + video_type["end_recording"] = &video_manager::end_recording; + video_type["snapshot_size"] = + [] (video_manager &vm) + { + s32 width, height; + vm.compute_snapshot_size(width, height); + return std::make_tuple(width, height); + }; + video_type["snapshot_pixels"] = + [] (video_manager &vm, sol::this_state s) + { + // TODO: would be better if this could return a tuple of (buffer, width, height) + s32 width, height; + vm.compute_snapshot_size(width, height); + int size = width * height * 4; + luaL_Buffer buff; + u32 *ptr = (u32 *)luaL_buffinitsize(s, &buff, size); + vm.pixels(ptr); + luaL_pushresultsize(&buff, size); + return sol::make_reference(s, sol::stack_reference(s, -1)); + }; + video_type["speed_factor"] = sol::property(&video_manager::speed_factor); + video_type["throttled"] = sol::property(&video_manager::throttled, &video_manager::set_throttled); + video_type["throttle_rate"] = sol::property(&video_manager::throttle_rate, &video_manager::set_throttle_rate); + video_type["frameskip"] = sol::property(&video_manager::frameskip, &video_manager::set_frameskip); + video_type["speed_percent"] = sol::property(&video_manager::speed_percent); + video_type["effective_frameskip"] = sol::property(&video_manager::effective_frameskip); + video_type["skip_this_frame"] = sol::property(&video_manager::skip_this_frame); + video_type["snap_native"] = sol::property(&video_manager::snap_native); + video_type["is_recording"] = sol::property(&video_manager::is_recording); + video_type["snapshot_target"] = sol::property(&video_manager::snapshot_target); + + + auto sound_type = sol().registry().new_usertype<sound_manager>("sound", sol::no_constructor); + sound_type["start_recording"] = + [] (sound_manager &sm, char const *filename) + { + return filename ? sm.start_recording(filename) : sm.start_recording(); + }; + sound_type["stop_recording"] = &sound_manager::stop_recording; + sound_type["muted"] = sol::property(&sound_manager::muted); + sound_type["ui_mute"] = sol::property( + static_cast<bool (sound_manager::*)() const>(&sound_manager::ui_mute), + static_cast<void (sound_manager::*)(bool)>(&sound_manager::ui_mute)); + sound_type["debugger_mute"] = sol::property( + static_cast<bool (sound_manager::*)() const>(&sound_manager::debugger_mute), + static_cast<void (sound_manager::*)(bool)>(&sound_manager::debugger_mute)); + sound_type["system_mute"] = sol::property( + static_cast<bool (sound_manager::*)() const>(&sound_manager::system_mute), + static_cast<void (sound_manager::*)(bool)>(&sound_manager::system_mute)); + sound_type["recording"] = sol::property(&sound_manager::is_recording); + + + auto ui_type = sol().registry().new_usertype<mame_ui_manager>("ui", sol::no_constructor); + // sol converts char32_t to a string + ui_type.set_function("get_char_width", [] (mame_ui_manager &m, uint32_t utf8char) { return m.get_char_width(utf8char); }); + ui_type.set_function("get_string_width", static_cast<float (mame_ui_manager::*)(std::string_view)>(&mame_ui_manager::get_string_width)); + ui_type.set_function("set_aggressive_input_focus", [] (mame_ui_manager &m, bool aggressive_focus) { osd_set_aggressive_input_focus(aggressive_focus); }); + ui_type["get_general_input_setting"] = sol::overload( + // TODO: overload with sequence type string - parser isn't available here + [] (mame_ui_manager &ui, ioport_type type, int player) { return ui.get_general_input_setting(type, player, SEQ_TYPE_STANDARD); }, + [] (mame_ui_manager &ui, ioport_type type) { return ui.get_general_input_setting(type, 0, SEQ_TYPE_STANDARD); }); + ui_type["options"] = sol::property([] (mame_ui_manager &m) { return static_cast<core_options *>(&m.options()); }); + ui_type["line_height"] = sol::property([] (mame_ui_manager &m) { return m.get_line_height(); }); + ui_type["menu_active"] = sol::property(&mame_ui_manager::is_menu_active); + ui_type["ui_active"] = sol::property(&mame_ui_manager::ui_active, &mame_ui_manager::set_ui_active); + ui_type["single_step"] = sol::property(&mame_ui_manager::single_step, &mame_ui_manager::set_single_step); + ui_type["show_fps"] = sol::property(&mame_ui_manager::show_fps, &mame_ui_manager::set_show_fps); + ui_type["show_profiler"] = sol::property(&mame_ui_manager::show_profiler, &mame_ui_manager::set_show_profiler); + ui_type["image_display_enabled"] = sol::property(&mame_ui_manager::image_display_enabled, &mame_ui_manager::set_image_display_enabled); + + // undocumented/unsupported + ui_type["show_menu"] = &mame_ui_manager::show_menu; // FIXME: this is dangerous - it doesn't give a proper chance for the current UI handler to clean up + + +/* rom_entry library + * + * manager:machine().devices[device_tag].roms[rom] + * + * rom:name() + * rom:hashdata() - see hash.h + * rom:offset() + * rom:length() + * rom:flags() - see romentry.h */ - sol().registry().new_usertype<memory_manager>("memory", "new", sol::no_constructor, - "banks", sol::property([this](memory_manager &mm) { - sol::table table = sol().create_table(); - for (auto &bank : mm.banks()) - table[bank.second->tag()] = bank.second.get(); - return table; - }), - "regions", sol::property([this](memory_manager &mm) { - sol::table table = sol().create_table(); - for (auto ®ion : mm.regions()) - table[region.second->name()] = region.second.get(); - return table; - }), - "shares", sol::property([this](memory_manager &mm) { - sol::table table = sol().create_table(); - for (auto &share : mm.shares()) - table[share.first] = share.second.get(); - return table; - })); + auto rom_entry_type = sol().registry().new_usertype<rom_entry>("rom_entry", "new", sol::no_constructor); + rom_entry_type.set("name", &rom_entry::name); + rom_entry_type.set("hashdata", &rom_entry::hashdata); + rom_entry_type.set("offset", &rom_entry::get_offset); + rom_entry_type.set("length", &rom_entry::get_length); + rom_entry_type.set("flags", &rom_entry::get_flags); -/* memory_region library - * - * manager:machine():memory().regions[region_tag] - * - * read/write by signedness u/i and bit-width 8/16/32/64: - * region:read_*(addr) - * region:write_*(addr, val) - * - * region.size - */ + auto output_type = sol().registry().new_usertype<output_manager>("output", sol::no_constructor); + output_type["set_value"] = &output_manager::set_value; + output_type["set_indexed_value"] = + [] (output_manager &o, char const *basename, int index, int value) + { + o.set_value(util::string_format("%s%d", basename, index), value); + }; + output_type["get_value"] = &output_manager::get_value; + output_type["get_indexed_value"] = + [] (output_manager &o, char const *basename, int index) + { + return o.get_value(util::string_format("%s%d", basename, index)); + }; + output_type["name_to_id"] = &output_manager::name_to_id; + output_type["id_to_name"] = &output_manager::id_to_name; - sol().registry().new_usertype<memory_region>("region", "new", sol::no_constructor, - "read_i8", ®ion_read<int8_t>, - "read_u8", ®ion_read<uint8_t>, - "read_i16", ®ion_read<int16_t>, - "read_u16", ®ion_read<uint16_t>, - "read_i32", ®ion_read<int32_t>, - "read_u32", ®ion_read<uint32_t>, - "read_i64", ®ion_read<int64_t>, - "read_u64", ®ion_read<uint64_t>, - "write_i8", ®ion_write<int8_t>, - "write_u8", ®ion_write<uint8_t>, - "write_i16", ®ion_write<int16_t>, - "write_u16", ®ion_write<uint16_t>, - "write_i32", ®ion_write<int32_t>, - "write_u32", ®ion_write<uint32_t>, - "write_i64", ®ion_write<int64_t>, - "write_u64", ®ion_write<uint64_t>, - "size", sol::property(&memory_region::bytes)); - - -/* memory_share library - * - * manager:machine():memory().shares[share_tag] - * - * read/write by signedness u/i and bit-width 8/16/32/64: - * share:read_*(addr) - * share:write_*(addr, val) - * - * region.size -*/ - sol().registry().new_usertype<memory_share>("share", "new", sol::no_constructor, - "read_i8", &share_read<int8_t>, - "read_u8", &share_read<uint8_t>, - "read_i16", &share_read<int16_t>, - "read_u16", &share_read<uint16_t>, - "read_i32", &share_read<int32_t>, - "read_u32", &share_read<uint32_t>, - "read_i64", &share_read<int64_t>, - "read_u64", &share_read<uint64_t>, - "write_i8", &share_write<int8_t>, - "write_u8", &share_write<uint8_t>, - "write_i16", &share_write<int16_t>, - "write_u16", &share_write<uint16_t>, - "write_i32", &share_write<int32_t>, - "write_u32", &share_write<uint32_t>, - "write_i64", &share_write<int64_t>, - "write_u64", &share_write<uint64_t>, - "size", sol::property(&memory_share::bytes)); - - -/* output_manager library - * - * manager:machine():outputs() - * - * outputs:set_value(name, val) - set output name to val - * outputs:set_indexed_value(index, val) - set output index to val - * outputs:get_value(name) - get output name value - * outputs:get_indexed_value(index) - get output index value - * outputs:name_to_id(name) - get index for name - * outputs:id_to_name(index) - get name for index - */ + auto mame_manager_type = sol().registry().new_usertype<mame_machine_manager>("manager", sol::no_constructor); + mame_manager_type["machine"] = sol::property(&mame_machine_manager::machine); + mame_manager_type["ui"] = sol::property(&mame_machine_manager::ui); + mame_manager_type["options"] = sol::property(&mame_machine_manager::options); + mame_manager_type["plugins"] = sol::property([] (mame_machine_manager &m) { return plugin_options_plugins(m.plugins()); }); + sol()["manager"] = std::ref(*mame_machine_manager::instance()); + sol()["mame_manager"] = std::ref(*mame_machine_manager::instance()); - sol().registry().new_usertype<output_manager>("output", "new", sol::no_constructor, - "set_value", &output_manager::set_value, - "set_indexed_value", [](output_manager &o, char const *basename, int index, int value) { o.set_value(util::string_format("%s%d", basename, index).c_str(), value); }, - "get_value", &output_manager::get_value, - "get_indexed_value", [](output_manager &o, char const *basename, int index) { return o.get_value(util::string_format("%s%d", basename, index).c_str()); }, - "name_to_id", &output_manager::name_to_id, - "id_to_name", &output_manager::id_to_name); + auto plugin_type = sol().registry().new_usertype<plugin_options::plugin>("plugin", sol::no_constructor); + plugin_type["name"] = sol::readonly(&plugin_options::plugin::m_name); + plugin_type["description"] = sol::readonly(&plugin_options::plugin::m_description); + plugin_type["type"] = sol::readonly(&plugin_options::plugin::m_type); + plugin_type["directory"] = sol::readonly(&plugin_options::plugin::m_directory); + plugin_type["start"] = sol::readonly(&plugin_options::plugin::m_start); -/* device_image_interface library - * - * manager:machine().images[image_type] - * - * image:exists() - * image:filename() - full path to the image file - * image:longname() - * image:manufacturer() - * image:year() - * image:software_list_name() - * image:image_type_name() - floppy/cart/cdrom/tape/hdd etc - * image:load() - * image:unload() - * image:create() - * image:crc() - * image:display() - * - * image.device - get associated device_t - * image.instance_name - * image.brief_instance_name - * image.software_parent - * image.is_readable - * image.is_writeable - * image.is_creatable - * image.is_reset_on_load - * image.must_be_loaded - */ - sol().registry().new_usertype<device_image_interface>("image", "new", sol::no_constructor, - "exists", &device_image_interface::exists, - "filename", &device_image_interface::filename, - "longname", &device_image_interface::longname, - "manufacturer", &device_image_interface::manufacturer, - "year", &device_image_interface::year, - "software_list_name", &device_image_interface::software_list_name, - "software_parent", sol::property([](device_image_interface &di) { const software_info *si = di.software_entry(); return si ? si->parentname() : ""; }), - "image_type_name", &device_image_interface::image_type_name, - "load", &device_image_interface::load, - "unload", &device_image_interface::unload, - "create", [](device_image_interface &di, const std::string &filename) { return di.create(filename); }, - "crc", &device_image_interface::crc, - "display", [](device_image_interface &di) { return di.call_display(); }, - "device", sol::property(static_cast<const device_t &(device_image_interface::*)() const>(&device_image_interface::device)), - "instance_name", sol::property(&device_image_interface::instance_name), - "brief_instance_name", sol::property(&device_image_interface::brief_instance_name), - "is_readable", sol::property(&device_image_interface::is_readable), - "is_writeable", sol::property(&device_image_interface::is_writeable), - "is_creatable", sol::property(&device_image_interface::is_creatable), - "is_reset_on_load", sol::property(&device_image_interface::is_reset_on_load), - "must_be_loaded", sol::property(&device_image_interface::must_be_loaded) - ); - - -/* mame_machine_manager library - * - * manager - * mame_manager - alias of manager - * - * manager:machine() - running machine - * manager:options() - core options - * manager:plugins() - plugin options - * manager:ui() - mame ui manager - */ - - sol().registry().new_usertype<mame_machine_manager>("manager", "new", sol::no_constructor, - "machine", &machine_manager::machine, - "options", [](mame_machine_manager &m) { return static_cast<core_options *>(&m.options()); }, - "plugins", [this](mame_machine_manager &m) { - sol::table table = sol().create_table(); - for (auto &curentry : m.plugins().plugins()) - { - sol::table plugin_table = sol().create_table(); - plugin_table["name"] = curentry.m_name; - plugin_table["description"] = curentry.m_description; - plugin_table["type"] = curentry.m_type; - plugin_table["directory"] = curentry.m_directory; - plugin_table["start"] = curentry.m_start; - table[curentry.m_name] = plugin_table; - } - return table; - }, - "ui", &mame_machine_manager::ui); - sol()["manager"] = std::ref(*mame_machine_manager::instance()); - sol()["mame_manager"] = std::ref(*mame_machine_manager::instance()); + // set up other user types + initialize_debug(emu); + initialize_input(emu); + initialize_memory(emu); + initialize_render(emu); } //------------------------------------------------- @@ -2702,6 +2229,10 @@ void lua_engine::initialize() //------------------------------------------------- bool lua_engine::frame_hook() { + std::vector<int> tasks = std::move(m_update_tasks); + m_update_tasks.clear(); + resume_tasks(m_lua_state, tasks, true); // TODO: doesn't need to return anything + return execute_function("LUA_ON_FRAME_DONE"); } @@ -2711,6 +2242,10 @@ bool lua_engine::frame_hook() void lua_engine::close() { + m_notifiers.reset(); + m_menu.clear(); + m_update_tasks.clear(); + m_frame_tasks.clear(); m_sol_state.reset(); if (m_lua_state) { @@ -2720,62 +2255,46 @@ void lua_engine::close() } } -void lua_engine::resume(void *ptr, int nparam) +void lua_engine::resume(s32 param) { - lua_rawgeti(m_lua_state, LUA_REGISTRYINDEX, nparam); - lua_State *L = lua_tothread(m_lua_state, -1); - lua_pop(m_lua_state, 1); - int stat = lua_resume(L, nullptr, 0); - if((stat != LUA_OK) && (stat != LUA_YIELD)) - { - osd_printf_error("[LUA ERROR] in resume: %s\n", lua_tostring(L, -1)); - lua_pop(L, 1); - } - luaL_unref(m_lua_state, LUA_REGISTRYINDEX, nparam); -} - -void lua_engine::run(sol::load_result res) -{ - if(res.valid()) - { - auto ret = invoke(res.get<sol::protected_function>()); - if(!ret.valid()) - { - sol::error err = ret; - osd_printf_error("[LUA ERROR] in run: %s\n", err.what()); - } - } - else - osd_printf_error("[LUA ERROR] %d loading Lua script\n", (int)res.status()); + attotime const now = machine().time(); + auto const pos = std::find_if( + m_waiting_tasks.begin(), + m_waiting_tasks.end(), + [&now] (auto const &x) { return now < x.first; }); + std::vector<int> expired; + expired.reserve(std::distance(m_waiting_tasks.begin(), pos)); + for (auto it = m_waiting_tasks.begin(); pos != it; ++it) + expired.emplace_back(it->second); + m_waiting_tasks.erase(m_waiting_tasks.begin(), pos); + if (!m_waiting_tasks.empty()) + m_timer->reset(m_waiting_tasks.begin()->first - now); + resume_tasks(m_lua_state, expired, true); } //------------------------------------------------- -// execute - load and execute script +// load_script - load script from file path //------------------------------------------------- -void lua_engine::load_script(const char *filename) +sol::load_result lua_engine::load_script(std::string const &filename) { - run(sol().load_file(filename)); + return sol().load_file(filename); } //------------------------------------------------- -// execute_string - execute script from string +// load_string - load script from string //------------------------------------------------- -void lua_engine::load_string(const char *value) +sol::load_result lua_engine::load_string(std::string const &value) { - run(sol().load(value)); + return sol().load(value); } //------------------------------------------------- -// invoke - invokes a function, wrapping profiler +// make_environment - make a sandbox //------------------------------------------------- -template<typename TFunc, typename... TArgs> -sol::protected_function_result lua_engine::invoke(TFunc &&func, TArgs&&... args) +sol::environment lua_engine::make_environment() { - g_profiler.start(PROFILER_LUA); - sol::protected_function_result result = func(std::forward<TArgs>(args)...); - g_profiler.stop(); - return result; + return sol::environment(sol(), sol::create, sol().globals()); } diff --git a/src/frontend/mame/luaengine.h b/src/frontend/mame/luaengine.h index 820f5a0baf3..a146ab2c0d2 100644 --- a/src/frontend/mame/luaengine.h +++ b/src/frontend/mame/luaengine.h @@ -7,59 +7,71 @@ Controls execution of the core MAME system. ***************************************************************************/ - #ifndef MAME_FRONTEND_MAME_LUAENGINE_H #define MAME_FRONTEND_MAME_LUAENGINE_H #pragma once -#ifndef __EMU_H__ -#error Dont include this file directly; include emu.h instead. -#endif - -#if defined(__GNUC__) && (__GNUC__ > 6) -#pragma GCC diagnostic ignored "-Wnoexcept-type" -#endif +#include "notifier.h" +#include <functional> #include <map> -#include <condition_variable> -#define SOL_SAFE_USERTYPE -//#define SOL_CHECK_ARGUMENTS -#include "sol2/sol.hpp" +#include <memory> +#include <optional> +#include <string> +#include <tuple> +#include <vector> + +#define SOL_USING_CXX_LUA 1 +#ifdef MAME_DEBUG +#define SOL_ALL_SAFETIES_ON 1 +#else +#define SOL_SAFE_USERTYPE 1 +#endif +#include "sol/sol.hpp" struct lua_State; class lua_engine { public: + // helper structures + template <typename T> struct devenum; + template <typename T> struct simple_list_wrapper; + template <typename T> struct tag_object_ptr_map; + template <typename T> using standard_tag_object_ptr_map = tag_object_ptr_map<std::unordered_map<std::string, std::unique_ptr<T> > >; + template <typename T> struct immutable_container_helper; + template <typename T, typename C, typename I = typename C::iterator> struct immutable_collection_helper; + template <typename T, typename C, typename I = typename C::iterator> struct immutable_sequence_helper; + // construction/destruction lua_engine(); ~lua_engine(); void initialize(); - void load_script(const char *filename); - void load_string(const char *value); + sol::load_result load_script(std::string const &filename); + sol::load_result load_string(std::string const &value); + sol::environment make_environment(); bool frame_hook(); - void menu_populate(const std::string &menu, std::vector<std::tuple<std::string, std::string, std::string>> &menu_list); - bool menu_callback(const std::string &menu, int index, const std::string &event); + std::optional<long> menu_populate(const std::string &menu, std::vector<std::tuple<std::string, std::string, std::string> > &menu_list, std::string &flags); + std::pair<bool, std::optional<long> > menu_callback(const std::string &menu, int index, const std::string &event); - void set_machine(running_machine *machine) { m_machine = machine; } - std::vector<std::string> &get_menu() { return m_menu; } + void set_machine(running_machine *machine); + std::vector<std::string> const &get_menu() { return m_menu; } void attach_notifiers(); - void on_frame_done(); - void on_sound_update(); + void on_sound_update(const std::map<std::string, std::vector<std::pair<const sound_stream::sample_t *, int>>> &sound); void on_periodic(); bool on_missing_mandatory_image(const std::string &instance_name); void on_machine_before_load_settings(); - template<typename T, typename U> - bool call_plugin(const std::string &name, const T in, U &out) + template <typename T, typename U> + bool call_plugin(const std::string &name, T &&in, U &out) { bool ret = false; - sol::object outobj = call_plugin(name, sol::make_object(sol(), in)); - if(outobj.is<U>()) + sol::object outobj = call_plugin(name, sol::make_object(sol(), std::forward<T>(in))); + if (outobj.is<U>()) { out = outobj.as<U>(); ret = true; @@ -67,16 +79,16 @@ public: return ret; } - template<typename T, typename U> - bool call_plugin(const std::string &name, const T in, std::vector<U> &out) + template <typename T, typename U> + bool call_plugin(const std::string &name, T &&in, std::vector<U> &out) { bool ret = false; - sol::object outobj = call_plugin(name, sol::make_object(sol(), in)); - if(outobj.is<sol::table>()) + sol::object outobj = call_plugin(name, sol::make_object(sol(), std::forward<T>(in))); + if (outobj.is<sol::table>()) { - for(auto &entry : outobj.as<sol::table>()) + for (auto &entry : outobj.as<sol::table>()) { - if(entry.second.template is<U>()) + if (entry.second.template is<U>()) { out.push_back(entry.second.template as<U>()); ret = true; @@ -87,30 +99,70 @@ public: } // this can also check if a returned table contains type T - template<typename T, typename U> - bool call_plugin_check(const std::string &name, const U in, bool table = false) + template <typename T, typename U> + bool call_plugin_check(const std::string &name, U &&in, bool table = false) { bool ret = false; - sol::object outobj = call_plugin(name, sol::make_object(sol(), in)); - if(outobj.is<T>() && !table) + sol::object outobj = call_plugin(name, sol::make_object(sol(), std::forward<U>(in))); + if (outobj.is<T>() && !table) ret = true; - else if(outobj.is<sol::table>() && table) + else if (outobj.is<sol::table>() && table) { // check just one entry, checking the whole thing shouldn't be necessary as this only supports homogeneous tables - if(outobj.as<sol::table>().begin().operator*().second.template is<T>()) + if (outobj.as<sol::table>().begin().operator*().second.template is<T>()) ret = true; } return ret; } - template<typename T> - void call_plugin_set(const std::string &name, const T in) + template <typename T> + void call_plugin_set(const std::string &name, T &&in) { - call_plugin(name, sol::make_object(sol(), in)); + call_plugin(name, sol::make_object(sol(), std::forward<T>(in))); } sol::state_view &sol() const { return *m_sol_state; } + + template <typename Func, typename... Params> + sol::protected_function_result invoke(Func &&func, Params&&... args) + { + auto profile = g_profiler.start(PROFILER_LUA); + + sol::thread th = sol::thread::create(m_lua_state); + sol::coroutine cr(th.state(), std::forward<Func>(func)); + return cr(std::forward<Params>(args)...); + } + + template <typename Func, typename... Params> + static auto invoke_direct(Func &&func, Params&&... args) + { + auto profile = g_profiler.start(PROFILER_LUA); + return func(std::forward<Params>(args)...); + } + private: + struct notifiers + { + util::notifier<> on_reset; + util::notifier<> on_stop; + util::notifier<> on_pause; + util::notifier<> on_resume; + util::notifier<> on_frame; + util::notifier<> on_presave; + util::notifier<> on_postload; + }; + + template <typename T, size_t Size> class enum_parser; + + class buffer_helper; + struct addr_space; + class palette_wrapper; + template <typename T> class bitmap_helper; + class tap_helper; + class addr_space_change_notif; + class symbol_table_wrapper; + class expression_wrapper; + // internal state lua_State *m_lua_state; std::unique_ptr<sol::state_view> m_sol_state; @@ -118,61 +170,44 @@ private: std::vector<std::string> m_menu; + emu_timer *m_timer; + + // machine event notifiers + std::optional<notifiers> m_notifiers; + + // deferred coroutines + std::vector<std::pair<attotime, int> > m_waiting_tasks; + std::vector<int> m_update_tasks; + std::vector<int> m_frame_tasks; + + template <typename... T> + auto make_notifier_adder(util::notifier<T...> ¬ifier, const char *desc); + template <typename T, typename D, typename R, typename... A> + auto make_simple_callback_setter(void (T::*setter)(delegate<R (A...)> &&), D &&dflt, const char *name, const char *desc); + running_machine &machine() const { return *m_machine; } void on_machine_prestart(); - void on_machine_start(); + void on_machine_reset(); void on_machine_stop(); void on_machine_pause(); void on_machine_resume(); void on_machine_frame(); + void on_machine_presave(); + void on_machine_postload(); - void resume(void *ptr, int nparam); + void resume(s32 param); void register_function(sol::function func, const char *id); - int enumerate_functions(const char *id, std::function<bool(const sol::protected_function &func)> &&callback); - bool execute_function(const char *id); + template <typename T> size_t enumerate_functions(const char *id, T &&callback); + template <typename... Params> bool execute_function(const char *id, Params&&... args); sol::object call_plugin(const std::string &name, sol::object in); - struct addr_space { - addr_space(address_space &space, device_memory_interface &dev) : - space(space), dev(dev) {} - template<typename T> T mem_read(offs_t address); - template<typename T> void mem_write(offs_t address, T val); - template<typename T> T log_mem_read(offs_t address); - template<typename T> void log_mem_write(offs_t address, T val); - template<typename T> T direct_mem_read(offs_t address); - template<typename T> void direct_mem_write(offs_t address, T val); - - address_space &space; - device_memory_interface &dev; - }; - - template<typename T> static T share_read(memory_share &share, offs_t address); - template<typename T> static void share_write(memory_share &share, offs_t address, T val); - template<typename T> static T region_read(memory_region ®ion, offs_t address); - template<typename T> static void region_write(memory_region ®ion, offs_t address, T val); - - struct save_item { - void *base; - unsigned int size; - unsigned int count; - }; - void close(); - void run(sol::load_result res); - - struct context - { - context() { busy = false; yield = false; } - std::string result; - std::condition_variable sync; - bool busy; - bool yield; - }; - - template<typename TFunc, typename... TArgs> - sol::protected_function_result invoke(TFunc &&func, TArgs&&... args); + void initialize_debug(sol::table &emu); + void initialize_input(sol::table &emu); + void initialize_memory(sol::table &emu); + void initialize_render(sol::table &emu); }; #endif // MAME_FRONTEND_MAME_LUAENGINE_H diff --git a/src/frontend/mame/luaengine.ipp b/src/frontend/mame/luaengine.ipp new file mode 100644 index 00000000000..e08f267538e --- /dev/null +++ b/src/frontend/mame/luaengine.ipp @@ -0,0 +1,660 @@ +// license:BSD-3-Clause +// copyright-holders:Miodrag Milanovic +/*************************************************************************** + + luaengine.ipp + + Controls execution of the core MAME system. + +***************************************************************************/ +#ifndef MAME_FRONTEND_MAME_LUAENGINE_IPP +#define MAME_FRONTEND_MAME_LUAENGINE_IPP + +#include "luaengine.h" + +#include "options.h" + +#include <lua.h> +#include <lualib.h> +#include <lauxlib.h> + +#include <cassert> +#include <system_error> +#include <type_traits> + + + +template <typename T> +struct lua_engine::simple_list_wrapper +{ + simple_list_wrapper(simple_list<T> const &l) : list(l) { } + + simple_list<T> const &list; +}; + + +template <typename T> +struct lua_engine::tag_object_ptr_map +{ + tag_object_ptr_map(T const &m) : map(m) { } + + T const ↦ +}; + + +class lua_engine::buffer_helper +{ +private: + class proxy + { + private: + buffer_helper &m_host; + char *m_space; + size_t const m_size; + + public: + proxy(proxy const &) = delete; + proxy &operator=(proxy const &) = delete; + + proxy(proxy &&that) : m_host(that.m_host), m_space(that.m_space), m_size(that.m_size) + { + that.m_space = nullptr; + } + + proxy(buffer_helper &host, size_t size) : m_host(host), m_space(luaL_prepbuffsize(&host.m_buffer, size)), m_size(size) + { + m_host.m_prepared = true; + } + + ~proxy() + { + if (m_space) + { + assert(m_host.m_prepared); + luaL_addsize(&m_host.m_buffer, 0U); + m_host.m_prepared = false; + } + } + + char *get() + { + return m_space; + } + + void add(size_t size) + { + assert(m_space); + assert(size <= m_size); + assert(m_host.m_prepared); + m_space = nullptr; + luaL_addsize(&m_host.m_buffer, size); + m_host.m_prepared = false; + } + }; + + luaL_Buffer m_buffer; + bool m_valid; + bool m_prepared; + +public: + buffer_helper(buffer_helper const &) = delete; + buffer_helper &operator=(buffer_helper const &) = delete; + + buffer_helper(lua_State *L) + { + luaL_buffinit(L, &m_buffer); + m_valid = true; + m_prepared = false; + } + + ~buffer_helper() + { + assert(!m_prepared); + if (m_valid) + luaL_pushresult(&m_buffer); + } + + void push() + { + assert(m_valid); + assert(!m_prepared); + luaL_pushresult(&m_buffer); + m_valid = false; + } + + proxy prepare(size_t size) + { + assert(m_valid); + assert(!m_prepared); + return proxy(*this, size); + } +}; + + +class lua_engine::palette_wrapper +{ +public: + palette_wrapper(uint32_t numcolors, uint32_t numgroups) : m_palette(palette_t::alloc(numcolors, numgroups)) + { + } + + palette_wrapper(palette_t &pal) : m_palette(&pal) + { + m_palette->ref(); + } + + palette_wrapper(palette_wrapper const &that) : m_palette(that.m_palette) + { + m_palette->ref(); + } + + ~palette_wrapper() + { + m_palette->deref(); + } + + palette_wrapper &operator=(palette_wrapper const &that) + { + that.m_palette->ref(); + m_palette->deref(); + m_palette = that.m_palette; + return *this; + } + + palette_t const &palette() const + { + return *m_palette; + } + + palette_t &palette() + { + return *m_palette; + } + +private: + palette_t *m_palette; +}; + + +namespace sol { + +// don't convert core_options to a table directly +template <> struct is_container<core_options> : std::false_type { }; + + +// these things should be treated as containers +template <typename T> struct is_container<lua_engine::devenum<T> > : std::true_type { }; +template <typename T> struct is_container<lua_engine::simple_list_wrapper<T> > : std::true_type { }; +template <typename T> struct is_container<lua_engine::tag_object_ptr_map<T> > : std::true_type { }; + + +template <typename T> struct usertype_container<lua_engine::devenum<T> >; + + +template <typename T> +struct usertype_container<lua_engine::simple_list_wrapper<T> > : lua_engine::immutable_collection_helper<lua_engine::simple_list_wrapper<T>, simple_list<T> const, typename simple_list<T>::auto_iterator> +{ +private: + static int next_pairs(lua_State *L) + { + typename usertype_container::indexed_iterator &i(stack::unqualified_get<user<typename usertype_container::indexed_iterator> >(L, 1)); + if (i.src.end() == i.it) + return stack::push(L, lua_nil); + int result; + result = stack::push(L, i.ix + 1); + result += stack::push_reference(L, *i.it); + ++i; + return result; + } + +public: + static int at(lua_State *L) + { + lua_engine::simple_list_wrapper<T> &self(usertype_container::get_self(L)); + std::ptrdiff_t const index(stack::unqualified_get<std::ptrdiff_t>(L, 2)); + if ((0 >= index) || (self.list.count() < index)) + return stack::push(L, lua_nil); + else + return stack::push_reference(L, *self.list.find(index - 1)); + } + + static int get(lua_State *L) { return at(L); } + static int index_get(lua_State *L) { return at(L); } + + static int index_of(lua_State *L) + { + lua_engine::simple_list_wrapper<T> &self(usertype_container::get_self(L)); + T &target(stack::unqualified_get<T>(L, 2)); + int const found(self.list.indexof(target)); + if (0 > found) + return stack::push(L, lua_nil); + else + return stack::push(L, found + 1); + } + + static int size(lua_State *L) + { + lua_engine::simple_list_wrapper<T> &self(usertype_container::get_self(L)); + return stack::push(L, self.list.count()); + } + + static int empty(lua_State *L) + { + lua_engine::simple_list_wrapper<T> &self(usertype_container::get_self(L)); + return stack::push(L, self.list.empty()); + } + + static int next(lua_State *L) { return stack::push(L, next_pairs); } + static int pairs(lua_State *L) { return ipairs(L); } + + static int ipairs(lua_State *L) + { + lua_engine::simple_list_wrapper<T> &self(usertype_container::get_self(L)); + stack::push(L, next_pairs); + stack::push<user<typename usertype_container::indexed_iterator> >(L, self.list, self.list.begin()); + stack::push(L, lua_nil); + return 3; + } +}; + + +template <typename T> +struct usertype_container<lua_engine::tag_object_ptr_map<T> > : lua_engine::immutable_collection_helper<lua_engine::tag_object_ptr_map<T>, T const, typename T::const_iterator> +{ +private: + template <bool Indexed> + static int next_pairs(lua_State *L) + { + typename usertype_container::indexed_iterator &i(stack::unqualified_get<user<typename usertype_container::indexed_iterator> >(L, 1)); + if (i.src.end() == i.it) + return stack::push(L, lua_nil); + int result; + if constexpr (Indexed) + result = stack::push(L, i.ix + 1); + else + result = stack::push(L, i.it->first); + result += stack::push_reference(L, *i.it->second); + ++i; + return result; + } + + template <bool Indexed> + static int start_pairs(lua_State *L) + { + lua_engine::tag_object_ptr_map<T> &self(usertype_container::get_self(L)); + stack::push(L, next_pairs<Indexed>); + stack::push<user<typename usertype_container::indexed_iterator> >(L, self.map, self.map.begin()); + stack::push(L, lua_nil); + return 3; + } + +public: + static int at(lua_State *L) + { + lua_engine::tag_object_ptr_map<T> &self(usertype_container::get_self(L)); + std::ptrdiff_t const index(stack::unqualified_get<std::ptrdiff_t>(L, 2)); + if ((0 >= index) || (self.map.size() < index)) + return stack::push(L, lua_nil); + auto const found(std::next(self.map.begin(), index - 1)); + if (!found->second) + return stack::push(L, lua_nil); + else + return stack::push_reference(L, *found->second); + } + + static int get(lua_State *L) + { + lua_engine::tag_object_ptr_map<T> &self(usertype_container::get_self(L)); + char const *const tag(stack::unqualified_get<char const *>(L)); + auto const found(self.map.find(tag)); + if ((self.map.end() == found) || !found->second) + return stack::push(L, lua_nil); + else + return stack::push_reference(L, *found->second); + } + + static int index_get(lua_State *L) + { + return get(L); + } + + static int index_of(lua_State *L) + { + lua_engine::tag_object_ptr_map<T> &self(usertype_container::get_self(L)); + auto &obj(stack::unqualified_get<decltype(*self.map.begin()->second)>(L, 2)); + auto it(self.map.begin()); + std::ptrdiff_t ix(0); + while ((self.map.end() != it) && (it->second.get() != &obj)) + { + ++it; + ++ix; + } + if (self.map.end() == it) + return stack::push(L, lua_nil); + else + return stack::push(L, ix + 1); + } + + static int size(lua_State *L) + { + lua_engine::tag_object_ptr_map<T> &self(usertype_container::get_self(L)); + return stack::push(L, self.map.size()); + } + + static int empty(lua_State *L) + { + lua_engine::tag_object_ptr_map<T> &self(usertype_container::get_self(L)); + return stack::push(L, self.map.empty()); + } + + static int next(lua_State *L) { return stack::push(L, next_pairs<false>); } + static int pairs(lua_State *L) { return start_pairs<false>(L); } + static int ipairs(lua_State *L) { return start_pairs<true>(L); } +}; + +} // namespace sol + + +// automatically convert std::error_condition to string +int sol_lua_push(sol::types<std::error_condition>, lua_State &L, std::error_condition &&value); + +// enums to automatically convert to strings +int sol_lua_push(sol::types<map_handler_type>, lua_State *L, map_handler_type &&value); +int sol_lua_push(sol::types<endianness_t>, lua_State *L, endianness_t &&value); + + +template <typename T> +struct lua_engine::immutable_container_helper +{ +protected: + static T &get_self(lua_State *L) + { + auto p(sol::stack::unqualified_check_get<T *>(L, 1)); + if (!p) + luaL_error(L, "sol: 'self' is not of type '%s' (pass 'self' as first argument with ':' or call on proper type)", sol::detail::demangle<T>().c_str()); + if (!*p) + luaL_error(L, "sol: 'self' argument is nil (pass 'self' as first argument with ':' or call on a '%s' type", sol::detail::demangle<T>().c_str()); + return **p; + } + +public: + static int set(lua_State *L) + { + return luaL_error(L, "sol: cannot call 'set(key, value)' on type '%s': container is not modifiable", sol::detail::demangle<T>().c_str()); + } + + static int index_set(lua_State *L) + { + return luaL_error(L, "sol: cannot call 'container[key] = value' on type '%s': container is not modifiable", sol::detail::demangle<T>().c_str()); + } + + static int add(lua_State *L) + { + return luaL_error(L, "sol: cannot call 'add' on type '%s': container is not modifiable", sol::detail::demangle<T>().c_str()); + } + + static int insert(lua_State *L) + { + return luaL_error(L, "sol: cannot call 'insert' on type '%s': container is not modifiable", sol::detail::demangle<T>().c_str()); + } + + static int find(lua_State *L) + { + return luaL_error(L, "sol: cannot call 'find' on type '%s': no supported comparison operator for the value type", sol::detail::demangle<T>().c_str()); + } + + static int index_of(lua_State *L) + { + return luaL_error(L, "sol: cannot call 'index_of' on type '%s': no supported comparison operator for the value type", sol::detail::demangle<T>().c_str()); + } + + static int clear(lua_State *L) + { + return luaL_error(L, "sol: cannot call 'clear' on type '%s': container is not modifiable", sol::detail::demangle<T>().c_str()); + } + + static int erase(lua_State *L) + { + return luaL_error(L, "sol: cannot call 'erase' on type '%s': container is not modifiable", sol::detail::demangle<T>().c_str()); + } +}; + + +template <typename T, typename C, typename I> +struct lua_engine::immutable_collection_helper : immutable_container_helper<T> +{ +protected: + using iterator = I; + + struct indexed_iterator + { + indexed_iterator(C &s, iterator i) : src(s), it(i), ix(0U) { } + + C &src; + iterator it; + std::size_t ix; + + indexed_iterator &operator++() + { + ++it; + ++ix; + return *this; + } + }; +}; + + +template <typename T, typename C, typename I> +struct lua_engine::immutable_sequence_helper : immutable_collection_helper<T, C, I> +{ +protected: + template <bool Indexed> + static int next_pairs(lua_State *L) + { + auto &i(sol::stack::unqualified_get<sol::user<typename immutable_sequence_helper::indexed_iterator> >(L, 1)); + if (i.src.end() == i.it) + return sol::stack::push(L, sol::lua_nil); + int result; + if constexpr (Indexed) + result = sol::stack::push(L, i.ix + 1); + else + result = T::push_key(L, i.it, i.ix); + if constexpr (std::is_reference_v<decltype(T::unwrap(i.it))>) + result += sol::stack::push_reference(L, std::ref(T::unwrap(i.it))); + else + result += sol::stack::push_reference(L, T::unwrap(i.it)); + ++i; + return result; + } + + template <bool Indexed> + static int start_pairs(lua_State *L) + { + T &self(immutable_sequence_helper::get_self(L)); + sol::stack::push(L, next_pairs<Indexed>); + sol::stack::push<sol::user<typename immutable_sequence_helper::indexed_iterator> >(L, self.items(), self.items().begin()); + sol::stack::push(L, sol::lua_nil); + return 3; + } + +public: + static int at(lua_State *L) + { + T &self(immutable_sequence_helper::get_self(L)); + std::ptrdiff_t const index(sol::stack::unqualified_get<std::ptrdiff_t>(L, 2)); + if ((0 >= index) || (self.items().size() < index)) + { + return sol::stack::push(L, sol::lua_nil); + } + else + { + if constexpr (std::is_reference_v<decltype(T::unwrap(std::next(self.items().begin(), index - 1)))>) + return sol::stack::push_reference(L, std::ref(T::unwrap(std::next(self.items().begin(), index - 1)))); + else + return sol::stack::push_reference(L, T::unwrap(std::next(self.items().begin(), index - 1))); + } + } + + static int index_of(lua_State *L) + { + T &self(immutable_sequence_helper::get_self(L)); + auto it(self.items().begin()); + std::ptrdiff_t ix(0); + auto const &item(sol::stack::unqualified_get<decltype(T::unwrap(it))>(L, 2)); + while ((self.items().end() != it) && (&item != &T::unwrap(it))) + { + ++it; + ++ix; + } + if (self.items().end() == it) + return sol::stack::push(L, sol::lua_nil); + else + return sol::stack::push(L, ix + 1); + } + + static int size(lua_State *L) + { + T &self(immutable_sequence_helper::get_self(L)); + return sol::stack::push(L, self.items().size()); + } + + static int empty(lua_State *L) + { + T &self(immutable_sequence_helper::get_self(L)); + return sol::stack::push(L, self.items().empty()); + } + + static int next(lua_State *L) { return sol::stack::push(L, next_pairs<false>); } + static int pairs(lua_State *L) { return start_pairs<false>(L); } + static int ipairs(lua_State *L) { return start_pairs<true>(L); } +}; + + + +struct lua_engine::addr_space +{ + addr_space(address_space &s, device_memory_interface &d) : space(s), dev(d) { } + + template <typename T> T mem_read(offs_t address); + template <typename T> void mem_write(offs_t address, T val); + template <typename T> T log_mem_read(offs_t address); + template <typename T> void log_mem_write(offs_t address, T val); + template <typename T> T direct_mem_read(offs_t address); + template <typename T> void direct_mem_write(offs_t address, T val); + + address_space &space; + device_memory_interface &dev; +}; + + +template <typename T, size_t Size> +class lua_engine::enum_parser +{ +public: + constexpr enum_parser(std::initializer_list<std::pair<std::string_view, T> > values) + { + if (values.size() != Size) + throw false && "size template argument incorrectly specified"; + std::copy(values.begin(), values.end(), m_map.begin()); + } + + T operator()(std::string_view text) const + { + auto iter = std::find_if( + m_map.begin() + 1, + m_map.end(), + [&text] (const auto &x) { return text == x.first; }); + if (iter == m_map.end()) + iter = m_map.begin(); + return iter->second; + } + +private: + std::array<std::pair<std::string_view, T>, Size> m_map; +}; + + +//------------------------------------------------- +// make_notifier_adder - make a function for +// subscribing to a notifier +//------------------------------------------------- + +template <typename... T> +auto lua_engine::make_notifier_adder(util::notifier<T...> ¬ifier, const char *desc) +{ + return + [this, ¬ifier, desc] (sol::protected_function cb) + { + return notifier.subscribe( + delegate<void (T...)>( + [this, desc, cbfunc = sol::protected_function(m_lua_state, cb)] (T... args) + { + auto status(invoke(cbfunc, std::forward<T>(args)...)); + if (!status.valid()) + { + auto err(status.template get<sol::error>()); + osd_printf_error("[LUA ERROR] error in %s callback: %s\n", desc, err.what()); + } + })); + }; +} + + +//------------------------------------------------- +// make_simple_callback_setter - make a callback +// setter for simple cases +//------------------------------------------------- + +template <typename T, typename D, typename R, typename... A> +auto lua_engine::make_simple_callback_setter(void (T::*setter)(delegate<R (A...)> &&), D &&dflt, const char *name, const char *desc) +{ + return + [this, setter, dflt, name, desc] (T &self, sol::object cb) + { + if (cb == sol::lua_nil) + { + (self.*setter)(delegate<R (A...)>()); + } + else if (cb.is<sol::protected_function>()) + { + (self.*setter)(delegate<R (A...)>( + [dflt, desc, cbfunc = sol::protected_function(m_lua_state, cb)] (A... args) -> R + { + auto status(invoke_direct(cbfunc, std::forward<A>(args)...)); + if (status.valid()) + { + if constexpr (std::is_same_v<R, void>) + { + std::ignore = dflt; + } + else + { + auto result(status.template get<std::optional<R> >()); + if (result) + { + return *result; + } + else + { + osd_printf_error("[LUA ERROR] invalid return from %s callback\n", desc); + return dflt(); + } + } + } + else + { + auto err(status.template get<sol::error>()); + osd_printf_error("[LUA ERROR] error in %s callback: %s\n", desc, err.what()); + if constexpr (!std::is_same_v<R, void>) + return dflt(); + } + })); + } + else + { + osd_printf_error("[LUA ERROR] must call %s with function or nil\n", name); + } + }; +} + +#endif // MAME_FRONTEND_MAME_LUAENGINE_IPP diff --git a/src/frontend/mame/luaengine_debug.cpp b/src/frontend/mame/luaengine_debug.cpp new file mode 100644 index 00000000000..8cdf29246c0 --- /dev/null +++ b/src/frontend/mame/luaengine_debug.cpp @@ -0,0 +1,542 @@ +// license:BSD-3-Clause +// copyright-holders:Miodrag Milanovic,Luca Bruno +/*************************************************************************** + + luaengine.cpp + + Controls execution of the core MAME system. + +***************************************************************************/ + +#include "emu.h" +#include "luaengine.ipp" + +#include "debug/debugcon.h" +#include "debug/debugcpu.h" +#include "debug/debugvw.h" +#include "debug/express.h" +#include "debug/points.h" +#include "debug/textbuf.h" +#include "debugger.h" + + +namespace { + +struct wrap_textbuf +{ + wrap_textbuf(text_buffer const &buf) : textbuf(buf) { } + + std::reference_wrapper<const text_buffer> textbuf; +}; + + +template <bool Enable> +sol::object do_breakpoint_enable(device_debug &dev, sol::this_state s, sol::object index) +{ + if (index == sol::lua_nil) + { + dev.breakpoint_enable_all(Enable); + dev.device().machine().debug_view().update_all(DVT_DISASSEMBLY); + dev.device().machine().debug_view().update_all(DVT_BREAK_POINTS); + return sol::lua_nil; + } + else if (index.is<int>()) + { + bool result(dev.breakpoint_enable(index.as<int>(), Enable)); + if (result) + { + dev.device().machine().debug_view().update_all(DVT_DISASSEMBLY); + dev.device().machine().debug_view().update_all(DVT_BREAK_POINTS); + } + return sol::make_object(s, result); + } + else + { + osd_printf_error("[LUA ERROR] must call bpenable with integer or nil\n"); + return sol::lua_nil; + } +} + + +template <bool Enable> +sol::object do_watchpoint_enable(device_debug &dev, sol::this_state s, sol::object index) +{ + if (index == sol::lua_nil) + { + dev.watchpoint_enable_all(Enable); + dev.device().machine().debug_view().update_all(DVT_WATCH_POINTS); + return sol::lua_nil; + } + else if (index.is<int>()) + { + bool result(dev.watchpoint_enable(index.as<int>(), Enable)); + if (result) + dev.device().machine().debug_view().update_all(DVT_WATCH_POINTS); + return sol::make_object(s, result); + } + else + { + osd_printf_error("[LUA ERROR] must call wpenable with integer or nil"); + return sol::lua_nil; + } +} + +} // anonymous namespace + + +class lua_engine::symbol_table_wrapper +{ +public: + symbol_table_wrapper(symbol_table_wrapper const &) = delete; + + symbol_table_wrapper(lua_engine &host, running_machine &machine, std::shared_ptr<symbol_table_wrapper> const &parent, device_t *device) + : m_host(host) + , m_table(machine, parent ? &parent->table() : nullptr, device) + , m_parent(parent) + { + } + + symbol_table &table() { return m_table; } + symbol_table const &table() const { return m_table; } + std::shared_ptr<symbol_table_wrapper> const &parent() { return m_parent; } + + symbol_entry &add(char const *name) { return m_table.add(name, symbol_table::READ_WRITE); } + symbol_entry &add(char const *name, u64 value) { return m_table.add(name, value); } + symbol_entry &add(char const *name, sol::protected_function getter, std::optional<sol::protected_function> setter, std::optional<char const *> format) + { + symbol_table::setter_func setfun; + if (setter) + { + setfun = + [this, cbfunc = sol::protected_function(m_host.m_lua_state, *setter)] (u64 value) + { + auto status = m_host.invoke(cbfunc, value); + if (!status.valid()) + { + sol::error err = status; + osd_printf_error("[LUA EROR] in symbol value setter callback: %s\n", err.what()); + } + }; + } + return m_table.add( + name, + [this, cbfunc = sol::protected_function(m_host.m_lua_state, getter)] () -> u64 + { + auto status = m_host.invoke(cbfunc); + if (status.valid()) + { + auto result = status.get<std::optional<u64> >(); + if (result) + return *result; + + osd_printf_error("[LUA EROR] invalid return from symbol value getter callback\n"); + } + else + { + sol::error err = status; + osd_printf_error("[LUA EROR] in symbol value getter callback: %s\n", err.what()); + } + return 0; + }, + std::move(setfun), + (format && *format) ? *format : ""); + } + symbol_entry *find(char const *name) const { return m_table.find(name); } + symbol_entry *find_deep(char const *name) { return m_table.find_deep(name); } + + u64 value(const char *symbol) { return m_table.value(symbol); } + void set_value(const char *symbol, u64 value) { m_table.set_value(symbol, value); } + + u64 read_memory(addr_space &space, offs_t address, int size, bool translate) { return m_table.read_memory(space.space, address, size, translate); } + void write_memory(addr_space &space, offs_t address, u64 data, int size, bool translate) { m_table.write_memory(space.space, address, data, size, translate); } + +private: + lua_engine &m_host; + symbol_table m_table; + std::shared_ptr<symbol_table_wrapper> const m_parent; +}; + + +class lua_engine::expression_wrapper +{ +public: + expression_wrapper(expression_wrapper const &) = delete; + expression_wrapper &operator=(expression_wrapper const &) = delete; + + expression_wrapper(std::shared_ptr<symbol_table_wrapper> const &symtable) + : m_expression(symtable->table()) + , m_symbols(symtable) + { + } + + void set_default_base(int base) { m_expression.set_default_base(base); } + + void parse(sol::this_state s, char const *string) + { + try + { + m_expression.parse(string); + } + catch (expression_error const &err) + { + sol::stack::push(s, err); + lua_error(s); + } + } + + u64 execute(sol::this_state s) + { + try + { + return m_expression.execute(); + } + catch (expression_error const &err) + { + sol::stack::push(s, err); + lua_error(s); + return 0; // unreachable - lua_error doesn't return + } + } + + bool is_empty() const { return m_expression.is_empty(); } + char const *original_string() const { return m_expression.original_string(); } + std::shared_ptr<symbol_table_wrapper> const &symbols() { return m_symbols; } + + void set_symbols(std::shared_ptr<symbol_table_wrapper> const &symtable) + { + m_expression.set_symbols(symtable->table()); + m_symbols = symtable; + } + +private: + parsed_expression m_expression; + std::shared_ptr<symbol_table_wrapper> m_symbols; +}; + + +void lua_engine::initialize_debug(sol::table &emu) +{ + + static const enum_parser<read_or_write, 4> s_read_or_write_parser = + { + { "r", read_or_write::READ }, + { "w", read_or_write::WRITE }, + { "rw", read_or_write::READWRITE }, + { "wr", read_or_write::READWRITE } + }; + + static const enum_parser<expression_space, 15> s_expression_space_parser = + { + { "p", EXPSPACE_PROGRAM_LOGICAL }, { "lp", EXPSPACE_PROGRAM_LOGICAL }, { "pp", EXPSPACE_PROGRAM_PHYSICAL }, + { "d", EXPSPACE_DATA_LOGICAL }, { "ld", EXPSPACE_DATA_LOGICAL }, { "pd", EXPSPACE_DATA_PHYSICAL }, + { "i", EXPSPACE_IO_LOGICAL }, { "li", EXPSPACE_IO_LOGICAL }, { "pi", EXPSPACE_IO_PHYSICAL }, + { "3", EXPSPACE_OPCODE_LOGICAL }, { "l3", EXPSPACE_OPCODE_LOGICAL }, { "p3", EXPSPACE_OPCODE_PHYSICAL }, + { "r", EXPSPACE_PRGDIRECT }, + { "o", EXPSPACE_OPDIRECT }, + { "m", EXPSPACE_REGION } + }; + + auto expression_error_type = emu.new_usertype<expression_error>( + "expression_error", + sol::no_constructor); + expression_error_type["code"] = sol::property( + [] (expression_error const &err) { return int(err.code()); }); + expression_error_type["offset"] = sol::property(&expression_error::offset); + expression_error_type[sol::meta_function::to_string] = &expression_error::code_string; + + auto symbol_table_type = emu.new_usertype<symbol_table_wrapper>( + "symbol_table", + sol::call_constructor, sol::factories( + [this] (running_machine &machine) + { return std::make_shared<symbol_table_wrapper>(*this, machine, nullptr, nullptr); }, + [this] (std::shared_ptr<symbol_table_wrapper> const &parent, device_t *device) + { return std::make_shared<symbol_table_wrapper>(*this, parent->table().machine(), parent, device); }, + [this] (std::shared_ptr<symbol_table_wrapper> const &parent) + { return std::make_shared<symbol_table_wrapper>(*this, parent->table().machine(), parent, nullptr); }, + [this] (device_t &device) + { return std::make_shared<symbol_table_wrapper>(*this, device.machine(), nullptr, &device); })); + symbol_table_type.set_function("set_memory_modified_func", + [this] (symbol_table_wrapper &st, sol::object cb) + { + if (cb == sol::lua_nil) + st.table().set_memory_modified_func(nullptr); + else if (cb.is<sol::protected_function>()) + st.table().set_memory_modified_func([this, cbfunc = sol::protected_function(m_lua_state, cb)] () { invoke(cbfunc); }); + else + osd_printf_error("[LUA ERROR] must call set_memory_modified_func with function or nil\n"); + }); + symbol_table_type["add"] = sol::overload( + static_cast<symbol_entry &(symbol_table_wrapper::*)(char const *)>(&symbol_table_wrapper::add), + static_cast<symbol_entry &(symbol_table_wrapper::*)(char const *, u64)>(&symbol_table_wrapper::add), + static_cast<symbol_entry &(symbol_table_wrapper::*)(char const *, sol::protected_function, std::optional<sol::protected_function>, std::optional<char const *>)>(&symbol_table_wrapper::add), + [] (symbol_table_wrapper &st, char const *name, sol::protected_function getter, sol::lua_nil_t, char const *format) -> symbol_entry & + { + return st.add(name, getter, std::nullopt, format); + }, + [] (symbol_table_wrapper &st, char const *name, sol::protected_function getter, std::optional<sol::protected_function> setter) -> symbol_entry & + { + return st.add(name, getter, setter, nullptr); + }, + [] (symbol_table_wrapper &st, char const *name, sol::protected_function getter, char const *format) -> symbol_entry & + { + return st.add(name, getter, std::nullopt, format); + }, + [] (symbol_table_wrapper &st, char const *name, sol::protected_function getter) -> symbol_entry & + { + return st.add(name, getter, std::nullopt, nullptr); + }, + [this] (symbol_table_wrapper &st, char const *name, int minparams, int maxparams, sol::protected_function execute) -> symbol_entry & + { + return st.table().add( + name, + minparams, + maxparams, + [this, cb = sol::protected_function(m_lua_state, execute)] (int numparams, u64 const *paramlist) -> u64 + { + // TODO: C++20 will make this obsolete + class helper + { + private: + u64 const *b, *e; + public: + helper(int n, u64 const *p) : b(p), e(p + n) { } + auto begin() const { return b; } + auto end() const { return e; } + }; + + auto status(invoke(cb, sol::as_args(helper(numparams, paramlist)))); + if (status.valid()) + { + auto result = status.get<std::optional<u64> >(); + if (result) + return *result; + + osd_printf_error("[LUA EROR] invalid return from symbol execute callback\n"); + } + else + { + sol::error err = status; + osd_printf_error("[LUA EROR] in symbol execute callback: %s\n", err.what()); + } + return 0; + }); + }); + symbol_table_type.set_function("find", &symbol_table_wrapper::find); + symbol_table_type.set_function("find_deep", &symbol_table_wrapper::find_deep); + symbol_table_type.set_function("value", &symbol_table_wrapper::value); + symbol_table_type.set_function("set_value", &symbol_table_wrapper::set_value); + symbol_table_type.set_function("memory_value", + [] (symbol_table_wrapper &st, char const *name, char const *space, u32 offset, int size, bool disable_se) + { + expression_space const es = s_expression_space_parser(space); + return st.table().memory_value(name, es, offset, size, disable_se); + }); + symbol_table_type.set_function("set_memory_value", + [] (symbol_table_wrapper &st, char const *name, char const *space, u32 offset, int size, u64 value, bool disable_se) + { + expression_space const es = s_expression_space_parser(space); + st.table().set_memory_value(name, es, offset, size, value, disable_se); + }); + symbol_table_type.set_function("read_memory", &symbol_table_wrapper::read_memory); + symbol_table_type.set_function("write_memory", &symbol_table_wrapper::write_memory); + symbol_table_type["entries"] = sol::property([] (symbol_table_wrapper const &st) { return standard_tag_object_ptr_map<symbol_entry>(st.table().entries()); }); + symbol_table_type["parent"] = sol::property(&symbol_table_wrapper::parent); + + + auto parsed_expression_type = emu.new_usertype<expression_wrapper>( + "parsed_expression", + sol::call_constructor, sol::initializers( + [] (expression_wrapper &wrapper, std::shared_ptr<symbol_table_wrapper> const &symbols) + { + new (&wrapper) expression_wrapper(symbols); + }, + [] (expression_wrapper &wrapper, sol::this_state s, std::shared_ptr<symbol_table_wrapper> const &symbols, char const *expression, int base) + { + new (&wrapper) expression_wrapper(symbols); + wrapper.set_default_base(base); + wrapper.parse(s, expression); + }, + [] (expression_wrapper &wrapper, sol::this_state s, std::shared_ptr<symbol_table_wrapper> const &symbols, char const *expression) + { + new (&wrapper) expression_wrapper(symbols); + wrapper.parse(s, expression); + })); + parsed_expression_type.set_function("set_default_base", &expression_wrapper::set_default_base); + parsed_expression_type.set_function("parse", &expression_wrapper::parse); + parsed_expression_type.set_function("execute", &expression_wrapper::execute); + parsed_expression_type["is_empty"] = sol::property(&expression_wrapper::is_empty); + parsed_expression_type["original_string"] = sol::property(&expression_wrapper::original_string); + parsed_expression_type["symbols"] = sol::property(&expression_wrapper::symbols, &expression_wrapper::set_symbols); + + + auto symbol_entry_type = sol().registry().new_usertype<symbol_entry>("symbol_entry", sol::no_constructor); + symbol_entry_type["name"] = sol::property(&symbol_entry::name); + symbol_entry_type["format"] = sol::property(&symbol_entry::format); + symbol_entry_type["is_function"] = sol::property(&symbol_entry::is_function); + symbol_entry_type["is_lval"] = sol::property(&symbol_entry::is_lval); + symbol_entry_type["value"] = sol::property(&symbol_entry::value, &symbol_entry::set_value); + + + auto debugger_type = sol().registry().new_usertype<debugger_manager>("debugger", sol::no_constructor); + debugger_type.set_function("command", [] (debugger_manager &debug, std::string const &cmd) { debug.console().execute_command(cmd, false); }); + debugger_type["consolelog"] = sol::property([] (debugger_manager &debug) { return wrap_textbuf(debug.console().get_console_textbuf()); }); + debugger_type["errorlog"] = sol::property([](debugger_manager &debug) { return wrap_textbuf(debug.console().get_errorlog_textbuf()); }); + debugger_type["visible_cpu"] = sol::property( + [](debugger_manager &debug) { return debug.console().get_visible_cpu(); }, + [](debugger_manager &debug, device_t &dev) { debug.console().set_visible_cpu(&dev); }); + debugger_type["execution_state"] = sol::property( + [] (debugger_manager &debug) { return debug.cpu().is_stopped() ? "stop" : "run"; }, + [] (debugger_manager &debug, std::string const &state) + { + if (state == "stop") + debug.cpu().set_execution_stopped(); + else + debug.cpu().set_execution_running(); + }); + + +/* wrap_textbuf library (requires debugger to be active) + * + * manager:machine():debugger().consolelog + * manager:machine():debugger().errorlog + * + * log[index] - get log entry + * #log - entry count + */ + + sol().registry().new_usertype<wrap_textbuf>("text_buffer", "new", sol::no_constructor, + "__metatable", [](){}, + "__newindex", [](){}, + "__index", [](wrap_textbuf &buf, int index) { return text_buffer_get_seqnum_line(buf.textbuf, index - 1); }, + "__len", [](wrap_textbuf &buf) { return text_buffer_num_lines(buf.textbuf) + text_buffer_line_index_to_seqnum(buf.textbuf, 0) - 1; }); + + + auto device_debug_type = sol().registry().new_usertype<device_debug>("device_debug", sol::no_constructor); + device_debug_type.set_function("step", + [] (device_debug &dev, sol::object num) + { + int steps = 1; + if (num.is<int>()) + steps = num.as<int>(); + dev.single_step(steps); + }); + device_debug_type.set_function("go", &device_debug::go); + device_debug_type.set_function("bpset", + [] (device_debug &dev, offs_t address, char const *cond, char const *act) + { + int result(dev.breakpoint_set(address, cond, act)); + dev.device().machine().debug_view().update_all(DVT_DISASSEMBLY); + dev.device().machine().debug_view().update_all(DVT_BREAK_POINTS); + return result; + }); + device_debug_type["bpclear"] = sol::overload( + [] (device_debug &dev, int index) + { + bool result(dev.breakpoint_clear(index)); + if (result) + { + dev.device().machine().debug_view().update_all(DVT_DISASSEMBLY); + dev.device().machine().debug_view().update_all(DVT_BREAK_POINTS); + } + return result; + }, + [] (device_debug &dev) + { + dev.breakpoint_clear_all(); + dev.device().machine().debug_view().update_all(DVT_DISASSEMBLY); + dev.device().machine().debug_view().update_all(DVT_BREAK_POINTS); + }); + device_debug_type.set_function("bpenable", &do_breakpoint_enable<true>); + device_debug_type.set_function("bpdisable", &do_breakpoint_enable<false>); + device_debug_type.set_function("bplist", + [this] (device_debug &dev) + { + sol::table table = sol().create_table(); + for (auto const &bpp : dev.breakpoint_list()) + table[bpp.second->index()] = sol::make_reference(sol(), bpp.second.get()); + return table; + }); + device_debug_type.set_function("wpset", + [] (device_debug &dev, addr_space &sp, std::string const &type, offs_t addr, offs_t len, char const *cond, char const *act) + { + read_or_write const wptype = s_read_or_write_parser(type); + int result(dev.watchpoint_set(sp.space, wptype, addr, len, cond, act)); + dev.device().machine().debug_view().update_all(DVT_WATCH_POINTS); + return result; + }); + device_debug_type["wpclear"] = sol::overload( + [] (device_debug &dev, int index) + { + bool result(dev.watchpoint_clear(index)); + if (result) + dev.device().machine().debug_view().update_all(DVT_WATCH_POINTS); + return result; + }, + [] (device_debug &dev) + { + dev.watchpoint_clear_all(); + dev.device().machine().debug_view().update_all(DVT_WATCH_POINTS); + }); + device_debug_type.set_function("wpenable", &do_watchpoint_enable<true>); + device_debug_type.set_function("wpdisable", &do_watchpoint_enable<false>); + device_debug_type.set_function("wplist", + [this] (device_debug &dev, addr_space &sp) + { + sol::table table = sol().create_table(); + for (auto &wpp : dev.watchpoint_vector(sp.space.spacenum())) + table[wpp->index()] = sol::make_reference(sol(), wpp.get()); + return table; + }); + + + auto breakpoint_type = sol().registry().new_usertype<debug_breakpoint>("breakpoint", sol::no_constructor); + breakpoint_type["index"] = sol::property(&debug_breakpoint::index); + breakpoint_type["enabled"] = sol::property( + &debug_breakpoint::enabled, + [] (debug_breakpoint &bp, bool val) + { + if (bp.enabled() != val) + { + bp.setEnabled(val); + bp.debugInterface()->device().machine().debug_view().update_all(DVT_DISASSEMBLY); + bp.debugInterface()->device().machine().debug_view().update_all(DVT_BREAK_POINTS); + } + }); + breakpoint_type["address"] = sol::property(&debug_breakpoint::address); + breakpoint_type["condition"] = sol::property(&debug_breakpoint::condition); + breakpoint_type["action"] = sol::property(&debug_breakpoint::action); + + + auto watchpoint_type = sol().registry().new_usertype<debug_watchpoint>("watchpoint", sol::no_constructor); + watchpoint_type["index"] = sol::property(&debug_watchpoint::index); + watchpoint_type["enabled"] = sol::property( + &debug_watchpoint::enabled, + [] (debug_watchpoint &wp, bool val) + { + if (wp.enabled() != val) + { + wp.setEnabled(val); + wp.debugInterface()->device().machine().debug_view().update_all(DVT_WATCH_POINTS); + } + }); + watchpoint_type["type"] = sol::property( + [] (debug_watchpoint &wp) -> char const * + { + switch (wp.type()) + { + case read_or_write::READ: + return "r"; + case read_or_write::WRITE: + return "w"; + case read_or_write::READWRITE: + return "rw"; + default: // huh? + return ""; + } + }); + watchpoint_type["address"] = sol::property(&debug_watchpoint::address); + watchpoint_type["length"] = sol::property(&debug_watchpoint::length); + watchpoint_type["condition"] = sol::property(&debug_watchpoint::condition); + watchpoint_type["action"] = sol::property(&debug_watchpoint::action); + +} diff --git a/src/frontend/mame/luaengine_input.cpp b/src/frontend/mame/luaengine_input.cpp new file mode 100644 index 00000000000..8ec97321cfb --- /dev/null +++ b/src/frontend/mame/luaengine_input.cpp @@ -0,0 +1,519 @@ +// license:BSD-3-Clause +// copyright-holders:Miodrag Milanovic,Luca Bruno +/*************************************************************************** + + luaengine_input.cpp + + Controls execution of the core MAME system. + +***************************************************************************/ + +#include "emu.h" +#include "luaengine.ipp" + +#include "iptseqpoll.h" + +#include "inputdev.h" +#include "natkeyboard.h" +#include "render.h" +#include "uiinput.h" + +#include <cstring> + + +namespace { + +struct natkbd_kbd_dev +{ + natkbd_kbd_dev(natural_keyboard &m, std::size_t i) : manager(m), index(i) { } + + natural_keyboard &manager; + std::size_t index; +}; + + +struct natkbd_kbd_list +{ + natkbd_kbd_list(natural_keyboard &m) : manager(m) { } + + natural_keyboard &manager; +}; + +} // anonymous namespace + + +namespace sol { + +template <> struct is_container<natkbd_kbd_list> : std::true_type { }; + + +template <> +struct usertype_container<natkbd_kbd_list> : lua_engine::immutable_container_helper<natkbd_kbd_list> +{ +private: + template <bool Indexed> + static int next_pairs(lua_State *L) + { + natkbd_kbd_dev &i(stack::unqualified_get<user<natkbd_kbd_dev> >(L, 1)); + if (i.manager.keyboard_count() <= i.index) + return stack::push(L, lua_nil); + int result; + if constexpr (Indexed) + result = stack::push(L, i.index + 1); + else + result = stack::push(L, i.manager.keyboard_device(i.index).tag()); + result += stack::push(L, i); + ++i.index; + return result; + } + + template <bool Indexed> + static int start_pairs(lua_State *L) + { + natkbd_kbd_list &self(get_self(L)); + stack::push(L, next_pairs<Indexed>); + stack::push<user<natkbd_kbd_dev> >(L, self.manager, 0); + stack::push(L, lua_nil); + return 3; + } + +public: + static int at(lua_State *L) + { + natkbd_kbd_list &self(get_self(L)); + std::ptrdiff_t const index(stack::unqualified_get<std::ptrdiff_t>(L, 2)); + if ((0 < index) && (self.manager.keyboard_count() >= index)) + return stack::push(L, natkbd_kbd_dev(self.manager, index - 1)); + else + return stack::push(L, lua_nil); + } + + static int get(lua_State *L) + { + natkbd_kbd_list &self(get_self(L)); + char const *const tag(stack::unqualified_get<char const *>(L)); + for (std::size_t i = 0; self.manager.keyboard_count() > i; ++i) + { + if (!std::strcmp(self.manager.keyboard_device(i).tag(), tag)) + return stack::push(L, natkbd_kbd_dev(self.manager, i)); + } + return stack::push(L, lua_nil); + } + + static int index_get(lua_State *L) + { + return get(L); + } + + static int size(lua_State *L) + { + natkbd_kbd_list &self(get_self(L)); + return stack::push(L, self.manager.keyboard_count()); + } + + static int empty(lua_State *L) + { + natkbd_kbd_list &self(get_self(L)); + return stack::push(L, !self.manager.keyboard_count()); + } + + static int next(lua_State *L) { return stack::push(L, next_pairs<false>); } + static int pairs(lua_State *L) { return start_pairs<false>(L); } + static int ipairs(lua_State *L) { return start_pairs<true>(L); } +}; + +} // namespace sol + + +//------------------------------------------------- +// initialize_input - register input user types +//------------------------------------------------- + +void lua_engine::initialize_input(sol::table &emu) +{ + + static const enum_parser<input_seq_type, 3> s_seq_type_parser = + { + { "standard", SEQ_TYPE_STANDARD }, + { "increment", SEQ_TYPE_INCREMENT }, + { "decrement", SEQ_TYPE_DECREMENT }, + }; + + + auto ioport_manager_type = sol().registry().new_usertype<ioport_manager>("ioport", sol::no_constructor); + ioport_manager_type["count_players"] = &ioport_manager::count_players; + ioport_manager_type["type_pressed"] = sol::overload( + &ioport_manager::type_pressed, + [] (ioport_manager &im, ioport_type type) { return im.type_pressed(type, 0); }, + [] (ioport_manager &im, input_type_entry const &type) { return im.type_pressed(type.type(), type.player()); }); + ioport_manager_type["type_name"] = sol::overload( + &ioport_manager::type_name, + [] (ioport_manager &im, ioport_type type) { return im.type_name(type, 0); }); + ioport_manager_type["type_group"] = sol::overload( + &ioport_manager::type_group, + [] (ioport_manager &im, ioport_type type) { return im.type_group(type, 0); }); + ioport_manager_type["type_seq"] = sol::overload( + [] (ioport_manager &im, ioport_type type, std::optional<int> player, std::optional<char const *> seq_type_string) + { + if (!player) + player = 0; + input_seq_type seq_type = seq_type_string ? s_seq_type_parser(*seq_type_string) : SEQ_TYPE_STANDARD; + return im.type_seq(type, *player, seq_type); + }, + [] (ioport_manager &im, ioport_type type, std::optional<int> player) + { + if (!player) + player = 0; + return im.type_seq(type, *player); + }, + [] (ioport_manager &im, input_type_entry const &type, std::optional<char const *> seq_type_string) + { + input_seq_type seq_type = seq_type_string ? s_seq_type_parser(*seq_type_string) : SEQ_TYPE_STANDARD; + return im.type_seq(type.type(), type.player(), seq_type); + }, + [] (ioport_manager &im, input_type_entry const &type) + { + return im.type_seq(type.type(), type.player()); + }); + ioport_manager_type["set_type_seq"] = sol::overload( + [] (ioport_manager &im, ioport_type type, std::optional<int> player, std::optional<char const *> seq_type_string, input_seq const &seq) + { + if (!player) + player = 0; + input_seq_type seq_type = seq_type_string ? s_seq_type_parser(*seq_type_string) : SEQ_TYPE_STANDARD; + im.set_type_seq(type, *player, seq_type, seq); + }, + [] (ioport_manager &im, input_type_entry const &type, std::optional<char const *> seq_type_string, input_seq const &seq) + { + input_seq_type seq_type = seq_type_string ? s_seq_type_parser(*seq_type_string) : SEQ_TYPE_STANDARD; + im.set_type_seq(type.type(), type.player(), seq_type, seq); + }); + ioport_manager_type["token_to_input_type"] = + [] (ioport_manager &im, std::string const &string) + { + int player; + ioport_type const type = im.token_to_input_type(string.c_str(), player); + return std::make_tuple(type, player); + }; + ioport_manager_type["input_type_to_token"] = sol::overload( + &ioport_manager::input_type_to_token, + [] (ioport_manager &im, ioport_type type) { return im.input_type_to_token(type, 0); }); + ioport_manager_type["types"] = sol::property(&ioport_manager::types); + ioport_manager_type["ports"] = sol::property([] (ioport_manager &im) { return tag_object_ptr_map<ioport_list>(im.ports()); }); + + + auto natkeyboard_type = sol().registry().new_usertype<natural_keyboard>("natkeyboard", sol::no_constructor); + natkeyboard_type["post"] = [] (natural_keyboard &nat, std::string const &text) { nat.post_utf8(text); }; + natkeyboard_type["post_coded"] = [] (natural_keyboard &nat, std::string const &text) { nat.post_coded(text); }; + natkeyboard_type["paste"] = &natural_keyboard::paste; + natkeyboard_type["dump"] = static_cast<std::string (natural_keyboard::*)() const>(&natural_keyboard::dump); + natkeyboard_type["empty"] = sol::property(&natural_keyboard::empty); + natkeyboard_type["can_post"] = sol::property(&natural_keyboard::can_post); + natkeyboard_type["is_posting"] = sol::property(&natural_keyboard::is_posting); + natkeyboard_type["in_use"] = sol::property(&natural_keyboard::in_use, &natural_keyboard::set_in_use); + natkeyboard_type["keyboards"] = sol::property([] (natural_keyboard &nat) { return natkbd_kbd_list(nat); }); + + + auto natkbddev_type = sol().registry().new_usertype<natkbd_kbd_dev>("natkeyboard_device", sol::no_constructor); + natkbddev_type["device"] = sol::property([] (natkbd_kbd_dev const &kbd) -> device_t & { return kbd.manager.keyboard_device(kbd.index); }); + natkbddev_type["tag"] = sol::property([] (natkbd_kbd_dev const &kbd) { return kbd.manager.keyboard_device(kbd.index).tag(); }); + natkbddev_type["basetag"] = sol::property([] (natkbd_kbd_dev const &kbd) { return kbd.manager.keyboard_device(kbd.index).basetag(); }); + natkbddev_type["name"] = sol::property([] (natkbd_kbd_dev const &kbd) { return kbd.manager.keyboard_device(kbd.index).name(); }); + natkbddev_type["shortname"] = sol::property([] (natkbd_kbd_dev const &kbd) { return kbd.manager.keyboard_device(kbd.index).shortname(); }); + natkbddev_type["is_keypad"] = sol::property([] (natkbd_kbd_dev const &kbd) { return kbd.manager.keyboard_is_keypad(kbd.index); }); + natkbddev_type["enabled"] = sol::property( + [] (natkbd_kbd_dev const &kbd) { return kbd.manager.keyboard_enabled(kbd.index); }, + [] (natkbd_kbd_dev &kbd, bool enable) + { + if (enable) + kbd.manager.enable_keyboard(kbd.index); + else + kbd.manager.disable_keyboard(kbd.index); + }); + + + auto ioport_port_type = sol().registry().new_usertype<ioport_port>("ioport_port", sol::no_constructor); + ioport_port_type["read"] = &ioport_port::read; + ioport_port_type["write"] = &ioport_port::write; + ioport_port_type["field"] = &ioport_port::field; + ioport_port_type["device"] = sol::property(&ioport_port::device); + ioport_port_type["tag"] = sol::property(&ioport_port::tag); + ioport_port_type["active"] = sol::property(&ioport_port::active); + ioport_port_type["live"] = sol::property(&ioport_port::live); + ioport_port_type["fields"] = sol::property( + [this] (ioport_port &p) + { + sol::table f_table = sol().create_table(); + // parse twice for custom and default names, default has priority + for (ioport_field &field : p.fields()) + { + if (field.type_class() != INPUT_CLASS_INTERNAL) + f_table[field.name()] = &field; + } + for (ioport_field &field : p.fields()) + { + if (field.type_class() != INPUT_CLASS_INTERNAL) + { + if (field.specific_name()) + f_table[field.specific_name()] = &field; + else + f_table[field.manager().type_name(field.type(), field.player())] = &field; + } + } + return f_table; + }); + + + auto ioport_field_type = sol().registry().new_usertype<ioport_field>("ioport_field", sol::no_constructor); + ioport_field_type["set_value"] = &ioport_field::set_value; + ioport_field_type["clear_value"] = &ioport_field::clear_value; + ioport_field_type["set_input_seq"] = + [] (ioport_field &f, std::string const &seq_type_string, const input_seq &seq) + { + input_seq_type seq_type = s_seq_type_parser(seq_type_string); + ioport_field::user_settings settings; + f.get_user_settings(settings); + settings.seq[seq_type] = seq; + if (seq.is_default()) + settings.cfg[seq_type].clear(); + else if (!seq.length()) + settings.cfg[seq_type] = "NONE"; + else + settings.cfg[seq_type] = f.port().device().machine().input().seq_to_tokens(seq); + f.set_user_settings(settings); + }; + ioport_field_type["input_seq"] = + [] (ioport_field &f, std::string const &seq_type_string) + { + input_seq_type seq_type = s_seq_type_parser(seq_type_string); + return f.seq(seq_type); + }; + ioport_field_type["set_default_input_seq"] = + [] (ioport_field &f, std::string const &seq_type_string, input_seq const &seq) + { + input_seq_type seq_type = s_seq_type_parser(seq_type_string); + f.set_defseq(seq_type, seq); + }; + ioport_field_type["default_input_seq"] = + [] (ioport_field &f, const std::string &seq_type_string) + { + input_seq_type seq_type = s_seq_type_parser(seq_type_string); + return f.defseq(seq_type); + }; + ioport_field_type["keyboard_codes"] = + [this] (ioport_field &f, int which) + { + sol::table result = sol().create_table(); + int index = 1; + for (char32_t code : f.keyboard_codes(which)) + result[index++] = code; + return result; + }; + ioport_field_type["device"] = sol::property(&ioport_field::device); + ioport_field_type["port"] = sol::property(&ioport_field::port); + ioport_field_type["live"] = sol::property(&ioport_field::live); + ioport_field_type["type"] = sol::property(&ioport_field::type); + ioport_field_type["name"] = sol::property(&ioport_field::name); + ioport_field_type["default_name"] = sol::property( + [] (ioport_field const &f) + { + return f.specific_name() ? f.specific_name() : f.manager().type_name(f.type(), f.player()); + }); + ioport_field_type["player"] = sol::property(&ioport_field::player, &ioport_field::set_player); + ioport_field_type["mask"] = sol::property(&ioport_field::mask); + ioport_field_type["defvalue"] = sol::property(&ioport_field::defvalue); + ioport_field_type["minvalue"] = sol::property( + [] (ioport_field const &f) + { + return f.is_analog() ? std::make_optional(f.minval()) : std::nullopt; + }); + ioport_field_type["maxvalue"] = sol::property( + [] (ioport_field const &f) + { + return f.is_analog() ? std::make_optional(f.maxval()) : std::nullopt; + }); + ioport_field_type["sensitivity"] = sol::property( + [] (ioport_field const &f) + { + return f.is_analog() ? std::make_optional(f.sensitivity()) : std::nullopt; + }); + ioport_field_type["way"] = sol::property(&ioport_field::way); + ioport_field_type["type_class"] = sol::property( + [] (ioport_field const &f) + { + switch (f.type_class()) + { + case INPUT_CLASS_KEYBOARD: return "keyboard"; + case INPUT_CLASS_CONTROLLER: return "controller"; + case INPUT_CLASS_CONFIG: return "config"; + case INPUT_CLASS_DIPSWITCH: return "dipswitch"; + case INPUT_CLASS_MISC: return "misc"; + default: break; + } + throw false; + }); + ioport_field_type["is_analog"] = sol::property(&ioport_field::is_analog); + ioport_field_type["is_digital_joystick"] = sol::property(&ioport_field::is_digital_joystick); + ioport_field_type["enabled"] = sol::property(&ioport_field::enabled); + ioport_field_type["optional"] = sol::property(&ioport_field::optional); + ioport_field_type["cocktail"] = sol::property(&ioport_field::cocktail); + ioport_field_type["toggle"] = sol::property(&ioport_field::toggle); + ioport_field_type["rotated"] = sol::property(&ioport_field::rotated); + ioport_field_type["analog_reverse"] = sol::property(&ioport_field::analog_reverse); + ioport_field_type["analog_reset"] = sol::property(&ioport_field::analog_reset); + ioport_field_type["analog_wraps"] = sol::property(&ioport_field::analog_wraps); + ioport_field_type["analog_invert"] = sol::property(&ioport_field::analog_invert); + ioport_field_type["impulse"] = sol::property(&ioport_field::impulse); + ioport_field_type["crosshair_scale"] = sol::property(&ioport_field::crosshair_scale, &ioport_field::set_crosshair_scale); + ioport_field_type["crosshair_offset"] = sol::property(&ioport_field::crosshair_offset, &ioport_field::set_crosshair_offset); + ioport_field_type["user_value"] = sol::property( + [] (ioport_field const &f) + { + ioport_field::user_settings settings; + f.get_user_settings(settings); + return settings.value; + }, + [] (ioport_field &f, ioport_value val) + { + ioport_field::user_settings settings; + f.get_user_settings(settings); + settings.value = val; + f.set_user_settings(settings); + }); + ioport_field_type["settings"] = sol::property( + [this] (ioport_field &f) + { + sol::table result = sol().create_table(); + for (ioport_setting const &setting : f.settings()) + if (setting.enabled()) + result[setting.value()] = setting.name(); + return result; + }); + + + auto ioport_field_live_type = sol().registry().new_usertype<ioport_field_live>("ioport_field_live", sol::no_constructor); + ioport_field_live_type["name"] = &ioport_field_live::name; + + + auto input_type_entry_type = sol().registry().new_usertype<input_type_entry>("input_type_entry", sol::no_constructor); + input_type_entry_type["type"] = sol::property(&input_type_entry::type); + input_type_entry_type["group"] = sol::property(&input_type_entry::group); + input_type_entry_type["player"] = sol::property(&input_type_entry::player); + input_type_entry_type["token"] = sol::property(&input_type_entry::token); + input_type_entry_type["name"] = sol::property(&input_type_entry::name); + input_type_entry_type["is_analog"] = sol::property([] (input_type_entry const &type) { return ioport_manager::type_is_analog(type.type()); }); + + + auto input_type = sol().registry().new_usertype<input_manager>("input", sol::no_constructor); + input_type["code_value"] = &input_manager::code_value; + input_type["code_pressed"] = &input_manager::code_pressed; + input_type["code_pressed_once"] = &input_manager::code_pressed_once; + input_type["code_name"] = &input_manager::code_name; + input_type["code_to_token"] = &input_manager::code_to_token; + input_type["code_from_token"] = &input_manager::code_from_token; + input_type["seq_pressed"] = &input_manager::seq_pressed; + input_type["seq_clean"] = &input_manager::seq_clean; + input_type["seq_name"] = &input_manager::seq_name; + input_type["seq_to_tokens"] = &input_manager::seq_to_tokens; + input_type["seq_from_tokens"] = + [] (input_manager &input, std::string_view tokens) + { + input_seq seq; + input.seq_from_tokens(seq, tokens); + return seq; + }; + input_type["axis_code_poller"] = [] (input_manager &input) { return std::unique_ptr<input_code_poller>(new axis_code_poller(input)); }; + input_type["switch_code_poller"] = [] (input_manager &input) { return std::unique_ptr<input_code_poller>(new switch_code_poller(input)); }; + input_type["keyboard_code_poller"] = [] (input_manager &input) { return std::unique_ptr<input_code_poller>(new keyboard_code_poller(input)); }; + input_type["axis_sequence_poller"] = [] (input_manager &input) { return std::unique_ptr<input_sequence_poller>(new axis_sequence_poller(input)); }; + input_type["switch_sequence_poller"] = [] (input_manager &input) { return std::unique_ptr<input_sequence_poller>(new switch_sequence_poller(input)); }; + input_type["device_classes"] = sol::property( + [this] (input_manager &input) + { + sol::table result = sol().create_table(); + for (input_device_class devclass_id = DEVICE_CLASS_FIRST_VALID; devclass_id <= DEVICE_CLASS_LAST_VALID; devclass_id++) + { + input_class &devclass = input.device_class(devclass_id); + result[devclass.name()] = &devclass; + } + return result; + }); + + + auto codepoll_type = sol().registry().new_usertype<input_code_poller>("input_code_poller", sol::no_constructor); + codepoll_type["reset"] = &input_code_poller::reset; + codepoll_type["poll"] = &input_code_poller::poll; + + + auto seqpoll_type = sol().registry().new_usertype<input_sequence_poller>("input_seq_poller", sol::no_constructor); + seqpoll_type["start"] = sol::overload( + [] (input_sequence_poller &poller) { return poller.start(); }, + [] (input_sequence_poller &poller, input_seq const &seq) { return poller.start(seq); }); + seqpoll_type["poll"] = &input_sequence_poller::poll; + seqpoll_type["sequence"] = sol::property(&input_sequence_poller::sequence); + seqpoll_type["valid"] = sol::property(&input_sequence_poller::valid); + seqpoll_type["modified"] = sol::property(&input_sequence_poller::modified); + + + auto iptseq_type = emu.new_usertype<input_seq>( + "input_seq", + sol::call_constructor, sol::constructors<input_seq(), input_seq(input_seq const &)>()); + iptseq_type["reset"] = &input_seq::reset; + iptseq_type["set_default"] = &input_seq::set_default; + iptseq_type["empty"] = sol::property(&input_seq::empty); + iptseq_type["length"] = sol::property(&input_seq::length); + iptseq_type["is_valid"] = sol::property(&input_seq::is_valid); + iptseq_type["is_default"] = sol::property(&input_seq::is_default); + + + auto input_class_type = sol().registry().new_usertype<input_class>("input_class", sol::no_constructor); + input_class_type["name"] = sol::property(&input_class::name); + input_class_type["enabled"] = sol::property(&input_class::enabled); + input_class_type["multi"] = sol::property(&input_class::multi); + input_class_type["devices"] = sol::property( + [this] (input_class &devclass) + { + sol::table result = sol().create_table(); + int index = 1; + for (int devindex = 0; devindex <= devclass.maxindex(); devindex++) + { + input_device *const dev = devclass.device(devindex); + if (dev) + result[index++] = dev; + } + return result; + }); + + + auto input_device_type = sol().registry().new_usertype<input_device>("input_device", sol::no_constructor); + input_device_type["name"] = sol::property(&input_device::name); + input_device_type["id"] = sol::property(&input_device::id); + input_device_type["devindex"] = sol::property(&input_device::devindex); + input_device_type["items"] = sol::property( + [this] (input_device &dev) + { + sol::table result = sol().create_table(); + for (input_item_id id = ITEM_ID_FIRST_VALID; id <= dev.maxitem(); id++) + { + input_device_item *item = dev.item(id); + if (item) + result[id] = dev.item(id); + } + return result; + }); + + + auto input_device_item_type = sol().registry().new_usertype<input_device_item>("input_device_item", sol::no_constructor); + input_device_item_type["name"] = sol::property(&input_device_item::name); + input_device_item_type["code"] = sol::property(&input_device_item::code); + input_device_item_type["token"] = sol::property(&input_device_item::token); + input_device_item_type["current"] = sol::property(&input_device_item::current); + + + auto uiinput_type = sol().registry().new_usertype<ui_input_manager>("uiinput", sol::no_constructor); + uiinput_type.set_function("reset", &ui_input_manager::reset); + uiinput_type.set_function("pressed", &ui_input_manager::pressed); + uiinput_type.set_function("pressed_repeat", &ui_input_manager::pressed_repeat); + uiinput_type["presses_enabled"] = sol::property(&ui_input_manager::presses_enabled, &ui_input_manager::set_presses_enabled); + +} diff --git a/src/frontend/mame/luaengine_mem.cpp b/src/frontend/mame/luaengine_mem.cpp new file mode 100644 index 00000000000..ff2f406828b --- /dev/null +++ b/src/frontend/mame/luaengine_mem.cpp @@ -0,0 +1,802 @@ +// license:BSD-3-Clause +// copyright-holders:Miodrag Milanovic,Luca Bruno +/*************************************************************************** + + luaengine_input.cpp + + Controls execution of the core MAME system. + +***************************************************************************/ + +#include "emu.h" +#include "luaengine.ipp" + +#include <cstring> + + +namespace { + +//------------------------------------------------- +// region_read - templated region readers for <sign>,<size> +// -> manager:machine():memory().regions[":maincpu"]:read_i8(0xC000) +//------------------------------------------------- + +template <typename T> +T region_read(memory_region ®ion, offs_t address) +{ + T mem_content = 0; + const offs_t lowmask = region.bytewidth() - 1; + for (int i = 0; i < sizeof(T); i++) + { + int addr = (region.endianness() == ENDIANNESS_LITTLE) ? (address + sizeof(T) - 1 - i) : (address + i); + if (addr < region.bytes()) + { + if constexpr (sizeof(T) > 1) + mem_content <<= 8; + if (region.endianness() == ENDIANNESS_BIG) + mem_content |= region.as_u8((BYTE8_XOR_BE(addr) & lowmask) | (addr & ~lowmask)); + else + mem_content |= region.as_u8((BYTE8_XOR_LE(addr) & lowmask) | (addr & ~lowmask)); + } + } + + return mem_content; +} + +//------------------------------------------------- +// region_write - templated region writer for <sign>,<size> +// -> manager:machine():memory().regions[":maincpu"]:write_u16(0xC000, 0xF00D) +//------------------------------------------------- + +template <typename T> +void region_write(memory_region ®ion, offs_t address, T val) +{ + const offs_t lowmask = region.bytewidth() - 1; + for (int i = 0; i < sizeof(T); i++) + { + int addr = (region.endianness() == ENDIANNESS_BIG) ? (address + sizeof(T) - 1 - i) : (address + i); + if (addr < region.bytes()) + { + if (region.endianness() == ENDIANNESS_BIG) + region.base()[(BYTE8_XOR_BE(addr) & lowmask) | (addr & ~lowmask)] = val & 0xff; + else + region.base()[(BYTE8_XOR_LE(addr) & lowmask) | (addr & ~lowmask)] = val & 0xff; + if constexpr (sizeof(T) > 1) + val >>= 8; + } + } +} + +//------------------------------------------------- +// share_read - templated share readers for <sign>,<size> +// -> manager:machine():memory().shares[":maincpu"]:read_i8(0xC000) +//------------------------------------------------- + +template <typename T> +T share_read(memory_share &share, offs_t address) +{ + T mem_content = 0; + const offs_t lowmask = share.bytewidth() - 1; + u8 *ptr = (u8 *)share.ptr(); + for (int i = 0; i < sizeof(T); i++) + { + int addr = share.endianness() == ENDIANNESS_LITTLE ? address + sizeof(T) - 1 - i : address + i; + if (addr < share.bytes()) + { + if constexpr (sizeof(T) > 1) + mem_content <<= 8; + if (share.endianness() == ENDIANNESS_BIG) + mem_content |= ptr[(BYTE8_XOR_BE(addr) & lowmask) | (addr & ~lowmask)]; + else + mem_content |= ptr[(BYTE8_XOR_LE(addr) & lowmask) | (addr & ~lowmask)]; + } + } + + return mem_content; +} + +//------------------------------------------------- +// share_write - templated share writer for <sign>,<size> +// -> manager:machine():memory().shares[":maincpu"]:write_u16(0xC000, 0xF00D) +//------------------------------------------------- + +template <typename T> +void share_write(memory_share &share, offs_t address, T val) +{ + const offs_t lowmask = share.bytewidth() - 1; + u8 *ptr = (u8 *)share.ptr(); + for (int i = 0; i < sizeof(T); i++) + { + int addr = share.endianness() == ENDIANNESS_BIG ? address + sizeof(T) - 1 - i : address + i; + if (addr < share.bytes()) + { + if (share.endianness() == ENDIANNESS_BIG) + ptr[(BYTE8_XOR_BE(addr) & lowmask) | (addr & ~lowmask)] = val & 0xff; + else + ptr[(BYTE8_XOR_LE(addr) & lowmask) | (addr & ~lowmask)] = val & 0xff; + if constexpr (sizeof(T) > 1) + val >>= 8; + } + } +} + +} // anonymous namespace + + + +//------------------------------------------------- +// sol_lua_push - automatically convert +// map_handler_type to a string +//------------------------------------------------- + +int sol_lua_push(sol::types<map_handler_type>, lua_State *L, map_handler_type &&value) +{ + const char *typestr; + switch (value) + { + case AMH_NONE: + typestr = "none"; + break; + case AMH_RAM: + typestr = "ram"; + break; + case AMH_ROM: + typestr = "rom"; + break; + case AMH_NOP: + typestr = "nop"; + break; + case AMH_UNMAP: + typestr = "unmap"; + break; + case AMH_DEVICE_DELEGATE: + case AMH_DEVICE_DELEGATE_M: + case AMH_DEVICE_DELEGATE_S: + case AMH_DEVICE_DELEGATE_SM: + case AMH_DEVICE_DELEGATE_MO: + case AMH_DEVICE_DELEGATE_SMO: + typestr = "delegate"; + break; + case AMH_PORT: + typestr = "port"; + break; + case AMH_BANK: + typestr = "bank"; + break; + case AMH_DEVICE_SUBMAP: + typestr = "submap"; + break; + default: + typestr = "unknown"; + break; + } + return sol::stack::push(L, typestr); +} + + +//------------------------------------------------- +// sol_lua_push - automatically convert +// endianness_t to a string +//------------------------------------------------- + +int sol_lua_push(sol::types<endianness_t>, lua_State *L, endianness_t &&value) +{ + return sol::stack::push(L, util::endian_to_string_view(value)); +} + + +//------------------------------------------------- +// tap_helper - class for managing address space +// taps +//------------------------------------------------- + +class lua_engine::tap_helper +{ +public: + tap_helper(tap_helper const &) = delete; + tap_helper(tap_helper &&) = delete; + + tap_helper( + lua_engine &host, + address_space &space, + read_or_write mode, + offs_t start, + offs_t end, + std::string &&name, + sol::protected_function &&callback) + : m_callback(host.m_lua_state, std::move(callback)) + , m_space(space) + , m_handler() + , m_name(std::move(name)) + , m_start(start) + , m_end(end) + , m_mode(mode) + , m_installing(0U) + { + reinstall(); + } + + ~tap_helper() + { + remove(); + } + + offs_t start() const noexcept { return m_start; } + offs_t end() const noexcept { return m_end; } + std::string const &name() const noexcept { return m_name; } + + void reinstall() + { + switch (m_space.data_width()) + { + case 8: do_install<u8>(); break; + case 16: do_install<u16>(); break; + case 32: do_install<u32>(); break; + case 64: do_install<u64>(); break; + } + } + + void remove() + { + ++m_installing; + try + { + m_handler.remove(); + } + catch (...) + { + --m_installing; + throw; + } + --m_installing; + } + +private: + template <typename T> + void do_install() + { + if (m_installing) + return; + ++m_installing; + try + { + m_handler.remove(); + + switch (m_mode) + { + case read_or_write::READ: + m_handler = m_space.install_read_tap( + m_start, + m_end, + m_name, + [this] (offs_t offset, T &data, T mem_mask) + { + auto result = invoke_direct(m_callback, offset, data, mem_mask).template get<std::optional<T> >(); + if (result) + data = *result; + }, + &m_handler); + break; + case read_or_write::WRITE: + m_handler = m_space.install_write_tap( + m_start, + m_end, + m_name, + [this] (offs_t offset, T &data, T mem_mask) + { + auto result = invoke_direct(m_callback, offset, data, mem_mask).template get<std::optional<T> >(); + if (result) + data = *result; + }, + &m_handler); + break; + case read_or_write::READWRITE: + // won't ever get here, but compilers complain about unhandled enum value + break; + } + } + catch (...) + { + --m_installing; + throw; + } + --m_installing; + }; + + sol::protected_function m_callback; + address_space &m_space; + memory_passthrough_handler m_handler; + std::string m_name; + offs_t const m_start; + offs_t const m_end; + read_or_write const m_mode; + unsigned m_installing; +}; + + +//------------------------------------------------- +// mem_read - templated memory readers for <sign>,<size> +// -> manager:machine().devices[":maincpu"].spaces["program"]:read_i8(0xC000) +//------------------------------------------------- + +template <typename T> +T lua_engine::addr_space::mem_read(offs_t address) +{ + T mem_content = 0; + switch (sizeof(mem_content) * 8) + { + case 8: + mem_content = space.read_byte(address); + break; + case 16: + if (WORD_ALIGNED(address)) + mem_content = space.read_word(address); + else + mem_content = space.read_word_unaligned(address); + break; + case 32: + if (DWORD_ALIGNED(address)) + mem_content = space.read_dword(address); + else + mem_content = space.read_dword_unaligned(address); + break; + case 64: + if (QWORD_ALIGNED(address)) + mem_content = space.read_qword(address); + else + mem_content = space.read_qword_unaligned(address); + break; + default: + break; + } + + return mem_content; +} + +//------------------------------------------------- +// mem_write - templated memory writer for <sign>,<size> +// -> manager:machine().devices[":maincpu"].spaces["program"]:write_u16(0xC000, 0xF00D) +//------------------------------------------------- + +template <typename T> +void lua_engine::addr_space::mem_write(offs_t address, T val) +{ + switch (sizeof(val) * 8) + { + case 8: + space.write_byte(address, val); + break; + case 16: + if (WORD_ALIGNED(address)) + space.write_word(address, val); + else + space.write_word_unaligned(address, val); + break; + case 32: + if (DWORD_ALIGNED(address)) + space.write_dword(address, val); + else + space.write_dword_unaligned(address, val); + break; + case 64: + if (QWORD_ALIGNED(address)) + space.write_qword(address, val); + else + space.write_qword_unaligned(address, val); + break; + default: + break; + } +} + +//------------------------------------------------- +// log_mem_read - templated logical memory readers for <sign>,<size> +// -> manager:machine().devices[":maincpu"].spaces["program"]:read_log_i8(0xC000) +//------------------------------------------------- + +template <typename T> +T lua_engine::addr_space::log_mem_read(offs_t address) +{ + address_space *tspace; + if (!dev.translate(space.spacenum(), device_memory_interface::TR_READ, address, tspace)) + return 0; + + T mem_content = 0; + switch (sizeof(mem_content) * 8) + { + case 8: + mem_content = tspace->read_byte(address); + break; + case 16: + if (WORD_ALIGNED(address)) + mem_content = tspace->read_word(address); + else + mem_content = tspace->read_word_unaligned(address); + break; + case 32: + if (DWORD_ALIGNED(address)) + mem_content = tspace->read_dword(address); + else + mem_content = tspace->read_dword_unaligned(address); + break; + case 64: + if (QWORD_ALIGNED(address)) + mem_content = tspace->read_qword(address); + else + mem_content = tspace->read_qword_unaligned(address); + break; + default: + break; + } + + return mem_content; +} + +//------------------------------------------------- +// log_mem_write - templated logical memory writer for <sign>,<size> +// -> manager:machine().devices[":maincpu"].spaces["program"]:write_log_u16(0xC000, 0xF00D) +//------------------------------------------------- + +template <typename T> +void lua_engine::addr_space::log_mem_write(offs_t address, T val) +{ + address_space *tspace; + if (!dev.translate(space.spacenum(), device_memory_interface::TR_WRITE, address, tspace)) + return; + + switch (sizeof(val) * 8) + { + case 8: + tspace->write_byte(address, val); + break; + case 16: + if (WORD_ALIGNED(address)) + tspace->write_word(address, val); + else + tspace->write_word_unaligned(address, val); + break; + case 32: + if (DWORD_ALIGNED(address)) + tspace->write_dword(address, val); + else + tspace->write_dword_unaligned(address, val); + break; + case 64: + if (QWORD_ALIGNED(address)) + tspace->write_qword(address, val); + else + tspace->write_qword_unaligned(address, val); + break; + default: + break; + } +} + +//------------------------------------------------- +// mem_direct_read - templated direct memory readers for <sign>,<size> +// -> manager:machine().devices[":maincpu"].spaces["program"]:read_direct_i8(0xC000) +//------------------------------------------------- + +template <typename T> +T lua_engine::addr_space::direct_mem_read(offs_t address) +{ + T mem_content = 0; + const offs_t lowmask = space.data_width() / 8 - 1; + for (int i = 0; i < sizeof(T); i++) + { + int addr = space.endianness() == ENDIANNESS_LITTLE ? address + sizeof(T) - 1 - i : address + i; + u8 *base = (u8 *)space.get_read_ptr(addr & ~lowmask); + if (base) + { + if constexpr (sizeof(T) > 1) + mem_content <<= 8; + if (space.endianness() == ENDIANNESS_BIG) + mem_content |= base[BYTE8_XOR_BE(addr) & lowmask]; + else + mem_content |= base[BYTE8_XOR_LE(addr) & lowmask]; + } + } + + return mem_content; +} + +//------------------------------------------------- +// mem_direct_write - templated memory writer for <sign>,<size> +// -> manager:machine().devices[":maincpu"].spaces["program"]:write_direct_u16(0xC000, 0xF00D) +//------------------------------------------------- + +template <typename T> +void lua_engine::addr_space::direct_mem_write(offs_t address, T val) +{ + const offs_t lowmask = space.data_width() / 8 - 1; + for (int i = 0; i < sizeof(T); i++) + { + int addr = space.endianness() == ENDIANNESS_BIG ? address + sizeof(T) - 1 - i : address + i; + u8 *base = (u8 *)space.get_read_ptr(addr & ~lowmask); + if (base) + { + if (space.endianness() == ENDIANNESS_BIG) + base[BYTE8_XOR_BE(addr) & lowmask] = val & 0xff; + else + base[BYTE8_XOR_LE(addr) & lowmask] = val & 0xff; + if constexpr (sizeof(T) > 1) + val >>= 8; + } + } +} + +//------------------------------------------------- +// initialize_memory - register memory user types +//------------------------------------------------- + +void lua_engine::initialize_memory(sol::table &emu) +{ + + auto addr_space_type = sol().registry().new_usertype<addr_space>("addr_space", sol::no_constructor); + addr_space_type.set_function(sol::meta_function::to_string, + [] (addr_space const &sp) + { + device_t &d(sp.dev.device()); + return util::string_format("%s(%s):%s", d.shortname(), d.tag(), sp.space.name()); + }); + addr_space_type.set_function("read_i8", &addr_space::mem_read<s8>); + addr_space_type.set_function("read_u8", &addr_space::mem_read<u8>); + addr_space_type.set_function("read_i16", &addr_space::mem_read<s16>); + addr_space_type.set_function("read_u16", &addr_space::mem_read<u16>); + addr_space_type.set_function("read_i32", &addr_space::mem_read<s32>); + addr_space_type.set_function("read_u32", &addr_space::mem_read<u32>); + addr_space_type.set_function("read_i64", &addr_space::mem_read<s64>); + addr_space_type.set_function("read_u64", &addr_space::mem_read<u64>); + addr_space_type.set_function("write_i8", &addr_space::mem_write<s8>); + addr_space_type.set_function("write_u8", &addr_space::mem_write<u8>); + addr_space_type.set_function("write_i16", &addr_space::mem_write<s16>); + addr_space_type.set_function("write_u16", &addr_space::mem_write<u16>); + addr_space_type.set_function("write_i32", &addr_space::mem_write<s32>); + addr_space_type.set_function("write_u32", &addr_space::mem_write<u32>); + addr_space_type.set_function("write_i64", &addr_space::mem_write<s64>); + addr_space_type.set_function("write_u64", &addr_space::mem_write<u64>); + addr_space_type.set_function("readv_i8", &addr_space::log_mem_read<s8>); + addr_space_type.set_function("readv_u8", &addr_space::log_mem_read<u8>); + addr_space_type.set_function("readv_i16", &addr_space::log_mem_read<s16>); + addr_space_type.set_function("readv_u16", &addr_space::log_mem_read<u16>); + addr_space_type.set_function("readv_i32", &addr_space::log_mem_read<s32>); + addr_space_type.set_function("readv_u32", &addr_space::log_mem_read<u32>); + addr_space_type.set_function("readv_i64", &addr_space::log_mem_read<s64>); + addr_space_type.set_function("readv_u64", &addr_space::log_mem_read<u64>); + addr_space_type.set_function("writev_i8", &addr_space::log_mem_write<s8>); + addr_space_type.set_function("writev_u8", &addr_space::log_mem_write<u8>); + addr_space_type.set_function("writev_i16", &addr_space::log_mem_write<s16>); + addr_space_type.set_function("writev_u16", &addr_space::log_mem_write<u16>); + addr_space_type.set_function("writev_i32", &addr_space::log_mem_write<s32>); + addr_space_type.set_function("writev_u32", &addr_space::log_mem_write<u32>); + addr_space_type.set_function("writev_i64", &addr_space::log_mem_write<s64>); + addr_space_type.set_function("writev_u64", &addr_space::log_mem_write<u64>); + addr_space_type.set_function("read_direct_i8", &addr_space::direct_mem_read<s8>); + addr_space_type.set_function("read_direct_u8", &addr_space::direct_mem_read<u8>); + addr_space_type.set_function("read_direct_i16", &addr_space::direct_mem_read<s16>); + addr_space_type.set_function("read_direct_u16", &addr_space::direct_mem_read<u16>); + addr_space_type.set_function("read_direct_i32", &addr_space::direct_mem_read<s32>); + addr_space_type.set_function("read_direct_u32", &addr_space::direct_mem_read<u32>); + addr_space_type.set_function("read_direct_i64", &addr_space::direct_mem_read<s64>); + addr_space_type.set_function("read_direct_u64", &addr_space::direct_mem_read<u64>); + addr_space_type.set_function("write_direct_i8", &addr_space::direct_mem_write<s8>); + addr_space_type.set_function("write_direct_u8", &addr_space::direct_mem_write<u8>); + addr_space_type.set_function("write_direct_i16", &addr_space::direct_mem_write<s16>); + addr_space_type.set_function("write_direct_u16", &addr_space::direct_mem_write<u16>); + addr_space_type.set_function("write_direct_i32", &addr_space::direct_mem_write<s32>); + addr_space_type.set_function("write_direct_u32", &addr_space::direct_mem_write<u32>); + addr_space_type.set_function("write_direct_i64", &addr_space::direct_mem_write<s64>); + addr_space_type.set_function("write_direct_u64", &addr_space::direct_mem_write<u64>); + addr_space_type.set_function("read_range", + [] (addr_space &sp, sol::this_state s, u64 first, u64 last, int width, sol::object opt_step) -> sol::object + { + u64 step = 1; + if (opt_step.is<u64>()) + { + step = opt_step.as<u64>(); + if ((step < 1) || (step > last - first)) + { + luaL_error(s, "Invalid step"); + return sol::lua_nil; + } + } + + offs_t space_size = sp.space.addrmask(); + if ((first > space_size) || (last > space_size) || (last < first)) + { + luaL_error(s, "Invalid offset"); + return sol::lua_nil; + } + + luaL_Buffer buff; + int byte_count = width / 8 * (last - first + 1) / step; + switch (width) + { + case 8: + { + u8 *dest = (u8 *)luaL_buffinitsize(s, &buff, byte_count); + for ( ; first <= last; first += step) + *dest++ = sp.mem_read<u8>(first); + break; + } + case 16: + { + u16 *dest = (u16 *)luaL_buffinitsize(s, &buff, byte_count); + for ( ; first <= last; first += step) + *dest++ = sp.mem_read<u16>(first); + break; + } + case 32: + { + u32 *dest = (u32 *)luaL_buffinitsize(s, &buff, byte_count); + for( ; first <= last; first += step) + *dest++ = sp.mem_read<u32>(first); + break; + } + case 64: + { + u64 *dest = (u64 *)luaL_buffinitsize(s, &buff, byte_count); + for( ; first <= last; first += step) + *dest++ = sp.mem_read<u64>(first); + break; + } + default: + luaL_error(s, "Invalid width. Must be 8/16/32/64"); + return sol::lua_nil; + } + luaL_pushresultsize(&buff, byte_count); + return sol::make_reference(s, sol::stack_reference(s, -1)); + }); + addr_space_type.set_function("add_change_notifier", + [this] (addr_space &sp, sol::protected_function &&cb) + { + return sp.space.add_change_notifier( + [this, callback = std::move(cb)] (read_or_write mode) + { + char const *modestr = ""; + switch (mode) + { + case read_or_write::READ: modestr = "r"; break; + case read_or_write::WRITE: modestr = "w"; break; + case read_or_write::READWRITE: modestr = "rw"; break; + } + auto status = invoke(callback, modestr); + if (!status.valid()) + { + sol::error err = status; + osd_printf_error("[LUA ERROR] in address space change notifier: %s\n", err.what()); + } + }); + }); + addr_space_type.set_function("install_read_tap", + [this] (addr_space &sp, offs_t start, offs_t end, std::string &&name, sol::protected_function &&cb) + { + return std::make_unique<tap_helper>(*this, sp.space, read_or_write::READ, start, end, std::move(name), std::move(cb)); + }); + addr_space_type.set_function("install_write_tap", + [this] (addr_space &sp, offs_t start, offs_t end, std::string &&name, sol::protected_function &&cb) + { + return std::make_unique<tap_helper>(*this, sp.space, read_or_write::WRITE, start, end, std::move(name), std::move(cb)); + }); + addr_space_type["name"] = sol::property([] (addr_space &sp) { return sp.space.name(); }); + addr_space_type["shift"] = sol::property([] (addr_space &sp) { return sp.space.addr_shift(); }); + addr_space_type["index"] = sol::property([] (addr_space &sp) { return sp.space.spacenum(); }); + addr_space_type["address_mask"] = sol::property([] (addr_space &sp) { return sp.space.addrmask(); }); + addr_space_type["data_width"] = sol::property([] (addr_space &sp) { return sp.space.data_width(); }); + addr_space_type["endianness"] = sol::property([] (addr_space &sp) { return sp.space.endianness(); }); + addr_space_type["map"] = sol::property([] (addr_space &sp) { return sp.space.map(); }); + + + auto tap_type = sol().registry().new_usertype<tap_helper>("mempassthrough", sol::no_constructor); + tap_type.set_function("reinstall", &tap_helper::reinstall); + tap_type.set_function("remove", &tap_helper::remove); + tap_type["addrstart"] = sol::property(&tap_helper::start); + tap_type["addrend"] = sol::property(&tap_helper::end); + tap_type["name"] = sol::property(&tap_helper::name); + + + auto addrmap_type = sol().registry().new_usertype<address_map>("addrmap", sol::no_constructor); + addrmap_type["spacenum"] = sol::readonly(&address_map::m_spacenum); + addrmap_type["device"] = sol::readonly(&address_map::m_device); + addrmap_type["unmap_value"] = sol::readonly(&address_map::m_unmapval); + addrmap_type["global_mask"] = sol::readonly(&address_map::m_globalmask); + addrmap_type["entries"] = sol::property([] (address_map &m) { return simple_list_wrapper<address_map_entry>(m.m_entrylist); }); + + + auto mapentry_type = sol().registry().new_usertype<address_map_entry>("mapentry", sol::no_constructor); + mapentry_type["address_start"] = sol::readonly(&address_map_entry::m_addrstart); + mapentry_type["address_end"] = sol::readonly(&address_map_entry::m_addrend); + mapentry_type["address_mirror"] = sol::readonly(&address_map_entry::m_addrmirror); + mapentry_type["address_mask"] = sol::readonly(&address_map_entry::m_addrmask); + mapentry_type["mask"] = sol::readonly(&address_map_entry::m_mask); + mapentry_type["cswidth"] = sol::readonly(&address_map_entry::m_cswidth); + mapentry_type["read"] = sol::readonly(&address_map_entry::m_read); + mapentry_type["write"] = sol::readonly(&address_map_entry::m_write); + mapentry_type["share"] = sol::readonly(&address_map_entry::m_share); + mapentry_type["region"] = sol::readonly(&address_map_entry::m_region); + mapentry_type["region_offset"] = sol::readonly(&address_map_entry::m_rgnoffs); + + + auto handler_data_type = sol().registry().new_usertype<map_handler_data>("handlerdata", sol::no_constructor); + handler_data_type["handlertype"] = sol::property([] (map_handler_data const &hd) { return hd.m_type; }); // can't use member pointer or won't be converted to string + handler_data_type["bits"] = sol::readonly(&map_handler_data::m_bits); + handler_data_type["name"] = sol::readonly(&map_handler_data::m_name); + handler_data_type["tag"] = sol::readonly(&map_handler_data::m_tag); + + + auto memory_type = sol().registry().new_usertype<memory_manager>("memory", sol::no_constructor); + memory_type["banks"] = sol::property([] (memory_manager &mm) { return standard_tag_object_ptr_map<memory_bank>(mm.banks()); }); + memory_type["regions"] = sol::property([] (memory_manager &mm) { return standard_tag_object_ptr_map<memory_region>(mm.regions()); }); + memory_type["shares"] = sol::property([] (memory_manager &mm) { return standard_tag_object_ptr_map<memory_share>(mm.shares()); }); + + + auto bank_type = sol().registry().new_usertype<memory_bank>("membank", sol::no_constructor); + bank_type["tag"] = sol::property(&memory_bank::tag); + bank_type["entry"] = sol::property(&memory_bank::entry, &memory_bank::set_entry); + + + auto region_type = sol().registry().new_usertype<memory_region>("region", sol::no_constructor); + region_type.set_function( + "read", + [] (memory_region ®ion, sol::this_state s, offs_t offset, offs_t length) + { + // TODO: should this do something special if the offset isn't a multiple of the byte width? + buffer_helper buf(s); + const offs_t limit = std::min<offs_t>(region.bytes(), offset + length); + const offs_t copyable = (limit > offset) ? (limit - offset) : 0; + auto space = buf.prepare(copyable); + if (copyable) + std::memcpy(space.get(), ®ion.as_u8(offset), copyable); + space.add(copyable); + buf.push(); + return sol::make_reference(s, sol::stack_reference(s, -1)); + }); + region_type.set_function("read_i8", ®ion_read<s8>); + region_type.set_function("read_u8", ®ion_read<u8>); + region_type.set_function("read_i16", ®ion_read<s16>); + region_type.set_function("read_u16", ®ion_read<u16>); + region_type.set_function("read_i32", ®ion_read<s32>); + region_type.set_function("read_u32", ®ion_read<u32>); + region_type.set_function("read_i64", ®ion_read<s64>); + region_type.set_function("read_u64", ®ion_read<u64>); + region_type.set_function("write_i8", ®ion_write<s8>); + region_type.set_function("write_u8", ®ion_write<u8>); + region_type.set_function("write_i16", ®ion_write<s16>); + region_type.set_function("write_u16", ®ion_write<u16>); + region_type.set_function("write_i32", ®ion_write<s32>); + region_type.set_function("write_u32", ®ion_write<u32>); + region_type.set_function("write_i64", ®ion_write<s64>); + region_type.set_function("write_u64", ®ion_write<u64>); + region_type["tag"] = sol::property(&memory_region::name); + region_type["size"] = sol::property(&memory_region::bytes); + region_type["length"] = sol::property([] (memory_region &r) { return r.bytes() / r.bytewidth(); }); + region_type["endianness"] = sol::property(&memory_region::endianness); + region_type["bitwidth"] = sol::property(&memory_region::bitwidth); + region_type["bytewidth"] = sol::property(&memory_region::bytewidth); + + + auto share_type = sol().registry().new_usertype<memory_share>("share", sol::no_constructor); + share_type.set_function("read_i8", &share_read<s8>); + share_type.set_function("read_u8", &share_read<u8>); + share_type.set_function("read_i16", &share_read<s16>); + share_type.set_function("read_u16", &share_read<u16>); + share_type.set_function("read_i32", &share_read<s32>); + share_type.set_function("read_u32", &share_read<u32>); + share_type.set_function("read_i64", &share_read<s64>); + share_type.set_function("read_u64", &share_read<u64>); + share_type.set_function("write_i8", &share_write<s8>); + share_type.set_function("write_u8", &share_write<u8>); + share_type.set_function("write_i16", &share_write<s16>); + share_type.set_function("write_u16", &share_write<u16>); + share_type.set_function("write_i32", &share_write<s32>); + share_type.set_function("write_u32", &share_write<u32>); + share_type.set_function("write_i64", &share_write<s64>); + share_type.set_function("write_u64", &share_write<u64>); + share_type["tag"] = sol::property(&memory_share::name); + share_type["size"] = sol::property(&memory_share::bytes); + share_type["length"] = sol::property([] (memory_share &s) { return s.bytes() / s.bytewidth(); }); + share_type["endianness"] = sol::property(&memory_share::endianness); + share_type["bitwidth"] = sol::property(&memory_share::bitwidth); + share_type["bytewidth"] = sol::property(&memory_share::bytewidth); + +} diff --git a/src/frontend/mame/luaengine_render.cpp b/src/frontend/mame/luaengine_render.cpp new file mode 100644 index 00000000000..8f931f91dac --- /dev/null +++ b/src/frontend/mame/luaengine_render.cpp @@ -0,0 +1,1274 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + luaengine_render.cpp + + Controls execution of the core MAME system. + +***************************************************************************/ + +#include "emu.h" +#include "luaengine.ipp" + +#include "mame.h" +#include "ui/ui.h" + +#include "render.h" +#include "rendlay.h" +#include "rendutil.h" + +#include "interface/uievents.h" +#include "ioprocs.h" + +#include <algorithm> +#include <atomic> +#include <iterator> + + +namespace { + +class render_texture_helper +{ +public: + render_texture_helper(render_texture_helper const &) = delete; + + render_texture_helper(render_texture_helper &&that) + : texture(that.texture) + , bitmap(that.bitmap) + , manager(that.manager) + , storage_lock_count(that.storage_lock_count) + , palette_lock_count(that.palette_lock_count) + { + that.texture = nullptr; + that.bitmap.reset(); + } + + template <typename T> + render_texture_helper(sol::this_state s, render_manager &m, std::shared_ptr<T> const &b, texture_format f) + : texture(nullptr) + , bitmap(b) + , manager(m) + , storage_lock_count(b->storage_lock_count) + , palette_lock_count(b->palette_lock_count) + { + if (bitmap) + { + texture = manager.texture_alloc(); + if (texture) + { + ++storage_lock_count; + ++palette_lock_count; + texture->set_bitmap(*bitmap, bitmap->cliprect(), f); + } + else + { + luaL_error(s, "Error allocating texture"); + } + } + } + + ~render_texture_helper() + { + free(); + } + + bool valid() const + { + return texture && bitmap; + } + + void free() + { + if (texture) + { + manager.texture_free(texture); + texture = nullptr; + } + if (bitmap) + { + assert(storage_lock_count); + assert(palette_lock_count); + --storage_lock_count; + --palette_lock_count; + bitmap.reset(); + } + } + + render_texture *texture; + std::shared_ptr<bitmap_t> bitmap; + +private: + render_manager &manager; + std::atomic<unsigned> &storage_lock_count; + std::atomic<unsigned> &palette_lock_count; +}; + + +struct layout_file_elements +{ + layout_file_elements(layout_file &f) : file(f) { } + layout_file::element_map &items() { return file.elements(); } + + layout_file &file; +}; + + +struct layout_file_views +{ + layout_file_views(layout_file &f) : file(f) { } + layout_file::view_list &items() { return file.views(); } + + static layout_view &unwrap(layout_file::view_list::iterator const &it) { return *it; } + static int push_key(lua_State *L, layout_file::view_list::iterator const &it, std::size_t ix) { return sol::stack::push_reference(L, it->unqualified_name()); } + + layout_file &file; +}; + + +struct layout_view_items +{ + layout_view_items(layout_view &v) : view(v) { } + layout_view::item_list &items() { return view.items(); } + + static layout_view_item &unwrap(layout_view::item_list::iterator const &it) { return *it; } + static int push_key(lua_State *L, layout_view::item_list::iterator const &it, std::size_t ix) { return sol::stack::push(L, ix + 1); } + + layout_view &view; +}; + + +struct render_target_view_names +{ + render_target_view_names(render_target &t) : target(t), count(-1) { } + + render_target ⌖ + int count; +}; + + +template <typename T> +auto get_bitmap_pixels(T const &bitmap, sol::this_state s, rectangle const &bounds) +{ + if (!bitmap.cliprect().contains(bounds)) + luaL_error(s, "Bounds exceed source clipping rectangle"); + luaL_Buffer buff; + size_t const size(bounds.width() * bounds.height() * sizeof(typename T::pixel_t)); + auto ptr = reinterpret_cast<typename T::pixel_t *>(luaL_buffinitsize(s, &buff, size)); + for (auto y = bounds.top(); bounds.bottom() >= y; ++y, ptr += bounds.width()) + std::copy_n(&bitmap.pix(y, bounds.left()), bounds.width(), ptr); + luaL_pushresultsize(&buff, size); + return std::make_tuple(sol::make_reference(s, sol::stack_reference(s, -1)), bounds.width(), bounds.height()); +} + + +template <typename T> +auto make_bitmap_specific_type(sol::table registry, char const *name) +{ + auto result = registry.new_usertype<T>( + name, + sol::no_constructor, + sol::base_classes, sol::bases<bitmap_t>()); + result.set_function("pix", [] (T &bitmap, int32_t x, int32_t y) { return bitmap.pix(y, x); }); + result["pixels"] = sol::overload( + [] (T const &bitmap, sol::this_state s) + { + return get_bitmap_pixels(bitmap, s, bitmap.cliprect()); + }, + [] (T const &bitmap, sol::this_state s, int32_t minx, int32_t miny, int32_t maxx, int32_t maxy) + { + return get_bitmap_pixels(bitmap, s, rectangle(minx, maxx, miny, maxy)); + }); + result["fill"] = sol::overload( + static_cast<void (T::*)(typename T::pixel_t)>(&T::fill), + [] (T &bitmap, typename T::pixel_t color, int32_t minx, int32_t miny, int32_t maxx, int32_t maxy) + { + bitmap.fill(color, rectangle(minx, maxx, miny, maxy)); + }); + result.set_function( + "plot", + [] (T &bitmap, int32_t x, int32_t y, typename T::pixel_t color) + { + if (bitmap.cliprect().contains(x, y)) + bitmap.pix(y, x) = color; + }); + result.set_function("plot_box", &T::plot_box); + result["bpp"] = sol::property(&T::bpp); + return result; +} + +} // anonymous namespace + + +namespace sol { + +template <> struct is_container<layout_file_elements> : std::true_type { }; +template <> struct is_container<layout_file_views> : std::true_type { }; +template <> struct is_container<layout_view_items> : std::true_type { }; +template <> struct is_container<render_target_view_names> : std::true_type { }; + + +template <> +struct usertype_container<layout_file_elements> : lua_engine::immutable_collection_helper<layout_file_elements, layout_file::element_map> +{ +private: + template <bool Indexed> + static int next_pairs(lua_State *L) + { + usertype_container::indexed_iterator &i(stack::unqualified_get<user<usertype_container::indexed_iterator> >(L, 1)); + if (i.src.end() == i.it) + return stack::push(L, lua_nil); + int result; + if constexpr (Indexed) + result = stack::push(L, i.ix + 1); + else + result = stack::push(L, i.it->first); + result += stack::push_reference(L, i.it->second); + ++i; + return result; + } + + template <bool Indexed> + static int start_pairs(lua_State *L) + { + layout_file_elements &self(usertype_container::get_self(L)); + stack::push(L, next_pairs<Indexed>); + stack::push<user<usertype_container::indexed_iterator> >(L, self.items(), self.items().begin()); + stack::push(L, lua_nil); + return 3; + } + +public: + static int at(lua_State *L) + { + layout_file_elements &self(usertype_container::get_self(L)); + std::ptrdiff_t const index(stack::unqualified_get<std::ptrdiff_t>(L, 2)); + if ((0 >= index) || (self.items().size() < index)) + return stack::push(L, lua_nil); + auto const found(std::next(self.items().begin(), index - 1)); + return stack::push_reference(L, found->second); + } + + static int get(lua_State *L) + { + layout_file_elements &self(usertype_container::get_self(L)); + char const *const tag(stack::unqualified_get<char const *>(L)); + auto const found(self.items().find(tag)); + if (self.items().end() == found) + return stack::push(L, lua_nil); + else + return stack::push_reference(L, found->second); + } + + static int index_get(lua_State *L) + { + return get(L); + } + + static int index_of(lua_State *L) + { + layout_file_elements &self(usertype_container::get_self(L)); + auto &obj(stack::unqualified_get<layout_element>(L, 2)); + auto it(self.items().begin()); + std::ptrdiff_t ix(0); + while ((self.items().end() != it) && (&it->second != &obj)) + { + ++it; + ++ix; + } + if (self.items().end() == it) + return stack::push(L, lua_nil); + else + return stack::push(L, ix + 1); + } + + static int size(lua_State *L) + { + layout_file_elements &self(usertype_container::get_self(L)); + return stack::push(L, self.items().size()); + } + + static int empty(lua_State *L) + { + layout_file_elements &self(usertype_container::get_self(L)); + return stack::push(L, self.items().empty()); + } + + static int next(lua_State *L) { return stack::push(L, next_pairs<false>); } + static int pairs(lua_State *L) { return start_pairs<false>(L); } + static int ipairs(lua_State *L) { return start_pairs<true>(L); } +}; + + +template <> +struct usertype_container<layout_file_views> : lua_engine::immutable_sequence_helper<layout_file_views, layout_file::view_list> +{ +public: + static int get(lua_State *L) + { + layout_file_views &self(get_self(L)); + char const *const name(stack::unqualified_get<char const *>(L)); + auto const found(std::find_if( + self.file.views().begin(), + self.file.views().end(), + [&name] (layout_view &v) { return v.unqualified_name() == name; })); + if (self.file.views().end() != found) + return stack::push_reference(L, *found); + else + return stack::push(L, lua_nil); + } + + static int index_get(lua_State *L) + { + return get(L); + } +}; + + +template <> +struct usertype_container<layout_view_items> : lua_engine::immutable_sequence_helper<layout_view_items, layout_view::item_list> +{ +public: + static int get(lua_State *L) + { + layout_view_items &self(get_self(L)); + char const *const id(stack::unqualified_get<char const *>(L)); + layout_view_item *const item(self.view.get_item(id)); + if (item) + return stack::push_reference(L, *item); + else + return stack::push(L, lua_nil); + } + + static int index_get(lua_State *L) + { + return get(L); + } + + static int pairs(lua_State *L) + { + return luaL_error(L, "sol: cannot call 'pairs' on type '%s': not iterable by ID", sol::detail::demangle<layout_view_items>().c_str()); + } +}; + + +template <> +struct usertype_container<render_target_view_names> : lua_engine::immutable_container_helper<render_target_view_names> +{ +private: + struct iterator + { + iterator(render_target &t, unsigned i) : target(t), index(i) { } + + render_target ⌖ + unsigned index; + }; + + static int next_pairs(lua_State *L) + { + iterator &i(stack::unqualified_get<user<iterator> >(L, 1)); + char const *name(i.target.view_name(i.index)); + if (!name) + return stack::push(L, lua_nil); + int result = stack::push(L, i.index + 1); + result += stack::push(L, name); + ++i.index; + return result; + } + +public: + static int at(lua_State *L) + { + render_target_view_names &self(get_self(L)); + unsigned const index(stack::unqualified_get<unsigned>(L, 2)); + return stack::push(L, self.target.view_name(index - 1)); + } + + static int get(lua_State *L) + { + return at(L); + } + + static int index_get(lua_State *L) + { + return at(L); + } + + static int find(lua_State *L) + { + render_target_view_names &self(get_self(L)); + char const *const key(stack::unqualified_get<char const *>(L, 2)); + for (unsigned i = 0; ; ++i) + { + char const *const name(self.target.view_name(i)); + if (!name) + return stack::push(L, lua_nil); + else if (!std::strcmp(key, name)) + return stack::push(L, i + 1); + } + } + + static int index_of(lua_State *L) + { + return find(L); + } + + static int size(lua_State *L) + { + render_target_view_names &self(get_self(L)); + if (0 > self.count) + for (self.count = 0; self.target.view_name(self.count); ++self.count) { } + return stack::push(L, self.count); + } + + static int empty(lua_State *L) + { + render_target_view_names &self(get_self(L)); + return stack::push(L, !self.target.view_name(0)); + } + + static int next(lua_State *L) + { + return stack::push(L, next_pairs); + } + + static int pairs(lua_State *L) + { + render_target_view_names &self(get_self(L)); + stack::push(L, next_pairs); + stack::push<user<iterator> >(L, self.target, 0); + stack::push(L, lua_nil); + return 3; + } + + static int ipairs(lua_State *L) + { + return pairs(L); + } +}; + +} // namespace sol + + +//------------------------------------------------- +// sol_lua_push - automatically convert +// osd::ui_event_handler::pointer to a string +//------------------------------------------------- + +int sol_lua_push(sol::types<osd::ui_event_handler::pointer>, lua_State *L, osd::ui_event_handler::pointer &&value) +{ + const char *typestr = "invalid"; + switch (value) + { + case osd::ui_event_handler::pointer::UNKNOWN: + typestr = "unknown"; + break; + case osd::ui_event_handler::pointer::MOUSE: + typestr = "mouse"; + break; + case osd::ui_event_handler::pointer::PEN: + typestr = "pen"; + break; + case osd::ui_event_handler::pointer::TOUCH: + typestr = "touch"; + break; + } + return sol::stack::push(L, typestr); +} + + +template <typename T> +class lua_engine::bitmap_helper : public T +{ +public: + using ptr = std::shared_ptr<bitmap_helper>; + + bitmap_helper(bitmap_helper const &) = delete; + bitmap_helper(bitmap_helper &&) = delete; + bitmap_helper &operator=(bitmap_helper const &) = delete; + bitmap_helper &operator=(bitmap_helper &&) = delete; + + bitmap_helper(sol::this_state s, int width, int height, int xslop, int yslop) + : T(width, height, xslop, yslop) + , storage_lock_count(0) + , palette_lock_count(0) + , storage() + { + if ((0 < width) && (0 < height) && !this->valid()) + luaL_error(s, "Error allocating bitmap storage"); + } + + bitmap_helper(ptr const &source, rectangle const &subrect) + : T(*source, subrect) + , storage_lock_count(0) + , palette_lock_count(0) + , storage(source->storage ? source->storage : source) + { + ++storage->storage_lock_count; + this->set_palette(source->palette()); + } + + ~bitmap_helper() + { + assert(!storage_lock_count); + assert(!palette_lock_count); + release_storage(); + } + + void reset(sol::this_state s) + { + if (storage_lock_count) + luaL_error(s, "Cannot reset bitmap while in use"); + palette_t *const p(this->palette()); + if (p) + p->ref(); + T::reset(); + this->set_palette(p); + if (p) + p->deref(); + release_storage(); + } + + void allocate(sol::this_state s, int width, int height, int xslop, int yslop) + { + if (storage_lock_count) + luaL_error(s, "Cannot reallocate bitmap while in use"); + palette_t *const p(this->palette()); + if (p) + p->ref(); + T::allocate(width, height, xslop, yslop); + this->set_palette(p); + if (p) + p->deref(); + release_storage(); + if ((0 < width) && (0 < height) && !this->valid()) + luaL_error(s, "Error allocating bitmap storage"); + } + + void resize(sol::this_state s, int width, int height, int xslop, int yslop) + { + if (storage_lock_count) + luaL_error(s, "Cannot resize bitmap while in use"); + T::resize(width, height, xslop, yslop); + release_storage(); + if ((0 < width) && (0 < height) && !this->valid()) + luaL_error(s, "Error allocating bitmap storage"); + } + + void wrap(sol::this_state s, ptr const &source, rectangle const &subrect) + { + if (source.get() == this) + luaL_error(s, "Bitmap cannot wrap itself"); + if (storage_lock_count) + luaL_error(s, "Cannot free bitmap storage while in use"); + if (!source->cliprect().contains(subrect)) + luaL_error(s, "Bounds exceed source clipping rectangle"); + palette_t *const p(this->palette()); + if (p) + p->ref(); + T::wrap(*source, subrect); + this->set_palette(p); + if (p) + p->deref(); + release_storage(); + storage = source->storage ? source->storage : source; + ++storage->storage_lock_count; + } + + std::atomic<unsigned> storage_lock_count; + std::atomic<unsigned> palette_lock_count; + + template <typename B> + static auto make_type(sol::table ®istry, char const *name) + { + auto result = registry.new_usertype<bitmap_helper>( + name, + sol::call_constructor, sol::factories( + [] (sol::this_state s) + { + return std::make_shared<bitmap_helper>(s, 0, 0, 0, 0); + }, + [] (sol::this_state s, int width, int height) + { + return std::make_shared<bitmap_helper>(s, width, height, 0, 0); + }, + [] (sol::this_state s, int width, int height, int xslop, int yslop) + { + return std::make_shared<bitmap_helper>(s, width, height, xslop, yslop); + }, + [] (ptr const &source) + { + return std::make_shared<bitmap_helper>(source, source->cliprect()); + }, + [] (sol::this_state s, ptr const &source, int32_t minx, int32_t miny, int32_t maxx, int32_t maxy) + { + rectangle const subrect(minx, maxx, miny, maxy); + if (!source->cliprect().contains(subrect)) + luaL_error(s, "Bounds exceed source clipping rectangle"); + return std::make_shared<bitmap_helper>(source, subrect); + }), + sol::base_classes, sol::bases<T, B, bitmap_t>()); + add_bitmap_members(result); + return result; + } + + template <typename B> + static auto make_indexed_type(sol::table ®istry, char const *name) + { + auto result = registry.new_usertype<bitmap_helper>( + name, + sol::call_constructor, sol::factories( + [] (sol::this_state s, palette_wrapper &p) + { + ptr result = std::make_shared<bitmap_helper>(s, 0, 0, 0, 0); + result->set_palette(&p.palette()); + return result; + }, + [] (sol::this_state s, palette_wrapper &p, int width, int height) + { + ptr result = std::make_shared<bitmap_helper>(s, width, height, 0, 0); + result->set_palette(&p.palette()); + return result; + }, + [] (sol::this_state s, palette_wrapper &p, int width, int height, int xslop, int yslop) + { + ptr result = std::make_shared<bitmap_helper>(s, width, height, xslop, yslop); + result->set_palette(&p.palette()); + return result; + }, + [] (ptr const &source) + { + return std::make_shared<bitmap_helper>(source, source->cliprect()); + }, + [] (sol::this_state s, ptr const &source, int32_t minx, int32_t miny, int32_t maxx, int32_t maxy) + { + rectangle const subrect(minx, maxx, miny, maxy); + if (!source->cliprect().contains(subrect)) + luaL_error(s, "Bounds exceed source clipping rectangle"); + return std::make_shared<bitmap_helper>(source, subrect); + }), + sol::base_classes, sol::bases<B, bitmap_t>()); + result["palette"] = sol::property( + [] (bitmap_helper const &b) + { + return b.palette() + ? std::optional<palette_wrapper>(std::in_place, *b.palette()) + : std::optional<palette_wrapper>(); + }, + [] (bitmap_helper &b, sol::this_state s, palette_wrapper &p) + { + if (b.palette_lock_count) + luaL_error(s, "Cannot set palette while in use"); + b.set_palette(&p.palette()); + }); + add_bitmap_members(result); + return result; + } + +private: + void release_storage() + { + if (storage) + { + assert(storage->storage_lock_count); + --storage->storage_lock_count; + storage.reset(); + } + } + + template <typename U> + static void add_bitmap_members(U &type) + { + type.set_function("reset", &bitmap_helper::reset); + type["allocate"] = sol::overload( + &bitmap_helper::allocate, + [] (bitmap_helper &bitmap, sol::this_state s, int width, int height) { bitmap.allocate(s, width, height, 0, 0); }); + type["resize"] = sol::overload( + &bitmap_helper::resize, + [] (bitmap_helper &bitmap, sol::this_state s, int width, int height) { bitmap.resize(s, width, height, 0, 0); }); + type["wrap"] = sol::overload( + [] (bitmap_helper &bitmap, sol::this_state s, ptr const &source) + { + bitmap.wrap(s, source, source->cliprect()); + }, + [] (bitmap_helper &bitmap, sol::this_state s, ptr const &source, int32_t minx, int32_t miny, int32_t maxx, int32_t maxy) + { + bitmap.wrap(s, source, rectangle(minx, maxx, miny, maxy)); + }); + type["locked"] = sol::property([] (bitmap_helper const &b) { return bool(b.storage_lock_count); }); + } + + ptr storage; +}; + + +//------------------------------------------------- +// initialize_render - register render user types +//------------------------------------------------- + +void lua_engine::initialize_render(sol::table &emu) +{ + + auto bounds_type = emu.new_usertype<render_bounds>( + "render_bounds", + sol::call_constructor, sol::initializers( + [] (render_bounds &b) { new (&b) render_bounds{ 0.0F, 0.0F, 1.0F, 1.0F }; }, + [] (render_bounds &b, float x0, float y0, float x1, float y1) { new (&b) render_bounds{ x0, y0, x1, y1 }; })); + bounds_type.set_function("includes", &render_bounds::includes); + bounds_type.set_function("set_xy", &render_bounds::set_xy); + bounds_type.set_function("set_wh", &render_bounds::set_wh); + bounds_type["x0"] = &render_bounds::x0; + bounds_type["y0"] = &render_bounds::y0; + bounds_type["x1"] = &render_bounds::x1; + bounds_type["y1"] = &render_bounds::y1; + bounds_type["width"] = sol::property(&render_bounds::width, [] (render_bounds &b, float w) { b.x1 = b.x0 + w; }); + bounds_type["height"] = sol::property(&render_bounds::height, [] (render_bounds &b, float h) { b.y1 = b.y0 + h; }); + bounds_type["aspect"] = sol::property(&render_bounds::aspect); + + + auto color_type = emu.new_usertype<render_color>( + "render_color", + sol::call_constructor, sol::initializers( + [] (render_color &c) { new (&c) render_color{ 1.0F, 1.0F, 1.0F, 1.0F }; }, + [] (render_color &c, float a, float r, float g, float b) { new (&c) render_color{ a, r, g, b }; })); + color_type.set_function("set", &render_color::set); + color_type["a"] = &render_color::a; + color_type["r"] = &render_color::r; + color_type["g"] = &render_color::g; + color_type["b"] = &render_color::b; + + + auto palette_type = emu.new_usertype<palette_wrapper>( + "palette", + sol::call_constructor, sol::initializers( + [] (palette_wrapper &pal, uint32_t colors) { new (&pal) palette_wrapper(colors, 1); }, + [] (palette_wrapper &pal, uint64_t colors, uint32_t groups) { new (&pal) palette_wrapper(colors, groups); })); + palette_type.set_function( + "entry_color", + [] (palette_wrapper const &pal, uint32_t index) { return uint32_t(pal.palette().entry_color(index)); }); + palette_type.set_function( + "entry_contrast", + [] (palette_wrapper const &pal, uint32_t index) { return pal.palette().entry_contrast(index); }); + palette_type.set_function( + "entry_adjusted_color", + [] (palette_wrapper const &pal, uint32_t index, std::optional<uint32_t> group) + { + if (group) + { + if ((pal.palette().num_colors() <= index) || (pal.palette().num_groups() <= *group)) + return uint32_t(rgb_t::black()); + index += *group * pal.palette().num_colors(); + } + return uint32_t(pal.palette().entry_adjusted_color(index)); + }); + palette_type["entry_set_color"] = sol::overload( + [] (palette_wrapper &pal, sol::this_state s, uint32_t index, uint32_t color) + { + if (pal.palette().num_colors() <= index) + luaL_error(s, "Color index out of range"); + pal.palette().entry_set_color(index, rgb_t(color)); + }, + [] (palette_wrapper &pal, sol::this_state s, uint32_t index, uint8_t red, uint8_t green, uint8_t blue) + { + if (pal.palette().num_colors() <= index) + luaL_error(s, "Color index out of range"); + pal.palette().entry_set_color(index, rgb_t(red, green, blue)); + }); + palette_type.set_function( + "entry_set_red_level", + [] (palette_wrapper &pal, sol::this_state s, uint32_t index, uint8_t level) + { + if (pal.palette().num_colors() <= index) + luaL_error(s, "Color index out of range"); + pal.palette().entry_set_red_level(index, level); + }); + palette_type.set_function( + "entry_set_green_level", + [] (palette_wrapper &pal, sol::this_state s, uint32_t index, uint8_t level) + { + if (pal.palette().num_colors() <= index) + luaL_error(s, "Color index out of range"); + pal.palette().entry_set_green_level(index, level); + }); + palette_type.set_function( + "entry_set_blue_level", + [] (palette_wrapper &pal, sol::this_state s, uint32_t index, uint8_t level) + { + if (pal.palette().num_colors() <= index) + luaL_error(s, "Color index out of range"); + pal.palette().entry_set_blue_level(index, level); + }); + palette_type.set_function( + "entry_set_contrast", + [] (palette_wrapper &pal, sol::this_state s, uint32_t index, float contrast) + { + if (pal.palette().num_colors() <= index) + luaL_error(s, "Color index out of range"); + pal.palette().entry_set_contrast(index, contrast); + }); + palette_type.set_function( + "group_set_brightness", + [] (palette_wrapper &pal, sol::this_state s, uint32_t group, float brightness) + { + if (pal.palette().num_colors() <= group) + luaL_error(s, "Group index out of range"); + pal.palette().group_set_brightness(group, brightness); + }); + palette_type.set_function( + "group_set_contrast", + [] (palette_wrapper &pal, sol::this_state s, uint32_t group, float contrast) + { + if (pal.palette().num_colors() <= group) + luaL_error(s, "Group index out of range"); + pal.palette().group_set_contrast(group, contrast); + }); + palette_type["colors"] = sol::property([] (palette_wrapper const &pal) { return pal.palette().num_colors(); }); + palette_type["groups"] = sol::property([] (palette_wrapper const &pal) { return pal.palette().num_groups(); }); + palette_type["max_index"] = sol::property([] (palette_wrapper const &pal) { return pal.palette().max_index(); }); + palette_type["black_entry"] = sol::property([] (palette_wrapper const &pal) { return pal.palette().black_entry(); }); + palette_type["white_entry"] = sol::property([] (palette_wrapper const &pal) { return pal.palette().white_entry(); }); + palette_type["brightness"] = sol::property([] (palette_wrapper &pal, float brightness) { pal.palette().set_brightness(brightness); }); + palette_type["contrast"] = sol::property([] (palette_wrapper &pal, float contrast) { pal.palette().set_contrast(contrast); }); + palette_type["gamma"] = sol::property([] (palette_wrapper &pal, float gamma) { pal.palette().set_gamma(gamma); }); + + + auto bitmap_type = sol().registry().new_usertype<bitmap_t>("bitmap", sol::no_constructor); + bitmap_type.set_function( + "cliprect", + [] (bitmap_t const &bitmap) + { + rectangle const &result(bitmap.cliprect()); + return std::make_tuple(result.left(), result.top(), result.right(), result.bottom()); + }); + bitmap_type["fill"] = sol::overload( + static_cast<void (bitmap_t::*)(uint64_t)>(&bitmap_t::fill), + [] (bitmap_t &bitmap, uint64_t color, int32_t minx, int32_t miny, int32_t maxx, int32_t maxy) + { + bitmap.fill(color, rectangle(minx, maxx, miny, maxy)); + }); + bitmap_type.set_function("plot_box", &bitmap_t::plot_box); + bitmap_type["width"] = sol::property(&bitmap_t::width); + bitmap_type["height"] = sol::property(&bitmap_t::height); + bitmap_type["rowpixels"] = sol::property(&bitmap_t::rowpixels); + bitmap_type["rowbytes"] = sol::property(&bitmap_t::rowbytes); + bitmap_type["bpp"] = sol::property(&bitmap_t::bpp); + bitmap_type["valid"] = sol::property(&bitmap_t::valid); + + make_bitmap_specific_type<bitmap8_t>(sol().registry(), "bitmap8"); + make_bitmap_specific_type<bitmap16_t>(sol().registry(), "bitmap16"); + make_bitmap_specific_type<bitmap32_t>(sol().registry(), "bitmap32"); + make_bitmap_specific_type<bitmap64_t>(sol().registry(), "bitmap64"); + + bitmap_helper<bitmap_ind8>::make_indexed_type<bitmap8_t>(emu, "bitmap_ind8"); + bitmap_helper<bitmap_ind16>::make_indexed_type<bitmap16_t>(emu, "bitmap_ind16"); + bitmap_helper<bitmap_ind32>::make_indexed_type<bitmap32_t>(emu, "bitmap_ind32"); + bitmap_helper<bitmap_ind64>::make_indexed_type<bitmap64_t>(emu, "bitmap_ind64"); + + bitmap_helper<bitmap_yuy16>::make_type<bitmap16_t>(emu, "bitmap_yuy16"); + bitmap_helper<bitmap_rgb32>::make_type<bitmap32_t>(emu, "bitmap_rgb32"); + + // ARGB32 bitmaps get extra functionality + auto bitmap_argb32_type = emu.new_usertype<bitmap_argb32>( + "bitmap_argb32_t", + sol::no_constructor, + sol::base_classes, sol::bases<bitmap32_t, bitmap_t>()); + bitmap_argb32_type.set_function( + "resample", + [] (bitmap_argb32 &bitmap, bitmap_argb32 &dest, std::optional<render_color> color) + { + render_resample_argb_bitmap_hq(dest, bitmap, color ? *color : render_color{ 1.0F, 1.0F, 1.0F, 1.0F }); + }); + + + auto bitmap_argb32_helper_type = bitmap_helper<bitmap_argb32>::make_type<bitmap32_t>(emu, "bitmap_argb32"); + bitmap_argb32_helper_type.set_function( + "load", + [] (sol::this_state s, std::string_view data) + { + auto stream(util::ram_read(data.data(), data.size())); + if (!stream) + luaL_error(s, "Error allocating stream wrapper"); + auto b = std::make_shared<bitmap_helper<bitmap_argb32> >(s, 0, 0, 0, 0); + switch (render_detect_image(*stream)) + { + case RENDUTIL_IMGFORMAT_PNG: + render_load_png(*b, *stream); + if (!b->valid()) + luaL_error(s, "Invalid or unsupported PNG data"); + break; + case RENDUTIL_IMGFORMAT_JPEG: + render_load_jpeg(*b, *stream); + if (!b->valid()) + luaL_error(s, "Invalid or unsupported PNG data"); + break; + case RENDUTIL_IMGFORMAT_MSDIB: + render_load_msdib(*b, *stream); + if (!b->valid()) + luaL_error(s, "Invalid or unsupported Microsoft DIB data"); + break; + default: + luaL_error(s, "Unsupported bitmap data format"); + } + return b; + }); + + auto render_texture_type = emu.new_usertype<render_texture_helper>("render_texture", sol::no_constructor); + render_texture_type.set_function("free", &render_texture_helper::free); + render_texture_type["valid"] = sol::property(&render_texture_helper::valid); + + + auto layout_element_type = sol().registry().new_usertype<layout_element>("layout_element", sol::no_constructor); + layout_element_type.set_function("invalidate", &layout_element::invalidate); + layout_element_type.set_function( + "set_draw_callback", + make_simple_callback_setter( + &layout_element::set_draw_callback, + nullptr, + "set_draw_callback", + "draw")); + layout_element_type["default_state"] = sol::property( + [] (layout_element const &e) -> std::optional<int> + { + if (0 <= e.default_state()) + return e.default_state(); + else + return std::nullopt; + }); + + + auto layout_view_type = sol().registry().new_usertype<layout_view>("layout_view", sol::no_constructor); + layout_view_type.set_function("has_screen", &layout_view::has_screen); + layout_view_type.set_function( + "set_prepare_items_callback", + make_simple_callback_setter( + &layout_view::set_prepare_items_callback, + nullptr, + "set_prepare_items_callback", + "prepare items")); + layout_view_type.set_function( + "set_preload_callback", + make_simple_callback_setter( + &layout_view::set_preload_callback, + nullptr, + "set_preload_callback", + "preload")); + layout_view_type.set_function( + "set_recomputed_callback", + make_simple_callback_setter( + &layout_view::set_recomputed_callback, + nullptr, + "set_recomputed_callback", + "recomputed")); + layout_view_type.set_function( + "set_pointer_updated_callback", + make_simple_callback_setter( + &layout_view::set_pointer_updated_callback, + nullptr, + "set_pointer_updated_callback", + "pointer updated")); + layout_view_type.set_function( + "set_pointer_left_callback", + make_simple_callback_setter( + &layout_view::set_pointer_left_callback, + nullptr, + "set_pointer_left_callback", + "pointer left")); + layout_view_type.set_function( + "set_pointer_aborted_callback", + make_simple_callback_setter( + &layout_view::set_pointer_aborted_callback, + nullptr, + "set_pointer_aborted_callback", + "pointer aborted")); + layout_view_type.set_function( + "set_forget_pointers_callback", + make_simple_callback_setter( + &layout_view::set_forget_pointers_callback, + nullptr, + "set_forget_pointers_callback", + "forget pointers")); + layout_view_type["items"] = sol::property([] (layout_view &v) { return layout_view_items(v); }); + layout_view_type["name"] = sol::property(&layout_view::name); + layout_view_type["unqualified_name"] = sol::property(&layout_view::unqualified_name); + layout_view_type["visible_screen_count"] = sol::property(&layout_view::visible_screen_count); + layout_view_type["effective_aspect"] = sol::property(&layout_view::effective_aspect); + layout_view_type["bounds"] = sol::property(&layout_view::bounds); + layout_view_type["has_art"] = sol::property(&layout_view::has_art); + layout_view_type["show_pointers"] = sol::property(&layout_view::show_pointers, &layout_view::set_show_pointers); + layout_view_type["hide_inactive_pointers"] = sol::property(&layout_view::hide_inactive_pointers, &layout_view::set_hide_inactive_pointers); + + + auto layout_view_item_type = sol().registry().new_usertype<layout_view_item>("layout_item", sol::no_constructor); + layout_view_item_type.set_function("set_state", &layout_view_item::set_state); + layout_view_item_type.set_function( + "set_element_state_callback", + make_simple_callback_setter( + &layout_view_item::set_element_state_callback, + [] () { return 0; }, + "set_element_state_callback", + "element state")); + layout_view_item_type.set_function( + "set_animation_state_callback", + make_simple_callback_setter( + &layout_view_item::set_animation_state_callback, + [] () { return 0; }, + "set_animation_state_callback", + "animation state")); + layout_view_item_type.set_function( + "set_bounds_callback", + make_simple_callback_setter( + &layout_view_item::set_bounds_callback, + [] () { return render_bounds{ 0.0f, 0.0f, 1.0f, 1.0f }; }, + "set_bounds_callback", + "bounds")); + layout_view_item_type.set_function( + "set_color_callback", + make_simple_callback_setter( + &layout_view_item::set_color_callback, + [] () { return render_color{ 1.0f, 1.0f, 1.0f, 1.0f }; }, + "set_color_callback", + "color")); + layout_view_item_type.set_function( + "set_scroll_size_x_callback", + make_simple_callback_setter( + &layout_view_item::set_scroll_size_x_callback, + [] () { return 1.0f; }, + "set_scroll_size_x_callback", + "horizontal scroll window size")); + layout_view_item_type.set_function( + "set_scroll_size_y_callback", + make_simple_callback_setter( + &layout_view_item::set_scroll_size_y_callback, + [] () { return 1.0f; }, + "set_scroll_size_y_callback", + "vertical scroll window size")); + layout_view_item_type.set_function( + "set_scroll_pos_x_callback", + make_simple_callback_setter( + &layout_view_item::set_scroll_pos_x_callback, + [] () { return 1.0f; }, + "set_scroll_pos_x_callback", + "horizontal scroll position")); + layout_view_item_type.set_function( + "set_scroll_pos_y_callback", + make_simple_callback_setter( + &layout_view_item::set_scroll_pos_y_callback, + [] () { return 1.0f; }, + "set_scroll_pos_y_callback", + "vertical scroll position")); + layout_view_item_type["id"] = sol::property( + [] (layout_view_item &i, sol::this_state s) -> sol::object + { + if (i.id().empty()) + return sol::lua_nil; + else + return sol::make_object(s, i.id()); + }); + layout_view_item_type["element"] = sol::property(&layout_view_item::element); + layout_view_item_type["bounds_animated"] = sol::property(&layout_view_item::bounds_animated); + layout_view_item_type["color_animated"] = sol::property(&layout_view_item::color_animated); + layout_view_item_type["bounds"] = sol::property(&layout_view_item::bounds); + layout_view_item_type["color"] = sol::property(&layout_view_item::color); + layout_view_item_type["scroll_wrap_x"] = sol::property(&layout_view_item::scroll_wrap_x); + layout_view_item_type["scroll_wrap_y"] = sol::property(&layout_view_item::scroll_wrap_y); + layout_view_item_type["scroll_size_x"] = sol::property( + &layout_view_item::scroll_size_x, + &layout_view_item::set_scroll_size_x); + layout_view_item_type["scroll_size_y"] = sol::property( + &layout_view_item::scroll_size_y, + &layout_view_item::set_scroll_size_y); + layout_view_item_type["scroll_pos_x"] = sol::property( + &layout_view_item::scroll_pos_x, + &layout_view_item::set_scroll_pos_y); + layout_view_item_type["scroll_pos_y"] = sol::property( + &layout_view_item::scroll_pos_y, + &layout_view_item::set_scroll_pos_y); + layout_view_item_type["blend_mode"] = sol::property(&layout_view_item::blend_mode); + layout_view_item_type["orientation"] = sol::property(&layout_view_item::orientation); + layout_view_item_type["element_state"] = sol::property(&layout_view_item::element_state); + layout_view_item_type["animation_state"] = sol::property(&layout_view_item::animation_state); + + + auto layout_file_type = sol().registry().new_usertype<layout_file>("layout_file", sol::no_constructor); + layout_file_type["set_resolve_tags_callback"] = + make_simple_callback_setter( + &layout_file::set_resolve_tags_callback, + nullptr, + "set_resolve_tags_callback", + "resolve tags"); + layout_file_type["device"] = sol::property(&layout_file::device); + layout_file_type["elements"] = sol::property([] (layout_file &f) { return layout_file_elements(f); }); + layout_file_type["views"] = sol::property([] (layout_file &f) { return layout_file_views(f); }); + + + auto target_type = sol().registry().new_usertype<render_target>("target", sol::no_constructor); + target_type["ui_container"] = sol::property(&render_target::ui_container); + target_type["index"] = sol::property([] (render_target const &t) { return t.index() + 1; }); + target_type["width"] = sol::property(&render_target::width); + target_type["height"] = sol::property(&render_target::height); + target_type["pixel_aspect"] = sol::property(&render_target::pixel_aspect); + target_type["hidden"] = sol::property(&render_target::hidden); + target_type["is_ui_target"] = sol::property(&render_target::is_ui_target); + target_type["max_update_rate"] = sol::property(&render_target::max_update_rate, &render_target::set_max_update_rate); + target_type["orientation"] = sol::property(&render_target::orientation, &render_target::set_orientation); + target_type["view_names"] = sol::property([] (render_target &t) { return render_target_view_names(t); }); + target_type["current_view"] = sol::property(&render_target::current_view); + target_type["view_index"] = sol::property( + [] (render_target const &t) { return t.view() + 1; }, + [] (render_target &t, unsigned v) { t.set_view(v - 1); }); + target_type["visibility_mask"] = sol::property(&render_target::visibility_mask); + target_type["screen_overlay"] = sol::property(&render_target::screen_overlay_enabled, &render_target::set_screen_overlay_enabled); + target_type["zoom_to_screen"] = sol::property(&render_target::zoom_to_screen, &render_target::set_zoom_to_screen); + + + auto render_container_type = sol().registry().new_usertype<render_container>("render_container", sol::no_constructor); + render_container_type.set_function( + "draw_box", + [] (render_container &ctnr, float x1, float y1, float x2, float y2, std::optional<uint32_t> fgcolor, std::optional<uint32_t> bgcolor) + { + x1 = std::clamp(x1, 0.0f, 1.0f); + y1 = std::clamp(y1, 0.0f, 1.0f); + x2 = std::clamp(x2, 0.0f, 1.0f); + y2 = std::clamp(y2, 0.0f, 1.0f); + mame_ui_manager &ui(mame_machine_manager::instance()->ui()); + if (!fgcolor) + fgcolor = ui.colors().text_color(); + if (!bgcolor) + bgcolor = ui.colors().background_color(); + ui.draw_outlined_box(ctnr, x1, y1, x2, y2, *fgcolor, *bgcolor); + }); + render_container_type.set_function( + "draw_line", + [] (render_container &ctnr, float x1, float y1, float x2, float y2, std::optional<uint32_t> color) + { + x1 = std::clamp(x1, 0.0f, 1.0f); + y1 = std::clamp(y1, 0.0f, 1.0f); + x2 = std::clamp(x2, 0.0f, 1.0f); + y2 = std::clamp(y2, 0.0f, 1.0f); + if (!color) + color = mame_machine_manager::instance()->ui().colors().text_color(); + ctnr.add_line(x1, y1, x2, y2, UI_LINE_WIDTH, rgb_t(*color), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + }); + render_container_type.set_function( + "draw_quad", + [] (render_container &cntr, render_texture_helper const &tex, float x1, float y1, float x2, float y2, std::optional<uint32_t> color) + { + cntr.add_quad(x1, y1, x2, y2, color ? *color : uint32_t(0xffffffff), tex.texture, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + }); + render_container_type.set_function( + "draw_text", + [] (render_container &ctnr, sol::this_state s, sol::object xobj, float y, char const *msg, std::optional<uint32_t> fgcolor, std::optional<uint32_t> bgcolor) + { + auto justify = ui::text_layout::text_justify::LEFT; + float x = 0; + if (xobj.is<float>()) + { + x = std::clamp(xobj.as<float>(), 0.0f, 1.0f); + } + else if (xobj.is<char const *>()) + { + char const *const justifystr(xobj.as<char const *>()); + if (!strcmp(justifystr, "left")) + justify = ui::text_layout::text_justify::LEFT; + else if (!strcmp(justifystr, "right")) + justify = ui::text_layout::text_justify::RIGHT; + else if (!strcmp(justifystr, "center")) + justify = ui::text_layout::text_justify::CENTER; + } + else + { + luaL_error(s, "Error in param 1 to draw_text"); + return; + } + y = std::clamp(y, 0.0f, 1.0f); + mame_ui_manager &ui(mame_machine_manager::instance()->ui()); + if (!fgcolor) + fgcolor = ui.colors().text_color(); + if (!bgcolor) + bgcolor = 0; + ui.draw_text_full( + ctnr, + msg, + x, y, (1.0f - x), + justify, ui::text_layout::word_wrapping::WORD, + mame_ui_manager::OPAQUE_, *fgcolor, *bgcolor); + }); + render_container_type["user_settings"] = sol::property(&render_container::get_user_settings, &render_container::set_user_settings); + render_container_type["orientation"] = sol::property( + &render_container::orientation, + [] (render_container &c, int v) + { + render_container::user_settings s(c.get_user_settings()); + s.m_orientation = v; + c.set_user_settings(s); + }); + render_container_type["xscale"] = sol::property( + &render_container::xscale, + [] (render_container &c, float v) + { + render_container::user_settings s(c.get_user_settings()); + s.m_xscale = v; + c.set_user_settings(s); + }); + render_container_type["yscale"] = sol::property( + &render_container::yscale, + [] (render_container &c, float v) + { + render_container::user_settings s(c.get_user_settings()); + s.m_yscale = v; + c.set_user_settings(s); + }); + render_container_type["xoffset"] = sol::property( + &render_container::xoffset, + [] (render_container &c, float v) + { + render_container::user_settings s(c.get_user_settings()); + s.m_xoffset = v; + c.set_user_settings(s); + }); + render_container_type["yoffset"] = sol::property( + &render_container::yoffset, + [] (render_container &c, float v) + { + render_container::user_settings s(c.get_user_settings()); + s.m_yoffset = v; + c.set_user_settings(s); + }); + render_container_type["is_empty"] = sol::property(&render_container::is_empty); + + + auto user_settings_type = sol().registry().new_usertype<render_container::user_settings>("render_container_settings", sol::no_constructor); + user_settings_type["orientation"] = &render_container::user_settings::m_orientation; + user_settings_type["brightness"] = &render_container::user_settings::m_brightness; + user_settings_type["contrast"] = &render_container::user_settings::m_contrast; + user_settings_type["gamma"] = &render_container::user_settings::m_gamma; + user_settings_type["xscale"] = &render_container::user_settings::m_xscale; + user_settings_type["yscale"] = &render_container::user_settings::m_yscale; + user_settings_type["xoffset"] = &render_container::user_settings::m_xoffset; + user_settings_type["yoffset"] = &render_container::user_settings::m_yoffset; + + + auto render_type = sol().registry().new_usertype<render_manager>("render", sol::no_constructor); + render_type["texture_alloc"] = sol::overload( + [] (render_manager &manager, sol::this_state s, bitmap_helper<bitmap_ind16>::ptr const &bitmap) + { + return render_texture_helper(s, manager, bitmap, TEXFORMAT_PALETTE16); + }, + [] (render_manager &manager, sol::this_state s, bitmap_helper<bitmap_yuy16>::ptr const &bitmap) + { + return render_texture_helper(s, manager, bitmap, TEXFORMAT_YUY16); + }, + [] (render_manager &manager, sol::this_state s, bitmap_helper<bitmap_rgb32>::ptr const &bitmap) + { + return render_texture_helper(s, manager, bitmap, TEXFORMAT_RGB32); + }, + [] (render_manager &manager, sol::this_state s, bitmap_helper<bitmap_argb32>::ptr const &bitmap) + { + return render_texture_helper(s, manager, bitmap, TEXFORMAT_ARGB32); + }); + render_type["max_update_rate"] = sol::property(&render_manager::max_update_rate); + render_type["ui_target"] = sol::property(&render_manager::ui_target); + render_type["ui_container"] = sol::property(&render_manager::ui_container); + render_type["targets"] = sol::property([] (render_manager &m) { return simple_list_wrapper<render_target>(m.targets()); }); + +} diff --git a/src/frontend/mame/mame.cpp b/src/frontend/mame/mame.cpp index e0d45e504c1..d732392a5c9 100644 --- a/src/frontend/mame/mame.cpp +++ b/src/frontend/mame/mame.cpp @@ -10,38 +10,47 @@ #include "emu.h" #include "mame.h" + +#include "ui/inifile.h" +#include "ui/selgame.h" +#include "ui/simpleselgame.h" +#include "ui/ui.h" + +#include "cheat.h" +#include "clifront.h" #include "emuopts.h" +#include "fileio.h" +#include "luaengine.h" #include "mameopts.h" #include "pluginopts.h" -#include "osdepend.h" +#include "rendlay.h" #include "validity.h" -#include "clifront.h" -#include "luaengine.h" -#include <time.h> -#include "ui/ui.h" -#include "ui/selgame.h" -#include "ui/simpleselgame.h" -#include "cheat.h" -#include "ui/inifile.h" + +#include "corestr.h" #include "xmlfile.h" +#include "osdepend.h" + +#include <ctime> + + //************************************************************************** // MACHINE MANAGER //************************************************************************** -mame_machine_manager* mame_machine_manager::m_manager = nullptr; +mame_machine_manager *mame_machine_manager::s_manager = nullptr; mame_machine_manager* mame_machine_manager::instance(emu_options &options, osd_interface &osd) { - if (!m_manager) - m_manager = global_alloc(mame_machine_manager(options, osd)); + if (!s_manager) + s_manager = new mame_machine_manager(options, osd); - return m_manager; + return s_manager; } mame_machine_manager* mame_machine_manager::instance() { - return m_manager; + return s_manager; } //------------------------------------------------- @@ -51,7 +60,7 @@ mame_machine_manager* mame_machine_manager::instance() mame_machine_manager::mame_machine_manager(emu_options &options,osd_interface &osd) : machine_manager(options, osd), m_plugins(std::make_unique<plugin_options>()), - m_lua(global_alloc(lua_engine)), + m_lua(std::make_unique<lua_engine>()), m_new_driver_pending(nullptr), m_firstrun(true), m_autoboot_timer(nullptr) @@ -65,8 +74,9 @@ mame_machine_manager::mame_machine_manager(emu_options &options,osd_interface &o mame_machine_manager::~mame_machine_manager() { - global_free(m_lua); - m_manager = nullptr; + m_autoboot_script.reset(); + m_lua.reset(); + s_manager = nullptr; } @@ -133,10 +143,7 @@ void mame_machine_manager::start_luaengine() std::string pluginpath; while (iter.next(pluginpath)) { - // user may specify environment variables; subsitute them - osd_subst_env(pluginpath, pluginpath); - - // and then scan the directory recursively + // scan the directory recursively m_plugins->scan_directory(pluginpath, true); } @@ -144,7 +151,7 @@ void mame_machine_manager::start_luaengine() // parse the file // attempt to open the output file emu_file file(options().ini_path(), OPEN_FLAG_READ); - if (file.open("plugin.ini") == osd_file::error::NONE) + if (!file.open("plugin.ini")) { try { @@ -160,18 +167,18 @@ void mame_machine_manager::start_luaengine() // process includes for (const std::string &incl : split(options().plugin(), ',')) { - plugin *p = m_plugins->find(incl); + plugin_options::plugin *p = m_plugins->find(incl); if (!p) - fatalerror("Fatal error: Could not load plugin: %s\n", incl.c_str()); + fatalerror("Fatal error: Could not load plugin: %s\n", incl); p->m_start = true; } // process excludes for (const std::string &excl : split(options().no_plugin(), ',')) { - plugin *p = m_plugins->find(excl); + plugin_options::plugin *p = m_plugins->find(excl); if (!p) - fatalerror("Fatal error: Unknown plugin: %s\n", excl.c_str()); + fatalerror("Fatal error: Unknown plugin: %s\n", excl); p->m_start = false; } } @@ -179,7 +186,7 @@ void mame_machine_manager::start_luaengine() // we have a special way to open the console plugin if (options().console()) { - plugin *p = m_plugins->find(OPTION_CONSOLE); + plugin_options::plugin *p = m_plugins->find(OPTION_CONSOLE); if (!p) fatalerror("Fatal error: Console plugin not found.\n"); @@ -190,12 +197,32 @@ void mame_machine_manager::start_luaengine() { emu_file file(options().plugins_path(), OPEN_FLAG_READ); - osd_file::error filerr = file.open("boot.lua"); - if (filerr == osd_file::error::NONE) + std::error_condition const filerr = file.open("boot.lua"); + if (!filerr) { - std::string exppath; - osd_subst_env(exppath, std::string(file.fullpath())); - m_lua->load_script(exppath.c_str()); + const std::string exppath = file.fullpath(); + auto &l(*lua()); + auto load_result = l.load_script(exppath); + if (!load_result.valid()) + { + sol::error err = load_result; + sol::load_status status = load_result.status(); + fatalerror("Error plugin bootstrap script %s: %s error\n%s\n", + exppath, + sol::to_string(status), + err.what()); + } + sol::protected_function func = load_result; + sol::protected_function_result call_result = l.invoke(func); + if (!call_result.valid()) + { + sol::error err = call_result; + sol::call_status status = call_result.status(); + fatalerror("Error running plugin bootstrap script %s: %s error\n%s\n", + options().autoboot_script(), + sol::to_string(status), + err.what()); + } } } } @@ -237,14 +264,14 @@ int mame_machine_manager::execute() m_options.revert(OPTION_PRIORITY_INI); std::ostringstream errors; - mame_options::parse_standard_inis(m_options, errors); + mame_options::parse_standard_inis(m_options, errors, system); } // otherwise, perform validity checks before anything else bool is_empty = (system == &GAME_NAME(___empty)); if (!is_empty) { - validity_checker valid(m_options); + validity_checker valid(m_options, true); valid.set_verbose(false); valid.check_shared_source(*system); } @@ -271,7 +298,10 @@ int mame_machine_manager::execute() else { if (machine.exit_pending()) + { m_options.set_system_name(""); + m_options.set_value(OPTION_BIOS, "", OPTION_PRIORITY_CMDLINE); + } } if (machine.exit_pending() && (!started_empty || is_empty)) @@ -286,21 +316,35 @@ int mame_machine_manager::execute() TIMER_CALLBACK_MEMBER(mame_machine_manager::autoboot_callback) { - if (strlen(options().autoboot_script())!=0) { - mame_machine_manager::instance()->lua()->load_script(options().autoboot_script()); + if (*options().autoboot_script()) + { + assert(m_autoboot_script); + sol::protected_function func = *m_autoboot_script; + sol::protected_function_result result = lua()->invoke(func); + if (!result.valid()) + { + sol::error err = result; + sol::call_status status = result.status(); + fatalerror("Error running autoboot script %s: %s error\n%s\n", + options().autoboot_script(), + sol::to_string(status), + err.what()); + } } - else if (strlen(options().autoboot_command())!=0) { - std::string cmd = std::string(options().autoboot_command()); + else if (*options().autoboot_command()) + { + std::string cmd(options().autoboot_command()); strreplace(cmd, "'", "\\'"); std::string val = std::string("emu.keypost('").append(cmd).append("')"); - mame_machine_manager::instance()->lua()->load_string(val.c_str()); + auto &l(*lua()); + l.invoke(l.load_string(val).get<sol::protected_function>()); } } void mame_machine_manager::reset() { // setup autoboot if needed - m_autoboot_timer->adjust(attotime(options().autoboot_delay(),0),0); + m_autoboot_timer->adjust(attotime(options().autoboot_delay(), 0), 0); } ui_manager* mame_machine_manager::create_ui(running_machine& machine) @@ -310,8 +354,6 @@ ui_manager* mame_machine_manager::create_ui(running_machine& machine) machine.add_notifier(MACHINE_NOTIFY_RESET, machine_notify_delegate(&mame_machine_manager::reset, this)); - m_ui->set_startup_text("Initializing...", true); - return m_ui.get(); } @@ -328,7 +370,7 @@ void mame_machine_manager::before_load_settings(running_machine& machine) m_lua->on_machine_before_load_settings(); } -void mame_machine_manager::create_custom(running_machine& machine) +void mame_machine_manager::create_custom(running_machine &machine) { // start the inifile manager m_inifile = std::make_unique<inifile_manager>(m_ui->options()); @@ -338,6 +380,25 @@ void mame_machine_manager::create_custom(running_machine& machine) // start favorite manager m_favorite = std::make_unique<favorite_manager>(m_ui->options()); + + // attempt to load the autoboot script if configured + m_autoboot_script.reset(); + if (*options().autoboot_script()) + { + auto result = lua()->load_script(options().autoboot_script()); + if (!result.valid()) + { + sol::error err = result; + sol::load_status status = result.status(); + fatalerror("Error loading autoboot script %s: %s error\n%s\n", + options().autoboot_script(), + sol::to_string(status), + err.what()); + } + m_autoboot_script.reset(new sol::load_result(std::move(result))); + sol::protected_function func = *m_autoboot_script; + sol::set_environment(lua()->make_environment(), func); + } } void mame_machine_manager::load_cheatfiles(running_machine& machine) @@ -357,7 +418,7 @@ std::vector<std::reference_wrapper<const std::string>> mame_machine_manager::mis assert(m_machine); // make sure that any required image has a mounted file - for (device_image_interface &image : image_interface_iterator(m_machine->root_device())) + for (device_image_interface &image : image_interface_enumerator(m_machine->root_device())) { if (image.must_be_loaded()) { @@ -398,9 +459,9 @@ int emulator_info::start_frontend(emu_options &options, osd_interface &osd, int return start_frontend(options, osd, args); } -void emulator_info::draw_user_interface(running_machine& machine) +bool emulator_info::draw_user_interface(running_machine& machine) { - mame_machine_manager::instance()->ui().update_and_render(machine.render().ui_container()); + return mame_machine_manager::instance()->ui().update_and_render(machine.render().ui_container()); } void emulator_info::periodic_check() @@ -413,19 +474,25 @@ bool emulator_info::frame_hook() return mame_machine_manager::instance()->lua()->frame_hook(); } -void emulator_info::sound_hook() +void emulator_info::sound_hook(const std::map<std::string, std::vector<std::pair<const float *, int>>> &sound) { - return mame_machine_manager::instance()->lua()->on_sound_update(); + return mame_machine_manager::instance()->lua()->on_sound_update(sound); } -void emulator_info::layout_file_cb(util::xml::data_node const &layout) +void emulator_info::layout_script_cb(layout_file &file, const char *script) { - util::xml::data_node const *const mamelayout = layout.get_child("mamelayout"); - if (mamelayout) + // TODO: come up with a better way to pass multiple arguments to plugin + //mame_machine_manager::instance()->lua()->call_plugin_set("layout", std::make_tuple(&file, script->get_value())); + auto &lua(mame_machine_manager::instance()->lua()->sol()); + sol::object obj = lua.registry()["cb_layout"]; + if (obj.is<sol::protected_function>()) { - util::xml::data_node const *const script = mamelayout->get_child("script"); - if (script) - mame_machine_manager::instance()->lua()->call_plugin_set("layout", script->get_value()); + auto res = obj.as<sol::protected_function>()(sol::make_reference(lua, &file), sol::make_reference(lua, script)); + if (!res.valid()) + { + sol::error err = res; + osd_printf_error("[LUA ERROR] in call_plugin: %s\n", err.what()); + } } } diff --git a/src/frontend/mame/mame.h b/src/frontend/mame/mame.h index 041612de758..80bd0419a22 100644 --- a/src/frontend/mame/mame.h +++ b/src/frontend/mame/mame.h @@ -6,12 +6,20 @@ Controls execution of the core MAME system. ***************************************************************************/ - #ifndef MAME_FRONTEND_MAME_MAME_H #define MAME_FRONTEND_MAME_MAME_H #pragma once +#include "main.h" + + +namespace sol { + +struct load_result; + +} // namespace sol + class plugin_options; class osd_interface; @@ -35,7 +43,7 @@ public: ~mame_machine_manager(); plugin_options &plugins() const { return *m_plugins; } - lua_engine *lua() { return m_lua; } + lua_engine *lua() { return m_lua.get(); } virtual void update_machine() override; @@ -71,19 +79,20 @@ private: mame_machine_manager &operator=(mame_machine_manager const &) = delete; mame_machine_manager &operator=(mame_machine_manager &&) = delete; - std::unique_ptr<plugin_options> m_plugins; // pointer to plugin options - lua_engine * m_lua; + std::unique_ptr<plugin_options> m_plugins; // pointer to plugin options + std::unique_ptr<lua_engine> m_lua; - const game_driver * m_new_driver_pending; // pointer to the next pending driver + const game_driver * m_new_driver_pending; // pointer to the next pending driver bool m_firstrun; - static mame_machine_manager* m_manager; - emu_timer *m_autoboot_timer; // autoboot timer - std::unique_ptr<mame_ui_manager> m_ui; // internal data from ui.cpp - std::unique_ptr<cheat_manager> m_cheat; // internal data from cheat.cpp - std::unique_ptr<inifile_manager> m_inifile; // internal data from inifile.c for INIs - std::unique_ptr<favorite_manager> m_favorite; // internal data from inifile.c for favorites + emu_timer * m_autoboot_timer; // auto-boot timer + std::unique_ptr<sol::load_result> m_autoboot_script; // auto-boot script + std::unique_ptr<mame_ui_manager> m_ui; // internal data from ui.cpp + std::unique_ptr<cheat_manager> m_cheat; // internal data from cheat.cpp + std::unique_ptr<inifile_manager> m_inifile; // internal data from inifile.c for INIs + std::unique_ptr<favorite_manager> m_favorite; // internal data from inifile.c for favorites + static mame_machine_manager *s_manager; }; //************************************************************************** @@ -92,5 +101,6 @@ private: extern const char build_version[]; extern const char bare_build_version[]; +extern const char bare_vcs_revision[]; #endif // MAME_FRONTEND_MAME_MAME_H diff --git a/src/frontend/mame/mameopts.cpp b/src/frontend/mame/mameopts.cpp index 1ab0b81874f..cd754fcb0f3 100644 --- a/src/frontend/mame/mameopts.cpp +++ b/src/frontend/mame/mameopts.cpp @@ -11,14 +11,21 @@ #include "emu.h" #include "mameopts.h" +#include "clifront.h" + +// emu #include "drivenum.h" +#include "fileio.h" +#include "hashfile.h" +#include "main.h" #include "screen.h" #include "softlist_dev.h" + +// lib/util +#include "path.h" #include "zippath.h" -#include "hashfile.h" -#include "clifront.h" -#include <ctype.h> +#include <cctype> #include <stack> @@ -43,55 +50,40 @@ void mame_options::parse_standard_inis(emu_options &options, std::ostream &error if (!cursystem) return; - // parse "vertical.ini" or "horizont.ini" - if (cursystem->flags & ORIENTATION_SWAP_XY) - parse_one_ini(options, "vertical", OPTION_PRIORITY_ORIENTATION_INI, &error_stream); - else - parse_one_ini(options, "horizont", OPTION_PRIORITY_ORIENTATION_INI, &error_stream); - - switch (cursystem->flags & machine_flags::MASK_TYPE) + if (&GAME_NAME(___empty) != cursystem) // hacky - this thing isn't a real system { - case machine_flags::TYPE_ARCADE: - parse_one_ini(options, "arcade", OPTION_PRIORITY_SYSTYPE_INI, &error_stream); - break; - case machine_flags::TYPE_CONSOLE: - parse_one_ini(options ,"console", OPTION_PRIORITY_SYSTYPE_INI, &error_stream); - break; - case machine_flags::TYPE_COMPUTER: - parse_one_ini(options, "computer", OPTION_PRIORITY_SYSTYPE_INI, &error_stream); - break; - case machine_flags::TYPE_OTHER: - parse_one_ini(options, "othersys", OPTION_PRIORITY_SYSTYPE_INI, &error_stream); - break; - default: - break; - } - - machine_config config(*cursystem, options); - for (const screen_device &device : screen_device_iterator(config.root_device())) - { - // parse "raster.ini" for raster games - if (device.screen_type() == SCREEN_TYPE_RASTER) - { - parse_one_ini(options, "raster", OPTION_PRIORITY_SCREEN_INI, &error_stream); - break; - } - // parse "vector.ini" for vector games - if (device.screen_type() == SCREEN_TYPE_VECTOR) - { - parse_one_ini(options, "vector", OPTION_PRIORITY_SCREEN_INI, &error_stream); - break; - } - // parse "lcd.ini" for lcd games - if (device.screen_type() == SCREEN_TYPE_LCD) + // parse "vertical.ini" or "horizont.ini" + if (cursystem->flags & ORIENTATION_SWAP_XY) + parse_one_ini(options, "vertical", OPTION_PRIORITY_ORIENTATION_INI, &error_stream); + else + parse_one_ini(options, "horizont", OPTION_PRIORITY_ORIENTATION_INI, &error_stream); + + machine_config config(*cursystem, options); + for (const screen_device &device : screen_device_enumerator(config.root_device())) { - parse_one_ini(options, "lcd", OPTION_PRIORITY_SCREEN_INI, &error_stream); - break; + // parse "raster.ini" for raster games + if (device.screen_type() == SCREEN_TYPE_RASTER) + { + parse_one_ini(options, "raster", OPTION_PRIORITY_SCREEN_INI, &error_stream); + break; + } + // parse "vector.ini" for vector games + if (device.screen_type() == SCREEN_TYPE_VECTOR) + { + parse_one_ini(options, "vector", OPTION_PRIORITY_SCREEN_INI, &error_stream); + break; + } + // parse "lcd.ini" for lcd games + if (device.screen_type() == SCREEN_TYPE_LCD) + { + parse_one_ini(options, "lcd", OPTION_PRIORITY_SCREEN_INI, &error_stream); + break; + } } } // next parse "source/<sourcefile>.ini" - std::string sourcename = core_filename_extract_base(cursystem->type.source(), true).insert(0, "source" PATH_SEPARATOR); + std::string sourcename = std::string(core_filename_extract_base(cursystem->type.source(), true)).insert(0, "source" PATH_SEPARATOR); parse_one_ini(options, sourcename.c_str(), OPTION_PRIORITY_SOURCE_INI, &error_stream); // then parse the grandparent, parent, and system-specific INIs @@ -112,7 +104,7 @@ void mame_options::parse_standard_inis(emu_options &options, std::ostream &error const game_driver *mame_options::system(const emu_options &options) { - int index = driver_list::find(core_filename_extract_base(options.system_name(), true).c_str()); + int index = driver_list::find(std::string(core_filename_extract_base(options.system_name(), true)).c_str()); return (index != -1) ? &driver_list::driver(index) : nullptr; } @@ -130,8 +122,8 @@ void mame_options::parse_one_ini(emu_options &options, const char *basename, int // open the file; if we fail, that's ok emu_file file(options.ini_path(), OPEN_FLAG_READ); osd_printf_verbose("Attempting load of %s.ini\n", basename); - osd_file::error filerr = file.open(basename, ".ini"); - if (filerr != osd_file::error::NONE) + std::error_condition const filerr = file.open(std::string(basename) + ".ini"); + if (filerr) return; // parse the file diff --git a/src/frontend/mame/mameopts.h b/src/frontend/mame/mameopts.h index 2b178fb2ead..bc66245e6c1 100644 --- a/src/frontend/mame/mameopts.h +++ b/src/frontend/mame/mameopts.h @@ -31,7 +31,6 @@ enum OPTION_PRIORITY_MAME_INI = OPTION_PRIORITY_NORMAL + 1, OPTION_PRIORITY_DEBUG_INI, OPTION_PRIORITY_ORIENTATION_INI, - OPTION_PRIORITY_SYSTYPE_INI, OPTION_PRIORITY_SCREEN_INI, OPTION_PRIORITY_SOURCE_INI, OPTION_PRIORITY_GPARENT_INI, diff --git a/src/frontend/mame/media_ident.cpp b/src/frontend/mame/media_ident.cpp index d15bee95dae..d09858198ea 100644 --- a/src/frontend/mame/media_ident.cpp +++ b/src/frontend/mame/media_ident.cpp @@ -2,7 +2,7 @@ // copyright-holders:Aaron Giles /*************************************************************************** - media_ident.c + media_ident.cpp Media identify. @@ -11,10 +11,12 @@ #include "emu.h" #include "drivenum.h" #include "media_ident.h" -#include "unzip.h" -#include "jedparse.h" #include "softlist_dev.h" +#include "jedparse.h" +#include "path.h" +#include "unzip.h" + //************************************************************************** // MEDIA IDENTIFIER @@ -47,7 +49,7 @@ void media_identifier::file_info::match( m_matches.emplace_back( util::string_format("%s:%s", list, software.shortname()), std::string(software.longname()), - ROM_GETNAME(&rom), + std::string(rom.name()), hashes.flag(util::hash_collection::FLAG_BAD_DUMP), false); } @@ -102,6 +104,8 @@ void media_identifier::identify_file(const char *name) void media_identifier::identify_data(const char *name, const uint8_t *data, std::size_t length) { + assert(data != nullptr && length != 0); + std::vector<file_info> info; digest_data(info, name, data, length); match_hashes(info); @@ -130,17 +134,17 @@ void media_identifier::collect_files(std::vector<file_info> &info, char const *p } } } - else if (core_filename_ends_with(path, ".7z") || core_filename_ends_with(path, ".zip")) + else if (core_filename_ends_with(path, ".7z") || core_filename_ends_with(path, ".zip") || core_filename_ends_with(path, ".imz")) { // first attempt to examine it as a valid zip/7z file util::archive_file::ptr archive; - util::archive_file::error err; + std::error_condition err; if (core_filename_ends_with(path, ".7z")) err = util::archive_file::open_7z(path, archive); else err = util::archive_file::open_zip(path, archive); - if ((util::archive_file::error::NONE == err) && archive) + if (!err && archive) { std::vector<std::uint8_t> data; @@ -158,21 +162,21 @@ void media_identifier::collect_files(std::vector<file_info> &info, char const *p { data.resize(std::size_t(length)); err = archive->decompress(&data[0], std::uint32_t(length)); - if (util::archive_file::error::NONE == err) + if (!err) digest_data(info, curfile.c_str(), &data[0], length); else - osd_printf_error("%s: error decompressing file\n", curfile.c_str()); + osd_printf_error("%s: error decompressing file (%s:%d %s)\n", curfile, err.category().name(), err.value(), err.message()); } catch (...) { // resizing the buffer could cause a bad_alloc if archive contains large files - osd_printf_error("%s: error decompressing file\n", curfile.c_str()); + osd_printf_error("%s: error decompressing file\n", curfile); } data.clear(); } else { - osd_printf_error("%s: file too large to decompress into memory\n", curfile.c_str()); + osd_printf_error("%s: file too large to decompress into memory\n", curfile); } } } @@ -205,16 +209,16 @@ void media_identifier::digest_file(std::vector<file_info> &info, char const *pat { // attempt to open as a CHD; fail if not chd_file chd; - chd_error const err = chd.open(path); + std::error_condition const err = chd.open(path); m_total++; - if (err != CHDERR_NONE) + if (err) { - osd_printf_info("%-20sNOT A CHD\n", core_filename_extract_base(path).c_str()); + osd_printf_info("%-20s NOT A CHD\n", core_filename_extract_base(path)); m_nonroms++; } else if (!chd.compressed()) { - osd_printf_info("%-20sis a writeable CHD\n", core_filename_extract_base(path).c_str()); + osd_printf_info("%-20s is a writeable CHD\n", core_filename_extract_base(path)); } else { @@ -231,12 +235,11 @@ void media_identifier::digest_file(std::vector<file_info> &info, char const *pat if (core_filename_ends_with(path, ".jed")) { // load the file and process if it opens and has a valid length - uint32_t length; - void *data; - if (osd_file::error::NONE == util::core_file::load(path, &data, length)) + util::core_file::ptr file; + if (!util::core_file::open(path, OPEN_FLAG_READ, file)) { jed_data jed; - if (JEDERR_NONE == jed_parse(data, length, &jed)) + if (JEDERR_NONE == jed_parse(*file, &jed)) { try { @@ -246,7 +249,6 @@ void media_identifier::digest_file(std::vector<file_info> &info, char const *pat util::hash_collection hashes; hashes.compute(&tempjed[0], tempjed.size(), util::hash_collection::HASH_TYPES_CRC_SHA1); info.emplace_back(path, tempjed.size(), std::move(hashes), file_flavour::JED); - free(data); m_total++; return; } @@ -254,36 +256,34 @@ void media_identifier::digest_file(std::vector<file_info> &info, char const *pat { } } - free(data); } } // load the file and process if it opens and has a valid length util::core_file::ptr file; - if ((osd_file::error::NONE == util::core_file::open(path, OPEN_FLAG_READ, file)) && file) + std::error_condition err = util::core_file::open(path, OPEN_FLAG_READ, file); + if (err || !file) { - util::hash_collection hashes; - hashes.begin(util::hash_collection::HASH_TYPES_CRC_SHA1); - std::uint8_t buf[1024]; - for (std::uint64_t remaining = file->size(); remaining; ) - { - std::uint32_t const block = std::min<std::uint64_t>(remaining, sizeof(buf)); - if (file->read(buf, block) < block) - { - osd_printf_error("%s: error reading file\n", path); - return; - } - remaining -= block; - hashes.buffer(buf, block); - } - hashes.end(); - info.emplace_back(path, file->size(), std::move(hashes), file_flavour::RAW); - m_total++; + osd_printf_error("%s: error opening file (%s)\n", path, err ? err.message() : std::string("could not allocate pointer")); + return; } - else + std::uint64_t length; + err = file->length(length); + if (err) + { + osd_printf_error("%s: error getting file length (%s)\n", path, err.message()); + return; + } + util::hash_collection hashes; + std::size_t actual; + err = hashes.compute(*file, 0U, length, actual, util::hash_collection::HASH_TYPES_CRC_SHA1); + if (err) { - osd_printf_error("%s: error opening file\n", path); + osd_printf_error("%s: error reading file (%s)\n", path, err.message()); + return; } + info.emplace_back(path, length, std::move(hashes), file_flavour::RAW); + m_total++; } } @@ -301,7 +301,7 @@ void media_identifier::digest_data(std::vector<file_info> &info, char const *nam if (core_filename_ends_with(name, ".jed")) { jed_data jed; - if (JEDERR_NONE == jed_parse(data, length, &jed)) + if (JEDERR_NONE == jed_parse(*util::ram_read(data, length), &jed)) { try { @@ -332,73 +332,64 @@ void media_identifier::digest_data(std::vector<file_info> &info, char const *nam void media_identifier::match_hashes(std::vector<file_info> &info) { - std::unordered_set<std::string> listnames; + if (info.empty()) + return; - // iterate over drivers - m_drivlist.reset(); - while (m_drivlist.next()) - { - // iterate over regions and files within the region - device_t &device = m_drivlist.config()->root_device(); - for (romload::region const ®ion : romload::entries(device.rom_region()).get_regions()) - { - for (romload::file const &rom : region.get_files()) + auto match_device = + [&info, listnames = std::unordered_set<std::string>()] (device_t &device) mutable { - util::hash_collection const romhashes(rom.get_hashdata()); - if (!romhashes.flag(util::hash_collection::FLAG_NO_DUMP)) + // iterate over regions and files within the region + for (romload::region const ®ion : romload::entries(device.rom_region()).get_regions()) { - for (file_info &file : info) - file.match(device, rom, romhashes); + for (romload::file const &rom : region.get_files()) + { + util::hash_collection const romhashes(rom.get_hashdata()); + if (!romhashes.flag(util::hash_collection::FLAG_NO_DUMP)) + { + for (file_info &file : info) + file.match(device, rom, romhashes); + } + } } - } - } - // next iterate over softlists - for (software_list_device &swlistdev : software_list_device_iterator(device)) - { - if (listnames.insert(swlistdev.list_name()).second) - { - for (software_info const &swinfo : swlistdev.get_info()) + // next iterate over softlists + for (software_list_device &swlistdev : software_list_device_enumerator(device)) { - for (software_part const &part : swinfo.parts()) + if (!listnames.insert(swlistdev.list_name()).second) + continue; + + for (software_info const &swinfo : swlistdev.get_info()) { - for (rom_entry const *region = part.romdata().data(); region; region = rom_next_region(region)) + for (software_part const &part : swinfo.parts()) { - for (rom_entry const *rom = rom_first_file(region); rom; rom = rom_next_file(rom)) + for (rom_entry const *region = part.romdata().data(); region; region = rom_next_region(region)) { - util::hash_collection romhashes(ROM_GETHASHDATA(rom)); - if (!romhashes.flag(util::hash_collection::FLAG_NO_DUMP)) + for (rom_entry const *rom = rom_first_file(region); rom; rom = rom_next_file(rom)) { - for (file_info &file : info) - file.match(swlistdev.list_name(), swinfo, *rom, romhashes); + util::hash_collection romhashes(rom->hashdata()); + if (!romhashes.flag(util::hash_collection::FLAG_NO_DUMP)) + { + for (file_info &file : info) + file.match(swlistdev.list_name(), swinfo, *rom, romhashes); + } } } } } } - } - } - } + }; - // iterator over devices + // iterate over drivers + m_drivlist.reset(); + while (m_drivlist.next()) + match_device(m_drivlist.config()->root_device()); + + // iterator over registered device types machine_config config(GAME_NAME(___empty), m_drivlist.options()); machine_config::token const tok(config.begin_configuration(config.root_device())); for (device_type type : registered_device_types) { - // iterate over regions and files within the region - device_t *const device = config.device_add("_tmp", type, 0); - for (romload::region const ®ion : romload::entries(device->rom_region()).get_regions()) - { - for (romload::file const &rom : region.get_files()) - { - util::hash_collection const romhashes(rom.get_hashdata()); - if (!romhashes.flag(util::hash_collection::FLAG_NO_DUMP)) - { - for (file_info &file : info) - file.match(*device, rom, romhashes); - } - } - } + match_device(*config.device_add("_tmp", type, 0)); config.device_remove("_tmp"); } } @@ -413,7 +404,7 @@ void media_identifier::print_results(std::vector<file_info> const &info) { for (file_info const &file : info) { - osd_printf_info("%-20s", core_filename_extract_base(file.name()).c_str()); + osd_printf_info("%-20s ", core_filename_extract_base(file.name())); if (file.matches().empty()) { osd_printf_info("NO MATCH\n"); @@ -425,10 +416,10 @@ void media_identifier::print_results(std::vector<file_info> const &info) for (match_data const &match : file.matches()) { if (!first) - osd_printf_info("%-20s", ""); + osd_printf_info("%-20s ", ""); first = false; osd_printf_info( - "= %s%-20s %-10s %s%s\n", + "= %s%-20s %-10s %s%s\n", match.bad() ? "(BAD) " : "", match.romname().c_str(), match.shortname().c_str(), diff --git a/src/frontend/mame/media_ident.h b/src/frontend/mame/media_ident.h index 3430e02cb0b..dbdeec126d9 100644 --- a/src/frontend/mame/media_ident.h +++ b/src/frontend/mame/media_ident.h @@ -10,7 +10,9 @@ #ifndef MAME_FRONTEND_MEDIA_IDENT_H #define MAME_FRONTEND_MEDIA_IDENT_H +#include "drivenum.h" #include "romload.h" + #include <vector> diff --git a/src/frontend/mame/pluginopts.cpp b/src/frontend/mame/pluginopts.cpp index 5bbddaf1473..d06bb4ce49b 100644 --- a/src/frontend/mame/pluginopts.cpp +++ b/src/frontend/mame/pluginopts.cpp @@ -10,7 +10,9 @@ #include "emu.h" #include "pluginopts.h" + #include "options.h" +#include "path.h" #include <rapidjson/document.h> #include <rapidjson/error/en.h> @@ -47,13 +49,12 @@ void plugin_options::scan_directory(const std::string &path, bool recursive) { if (entry->type == osd::directory::entry::entry_type::FILE && !strcmp(entry->name, "plugin.json")) { - std::string curfile = std::string(path).append(PATH_SEPARATOR).append(entry->name); - load_plugin(curfile); + load_plugin(util::path_concat(path, entry->name)); } else if (entry->type == osd::directory::entry::entry_type::DIR) { if (recursive && strcmp(entry->name, ".") && strcmp(entry->name, "..")) - scan_directory(path + PATH_SEPARATOR + entry->name, recursive); + scan_directory(util::path_concat(path, entry->name), recursive); } } } @@ -73,15 +74,14 @@ bool plugin_options::load_plugin(const std::string &path) if (document.HasParseError()) { - std::string error(GetParseError_En(document.GetParseError())); - osd_printf_error("Unable to parse plugin definition file %s. Errors returned:\n", path.c_str()); - osd_printf_error("%s\n", error.c_str()); + const std::string error(GetParseError_En(document.GetParseError())); + osd_printf_error("Unable to parse plugin definition file %s. Errors returned:\n%s", path, error); return false; } if (!document["plugin"].IsObject()) { - osd_printf_error("Bad plugin definition file %s:\n", path.c_str()); + osd_printf_error("Bad plugin definition file %s:\n", path); return false; } @@ -108,7 +108,7 @@ bool plugin_options::load_plugin(const std::string &path) // find //------------------------------------------------- -plugin *plugin_options::find(const std::string &name) +plugin_options::plugin *plugin_options::find(const std::string &name) { auto iter = std::find_if( m_plugins.begin(), @@ -132,7 +132,7 @@ static core_options create_core_options(const plugin_options &plugin_opts) // the data back static const options_entry s_option_entries[] = { - { nullptr, nullptr, OPTION_HEADER, "PLUGINS OPTIONS" }, + { nullptr, nullptr, core_options::option_type::HEADER, "PLUGINS OPTIONS" }, { nullptr } }; @@ -140,13 +140,16 @@ static core_options create_core_options(const plugin_options &plugin_opts) opts.add_entries(s_option_entries); // create an entry for each option - for (const plugin &p : plugin_opts.plugins()) + for (const plugin_options::plugin &p : plugin_opts.plugins()) { - opts.add_entry( - { p.m_name }, - nullptr, - core_options::option_type::BOOLEAN, - p.m_start ? "1" : "0"); + if (p.m_type != "library") + { + opts.add_entry( + { p.m_name }, + nullptr, + core_options::option_type::BOOLEAN, + p.m_start ? "1" : "0"); + } } return opts; @@ -166,7 +169,7 @@ void plugin_options::parse_ini_file(util::core_file &inifile) // and reflect these options back for (plugin &p : m_plugins) - p.m_start = opts.bool_value(p.m_name.c_str()); + p.m_start = opts.bool_value(p.m_name); } diff --git a/src/frontend/mame/pluginopts.h b/src/frontend/mame/pluginopts.h index 3565f5d9e78..41c688b2ab6 100644 --- a/src/frontend/mame/pluginopts.h +++ b/src/frontend/mame/pluginopts.h @@ -17,23 +17,20 @@ #include <string> -// ======================> plugin - -struct plugin -{ - std::string m_name; - std::string m_description; - std::string m_type; - std::string m_directory; - bool m_start; -}; - - // ======================> plugin_options class plugin_options { public: + struct plugin + { + std::string m_name; + std::string m_description; + std::string m_type; + std::string m_directory; + bool m_start; + }; + plugin_options(); // accessors diff --git a/src/frontend/mame/ui/about.cpp b/src/frontend/mame/ui/about.cpp new file mode 100644 index 00000000000..7b737273c3a --- /dev/null +++ b/src/frontend/mame/ui/about.cpp @@ -0,0 +1,123 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + ui/about.cpp + + About box + +***************************************************************************/ + +#include "emu.h" +#include "ui/about.h" + +#include "ui/ui.h" + +#include "mame.h" + + +namespace ui { + +namespace { + +#include "copying.ipp" + +} // anonymous namespace + + +/************************************************** + ABOUT BOX +**************************************************/ + + +//------------------------------------------------- +// ctor +//------------------------------------------------- + +menu_about::menu_about(mame_ui_manager &mui, render_container &container) + : menu_textbox(mui, container) + , m_header{ + util::string_format( +#ifdef MAME_DEBUG + _("about-header", "%1$s %2$s (%3$s%4$sP%5$s, debug)"), +#else + _("about-header", "%1$s %2$s (%3$s%4$sP%5$s)"), +#endif + emulator_info::get_appname(), + bare_build_version, + (sizeof(int) == sizeof(void *)) ? "I" : "", + (sizeof(long) == sizeof(void *)) ? "L" : (sizeof(long long) == sizeof(void *)) ? "LL" : "", + sizeof(void *) * 8), + util::string_format(_("about-header", "Revision: %1$s"), bare_vcs_revision) } +{ + set_process_flags(PROCESS_CUSTOM_NAV); +} + + +//------------------------------------------------- +// dtor +//------------------------------------------------- + +menu_about::~menu_about() +{ +} + + +//------------------------------------------------- +// recompute metrics +//------------------------------------------------- + +void menu_about::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + menu_textbox::recompute_metrics(width, height, aspect); + + // make space for the title and revision + set_custom_space((line_height() * m_header.size()) + (tb_border() * 3.0F), 0.0F); +} + + +//------------------------------------------------- +// perform our special rendering +//------------------------------------------------- + +void menu_about::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +{ + // draw the title + draw_text_box( + std::begin(m_header), std::end(m_header), + origx1, origx2, origy1 - top, origy1 - tb_border(), + text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, false, + ui().colors().text_color(), UI_GREEN_COLOR); +} + + +//------------------------------------------------- +// populate_text - populate the about box text +//------------------------------------------------- + +void menu_about::populate_text(std::optional<text_layout> &layout, float &width, int &lines) +{ + if (!layout || (layout->width() != width)) + { + rgb_t const color = ui().colors().text_color(); + layout.emplace(create_layout(width)); + for (char const *const *line = copying_text; *line; ++line) + { + layout->add_text(*line, color); + layout->add_text("\n", color); + } + lines = layout->lines(); + } + width = layout->actual_width(); +} + + +//------------------------------------------------- +// populate - populates the about modal +//------------------------------------------------- + +void menu_about::populate() +{ +} + +} // namespace ui diff --git a/src/frontend/mame/ui/about.h b/src/frontend/mame/ui/about.h new file mode 100644 index 00000000000..05026b318f6 --- /dev/null +++ b/src/frontend/mame/ui/about.h @@ -0,0 +1,45 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + ui/about.h + + About box + +***************************************************************************/ +#ifndef MAME_FRONTEND_UI_ABOUT_H +#define MAME_FRONTEND_UI_ABOUT_H + +#pragma once + +#include "ui/text.h" +#include "ui/textbox.h" + +#include <optional> +#include <string> +#include <vector> + + +namespace ui { + +class menu_about : public menu_textbox +{ +public: + menu_about(mame_ui_manager &mui, render_container &container); + virtual ~menu_about() override; + +protected: + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) override; + + virtual void populate_text(std::optional<text_layout> &layout, float &width, int &lines) override; + +private: + virtual void populate() override; + + std::vector<std::string> const m_header; +}; + +} // namespace ui + +#endif // MAME_FRONTEND_UI_ABOUT_H diff --git a/src/frontend/mame/ui/analogipt.cpp b/src/frontend/mame/ui/analogipt.cpp new file mode 100644 index 00000000000..b126d22cfec --- /dev/null +++ b/src/frontend/mame/ui/analogipt.cpp @@ -0,0 +1,811 @@ +// license:BSD-3-Clause +// copyright-holders:Nicola Salmoria, Aaron Giles, Nathan Woods, Vas Crabb +/********************************************************************* + + ui/analogipt.cpp + + Analog inputs menu. + +*********************************************************************/ + +#include "emu.h" +#include "ui/analogipt.h" + +#include "ui/textbox.h" + +#include "uiinput.h" + +#include <algorithm> +#include <iterator> +#include <string> +#include <utility> + + +namespace ui { + +namespace { + +char const HELP_TEXT[] = N_p("menu-analoginput", + "Show/hide settings \t\t%1$s\n" + "Decrease value \t\t%2$s\n" + "Increase value \t\t%3$s\n" + "Restore default value \t\t%4$s\n" + "Previous device \t\t%5$s\n" + "Next device \t\t%6$s\n" + "Return to previous menu \t\t%7$s"); + +} // anonymous namespace + + +inline menu_analog::item_data::item_data(ioport_field &f, int t) noexcept + : field(f) + , type(t) + , defvalue( + (ANALOG_ITEM_KEYSPEED == t) ? f.delta() : + (ANALOG_ITEM_CENTERSPEED == t) ? f.centerdelta() : + (ANALOG_ITEM_REVERSE == t) ? f.analog_reverse() : + (ANALOG_ITEM_SENSITIVITY == t) ? f.sensitivity() : + -1) + , min((ANALOG_ITEM_SENSITIVITY == t) ? 1 : 0) + , max((ANALOG_ITEM_REVERSE == t) ? 1 : std::max(defvalue * 4, 255)) + , cur(-1) +{ +} + + +inline menu_analog::field_data::field_data(ioport_field &f) noexcept + : field(f) + , range(0.0F) + , neutral(0.0F) + , origin(0.0F) + , shift(0U) + , show_neutral((f.defvalue() != f.minval()) && (f.defvalue() != f.maxval())) +{ + for (ioport_value m = f.mask(); m && !BIT(m, 0); m >>= 1, ++shift) { } + ioport_value const m(f.mask() >> shift); + range = (f.maxval() - f.minval()) & m; + ioport_value const n((f.analog_reverse() ? (f.maxval() - f.defvalue()) : (f.defvalue() - f.minval())) & m); + neutral = float(n) / range; + if (!f.analog_wraps() || (f.defvalue() == f.minval()) || (f.defvalue() == f.maxval())) + origin = neutral; +} + + +menu_analog::menu_analog(mame_ui_manager &mui, render_container &container) + : menu(mui, container) + , m_item_data() + , m_field_data() + , m_prompt() + , m_bottom_fields(0U) + , m_visible_fields(0) + , m_top_field(0) + , m_hide_menu(false) + , m_box_left(1.0F) + , m_box_top(1.0F) + , m_box_right(0.0F) + , m_box_bottom(0.0F) + , 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_scroll_base(0) + , m_arrow_clicked_first(false) +{ + set_process_flags(PROCESS_LR_REPEAT); + set_heading(_("menu-analoginput", "Analog Input Adjustments")); +} + + +menu_analog::~menu_analog() +{ +} + + +void menu_analog::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + menu::recompute_metrics(width, height, aspect); + + m_box_left = m_box_top = 1.0F; + m_box_right = m_box_bottom = 0.0F; + + // space for live display + set_custom_space(0.0F, (line_height() * m_bottom_fields) + (tb_border() * 3.0F)); +} + + +void menu_analog::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +{ + // work out how much space to use for field names + float const extrawidth(0.4F + (((ui().box_lr_border() * 2.0F) + ui().get_line_height()) * x_aspect())); + float const nameavail(1.0F - (lr_border() * 2.0F) - extrawidth); + float namewidth(0.0F); + for (field_data &data : m_field_data) + namewidth = (std::min)((std::max)(get_string_width(data.field.get().name()), namewidth), nameavail); + + // make a box or two + rgb_t const fgcolor(ui().colors().text_color()); + m_box_left = (1.0F - namewidth - extrawidth) * 0.5F; + m_box_right = m_box_left + namewidth + extrawidth; + float firstliney; + if (m_hide_menu) + { + if (m_prompt.empty()) + m_prompt = util::string_format(_("menu-analoginput", "Press %s to show settings"), ui().get_general_input_setting(IPT_UI_ON_SCREEN_DISPLAY)); + draw_text_box( + &m_prompt, &m_prompt + 1, + m_box_left, m_box_right, origy1 - top, origy1 - top + line_height() + (tb_border() * 2.0F), + text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, false, + fgcolor, ui().colors().background_color()); + m_box_top = origy1 - top + line_height() + (tb_border() * 3.0F); + firstliney = origy1 - top + line_height() + (tb_border() * 4.0F); + m_visible_fields = std::min<int>(m_field_data.size(), int((origy2 + bottom - tb_border() - firstliney) / line_height())); + m_box_bottom = firstliney + (line_height() * m_visible_fields) + tb_border(); + } + else + { + m_box_top = origy2 + tb_border(); + m_box_bottom = origy2 + bottom; + firstliney = origy2 + (tb_border() * 2.0F); + m_visible_fields = m_bottom_fields; + } + ui().draw_outlined_box(container(), m_box_left, m_box_top, m_box_right, m_box_bottom, ui().colors().background_color()); + + // force the field being configured to be visible + ioport_field *const selfield(selectedref ? &reinterpret_cast<item_data *>(selectedref)->field.get() : nullptr); + if (!m_hide_menu && selfield) + { + auto const found( + std::find_if( + m_field_data.begin(), + m_field_data.end(), + [selfield] (field_data const &d) { return &d.field.get() == selfield; })); + if (m_field_data.end() != found) + { + auto const i(std::distance(m_field_data.begin(), found)); + if (m_top_field > i) + m_top_field = i; + if ((m_top_field + m_visible_fields) <= i) + m_top_field = i - m_bottom_fields + 1; + } + } + if (0 > m_top_field) + m_top_field = 0; + if ((m_top_field + m_visible_fields) > m_field_data.size()) + m_top_field = m_field_data.size() - m_visible_fields; + + // show live fields + namewidth += line_height() * x_aspect(); + float const nameleft(m_box_left + lr_border()); + float const indleft(nameleft + namewidth); + float const indright(indleft + 0.4F); + for (unsigned line = 0; m_visible_fields > line; ++line) + { + // draw arrows if scrolling is possible and menu is hidden + float const liney(firstliney + (line_height() * float(line))); + if (m_hide_menu) + { + bool const uparrow(!line && m_top_field); + bool const downarrow(((m_visible_fields - 1) == line) && ((m_field_data.size() - 1) > (line + m_top_field))); + if (uparrow || downarrow) + { + bool const active((uparrow && (pointer_action::SCROLL_UP == m_pointer_action)) || (downarrow && (pointer_action::SCROLL_DOWN == m_pointer_action))); + bool const hovered((active || pointer_idle()) && pointer_in_rect(nameleft, liney, indright, liney + line_height())); + float const arrowwidth(line_height() * x_aspect()); + rgb_t const arrowcolor(!(active || hovered) ? fgcolor : (active && hovered) ? ui().colors().selected_color() : ui().colors().mouseover_color()); + if (active || hovered) + { + highlight( + nameleft, liney, indright, liney + line_height(), + (active && hovered) ? ui().colors().selected_bg_color() : ui().colors().mouseover_bg_color()); + } + draw_arrow( + 0.5F * (nameleft + indright - arrowwidth), liney + (0.25F * line_height()), + 0.5F * (nameleft + indright + arrowwidth), liney + (0.75F * line_height()), + arrowcolor, line ? (ROT0 ^ ORIENTATION_FLIP_Y) : ROT0); + continue; + } + } + + // draw the field name, using the selected colour if it's being configured + field_data &data(m_field_data[line + m_top_field]); + bool const selected(&data.field.get() == selfield); + rgb_t const fieldcolor(selected ? ui().colors().selected_color() : fgcolor); + draw_text_normal( + data.field.get().name(), + nameleft, liney, namewidth, + text_layout::text_justify::LEFT, text_layout::word_wrapping::NEVER, + fieldcolor); + + ioport_value cur(0U); + data.field.get().live().analog->read(cur); + cur = ((cur >> data.shift) - data.field.get().minval()) & (data.field.get().mask() >> data.shift); + float fill(float(cur) / data.range); + if (data.field.get().analog_reverse()) + fill = 1.0F - fill; + + float const indtop(liney + (line_height() * 0.2F)); + float const indbottom(liney + (line_height() * 0.8F)); + if (data.origin > fill) + container().add_rect(indleft + (fill * 0.4F), indtop, indleft + (data.origin * 0.4F), indbottom, fgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + else + container().add_rect(indleft + (data.origin * 0.4F), indtop, indleft + (fill * 0.4F), indbottom, fgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + container().add_line(indleft, indtop, indright, indtop, UI_LINE_WIDTH, fgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + container().add_line(indright, indtop, indright, indbottom, UI_LINE_WIDTH, fgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + container().add_line(indright, indbottom, indleft, indbottom, UI_LINE_WIDTH, fgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + container().add_line(indleft, indbottom, indleft, indtop, UI_LINE_WIDTH, fgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + if (data.show_neutral) + container().add_line(indleft + (data.neutral * 0.4F), indtop, indleft + (data.neutral * 0.4F), indbottom, UI_LINE_WIDTH, fgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + } +} + + +std::tuple<int, bool, bool> menu_analog::custom_pointer_updated(bool changed, ui_event const &uievt) +{ + // no pointer input if we don't have up-to-date content on-screen + if ((m_box_left > m_box_right) || (ui_event::type::POINTER_ABORT == uievt.event_type)) + { + m_pointer_action = pointer_action::NONE; + return std::make_tuple(IPT_INVALID, false, false); + } + + // if nothing's happening, check for clicks + if (pointer_idle()) + { + if ((uievt.pointer_pressed & 0x01) && !(uievt.pointer_buttons & ~u32(0x01))) + { + if (1 == uievt.pointer_clicks) + m_arrow_clicked_first = false; + + float const firstliney(m_box_top + tb_border()); + float const fieldleft(m_box_left + lr_border()); + float const fieldright(m_box_right - lr_border()); + auto const [x, y] = pointer_location(); + bool const inwidth((x >= fieldleft) && (x < fieldright)); + if (m_hide_menu && m_top_field && inwidth && (y >= firstliney) && (y < (firstliney + line_height()))) + { + // scroll up arrow + if (1 == uievt.pointer_clicks) + m_arrow_clicked_first = true; + + --m_top_field; + m_pointer_action = pointer_action::SCROLL_UP; + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(300); + m_last_pointer = std::make_pair(x, y); + return std::make_tuple(IPT_INVALID, true, true); + } + else if (m_hide_menu && ((m_top_field + m_visible_fields) < m_field_data.size()) && inwidth && (y >= (firstliney + (float(m_visible_fields - 1) * line_height()))) && (y < (firstliney + (float(m_visible_fields) * line_height())))) + { + // scroll down arrow + if (1 == uievt.pointer_clicks) + m_arrow_clicked_first = true; + + ++m_top_field; + m_pointer_action = pointer_action::SCROLL_DOWN; + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(300); + m_last_pointer = std::make_pair(x, y); + return std::make_tuple(IPT_INVALID, true, true); + } + else if ((x >= m_box_left) && (x < m_box_right) && (y >= m_box_top) && (y < m_box_bottom)) + { + if (!m_arrow_clicked_first && (2 == uievt.pointer_clicks)) + { + // toggle menu display + // FIXME: this should really use the start point of the multi-click action + m_pointer_action = pointer_action::CHECK_TOGGLE_MENU; + m_base_pointer = std::make_pair(x, y); + return std::make_tuple(IPT_INVALID, true, false); + } + else if (ui_event::pointer::TOUCH == uievt.pointer_type) + { + m_pointer_action = pointer_action::SCROLL_DRAG; + m_base_pointer = std::make_pair(x, y); + m_last_pointer = m_base_pointer; + m_scroll_base = m_top_field; + return std::make_tuple(IPT_INVALID, true, false); + } + } + } + return std::make_tuple(IPT_INVALID, false, false); + } + + // handle in-progress actions + switch (m_pointer_action) + { + case pointer_action::NONE: + break; + + case pointer_action::SCROLL_UP: + case pointer_action::SCROLL_DOWN: + { + // check for re-entry + bool redraw(false); + float const linetop(m_box_top + tb_border() + ((pointer_action::SCROLL_DOWN == m_pointer_action) ? (float(m_visible_fields - 1) * line_height()) : 0.0F)); + float const linebottom(linetop + line_height()); + auto const [x, y] = pointer_location(); + bool const reentered(reentered_rect(m_last_pointer.first, m_last_pointer.second, x, y, m_box_left + lr_border(), linetop, m_box_right - lr_border(), linebottom)); + if (reentered) + { + auto const now(std::chrono::steady_clock::now()); + if (scroll_if_expired(now)) + { + redraw = true; + m_scroll_repeat = now + std::chrono::milliseconds(100); + } + } + m_last_pointer = std::make_pair(x, y); + 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); + } + break; + + case pointer_action::SCROLL_DRAG: + { + bool const scrolled(update_scroll_drag(uievt)); + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, scrolled); + } + + case pointer_action::CHECK_TOGGLE_MENU: + if ((ui_event::pointer::TOUCH == uievt.pointer_type) && (0 > uievt.pointer_clicks)) + { + // converted to hold/drag - treat as scroll if it's touch + m_pointer_action = pointer_action::SCROLL_DRAG; + m_last_pointer = m_base_pointer; + m_scroll_base = m_top_field; + bool const scrolled(update_scroll_drag(uievt)); + return std::make_tuple(IPT_INVALID, pointer_action::NONE != m_pointer_action, scrolled); + } + else if (uievt.pointer_released & 0x01) + { + // primary button released - simulate the on-screen display key if it wasn't converted to a hold/drag + return std::make_tuple((2 == uievt.pointer_clicks) ? IPT_UI_ON_SCREEN_DISPLAY : IPT_INVALID, false, false); + } + else if ((2 != uievt.pointer_clicks) || (uievt.pointer_buttons & ~u32(0x01))) + { + // treat converting to a hold/drag or pressing another button as cancelling the action + return std::make_tuple(IPT_INVALID, false, false); + } + return std::make_tuple(IPT_INVALID, true, false); + } + return std::make_tuple(IPT_INVALID, false, false); +} + + +void menu_analog::menu_activated() +{ + // scripts could have changed something in the mean time + m_item_data.clear(); + m_field_data.clear(); + reset(reset_options::REMEMBER_POSITION); + + m_box_left = m_box_top = 1.0F; + m_box_right = m_box_bottom = 0.0F; + m_pointer_action = pointer_action::NONE; + m_arrow_clicked_first = false; +} + + +bool menu_analog::handle(event const *ev) +{ + // deal with repeating scroll arrows + bool scrolled(false); + if ((pointer_action::SCROLL_UP == m_pointer_action) || (pointer_action::SCROLL_DOWN == m_pointer_action)) + { + float const linetop(m_box_top + tb_border() + ((pointer_action::SCROLL_DOWN == m_pointer_action) ? (float(m_visible_fields - 1) * line_height()) : 0.0F)); + float const linebottom(linetop + line_height()); + if (pointer_in_rect(m_box_left + lr_border(), linetop, m_box_right - lr_border(), linebottom)) + { + while (scroll_if_expired(std::chrono::steady_clock::now())) + { + scrolled = true; + m_scroll_repeat += std::chrono::milliseconds(100); + } + } + } + + if (!ev) + { + return scrolled; + } + else if (IPT_UI_ON_SCREEN_DISPLAY == ev->iptkey) + { + m_hide_menu = !m_hide_menu; + + m_box_left = m_box_top = 1.0F; + m_box_right = m_box_bottom = 0.0F; + m_pointer_action = pointer_action::NONE; + m_arrow_clicked_first = false; + + set_process_flags(PROCESS_LR_REPEAT | (m_hide_menu ? (PROCESS_CUSTOM_NAV | PROCESS_CUSTOM_ONLY) : 0)); + return true; + } + else if (IPT_UI_HELP == ev->iptkey) + { + stack_push<menu_fixed_textbox>( + ui(), + container(), + _("menu-analoginput", "Analog Input Adjustments Help"), + util::string_format( + _("menu-analoginput", HELP_TEXT), + ui().get_general_input_setting(IPT_UI_ON_SCREEN_DISPLAY), + ui().get_general_input_setting(IPT_UI_LEFT), + ui().get_general_input_setting(IPT_UI_RIGHT), + ui().get_general_input_setting(IPT_UI_CLEAR), + ui().get_general_input_setting(IPT_UI_PREV_GROUP), + ui().get_general_input_setting(IPT_UI_NEXT_GROUP), + ui().get_general_input_setting(IPT_UI_BACK))); + } + else if (m_hide_menu) + { + switch (ev->iptkey) + { + case IPT_UI_UP: + if (m_top_field) + { + --m_top_field; + return true; + } + break; + case IPT_UI_DOWN: + if ((m_top_field + m_visible_fields) < m_field_data.size()) + { + ++m_top_field; + return true; + } + break; + case IPT_UI_PAGE_UP: + if (m_visible_fields) + { + m_top_field -= std::min<int>(m_visible_fields - 3, m_top_field); + return true; + } + break; + case IPT_UI_PAGE_DOWN: + if ((m_top_field + m_visible_fields) < m_field_data.size()) + { + m_top_field = std::min<int>(m_top_field + m_visible_fields - 3, m_field_data.size() - m_visible_fields); + return true; + } + break; + case IPT_UI_HOME: + if (m_top_field) + { + m_top_field = 0; + return true; + } + break; + case IPT_UI_END: + if ((m_top_field + m_visible_fields) < m_field_data.size()) + { + m_top_field = m_field_data.size() - m_visible_fields; + return true; + } + break; + } + } + else if (ev->itemref) + { + item_data &data(*reinterpret_cast<item_data *>(ev->itemref)); + int newval(data.cur); + bool const ctrl_pressed = machine().input().code_pressed(KEYCODE_LCONTROL) || machine().input().code_pressed(KEYCODE_RCONTROL); + + switch (ev->iptkey) + { + // flip toggles when selected + case IPT_UI_SELECT: + if (ANALOG_ITEM_REVERSE == data.type) + newval = newval ? 0 : 1; + break; + + // if cleared, reset to default value + case IPT_UI_CLEAR: + newval = data.defvalue; + break; + + // left decrements + case IPT_UI_LEFT: + newval -= ctrl_pressed ? 10 : 1; + break; + + // right increments + case IPT_UI_RIGHT: + newval += ctrl_pressed ? 10 : 1; + break; + + // move to first item for previous device + case IPT_UI_PREV_GROUP: + { + auto current = std::distance(m_item_data.data(), &data); + device_t const *dev(&data.field.get().device()); + bool found_break = false; + while (0 < current) + { + if (!found_break) + { + device_t const *prev(&m_item_data[--current].field.get().device()); + if (prev != dev) + { + dev = prev; + found_break = true; + } + } + else if (&m_item_data[current - 1].field.get().device() != dev) + { + set_selection(&m_item_data[current]); + set_top_line(selected_index() - 1); + return true; + } + else + { + --current; + } + if (found_break && !current) + { + set_selection(&m_item_data[current]); + set_top_line(selected_index() - 1); + return true; + } + } + } + break; + + // move to first item for next device + case IPT_UI_NEXT_GROUP: + { + auto current = std::distance(m_item_data.data(), &data); + device_t const *const dev(&data.field.get().device()); + while (m_item_data.size() > ++current) + { + if (&m_item_data[current].field.get().device() != dev) + { + set_selection(&m_item_data[current]); + set_top_line(selected_index() - 1); + return true; + } + } + } + break; + } + + // clamp to range + newval = std::clamp(newval, data.min, data.max); + + // if things changed, update + if (newval != data.cur) + { + ioport_field::user_settings settings; + + // get the settings and set the new value + data.field.get().get_user_settings(settings); + switch (data.type) + { + case ANALOG_ITEM_KEYSPEED: settings.delta = newval; break; + case ANALOG_ITEM_CENTERSPEED: settings.centerdelta = newval; break; + case ANALOG_ITEM_REVERSE: settings.reverse = newval; break; + case ANALOG_ITEM_SENSITIVITY: settings.sensitivity = newval; break; + } + data.field.get().set_user_settings(settings); + data.cur = newval; + + // update the menu item + ev->item->set_subtext(item_text(data.type, newval)); + ev->item->set_flags((data.cur <= data.min) ? FLAG_RIGHT_ARROW : (data.cur >= data.max) ? FLAG_LEFT_ARROW : FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW); + return true; + } + } + + return scrolled; +} + + +void menu_analog::populate() +{ + // loop over input ports + if (m_item_data.empty()) + find_fields(); + + device_t *prev_owner(nullptr); + ioport_field *field(nullptr); + ioport_field::user_settings settings; + + // add the items + std::string text; + for (item_data &data : m_item_data) + { + // get the user settings + if (&data.field.get() != field) + { + field = &data.field.get(); + field->get_user_settings(settings); + + if (&field->device() != prev_owner) + { + prev_owner = &field->device(); + if (prev_owner->owner()) + item_append(string_format(_("%1$s [root%2$s]"), prev_owner->type().fullname(), prev_owner->tag()), FLAG_UI_HEADING | FLAG_DISABLE, nullptr); + else + item_append(string_format(_("[root%1$s]"), prev_owner->tag()), FLAG_UI_HEADING | FLAG_DISABLE, nullptr); + } + } + + // determine the properties of this item + switch (data.type) + { + default: + case ANALOG_ITEM_KEYSPEED: + text = string_format(_("menu-analoginput", "%1$s Increment/Decrement Speed"), field->name()); + data.cur = settings.delta; + break; + + case ANALOG_ITEM_CENTERSPEED: + text = string_format(_("menu-analoginput", "%1$s Auto-centering Speed"), field->name()); + data.cur = settings.centerdelta; + break; + + case ANALOG_ITEM_REVERSE: + text = string_format(_("menu-analoginput", "%1$s Reverse"), field->name()); + data.cur = settings.reverse; + break; + + case ANALOG_ITEM_SENSITIVITY: + text = string_format(_("menu-analoginput", "%1$s Sensitivity"), field->name()); + data.cur = settings.sensitivity; + break; + } + + // append a menu item + item_append( + std::move(text), + item_text(data.type, data.cur), + (data.cur <= data.min) ? FLAG_RIGHT_ARROW : (data.cur >= data.max) ? FLAG_LEFT_ARROW : FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW, + &data); + } + + // display a message if there are toggle inputs enabled + if (!prev_owner) + item_append(_("menu-analoginput", "[no analog inputs are enabled]"), FLAG_DISABLE, nullptr); + + item_append(menu_item_type::SEPARATOR); + + // space for live display + set_custom_space(0.0F, (line_height() * m_bottom_fields) + (tb_border() * 3.0F)); +} + + +void menu_analog::find_fields() +{ + assert(m_field_data.empty()); + + // collect analog fields + for (auto &port : machine().ioport().ports()) + { + for (ioport_field &field : port.second->fields()) + { + if (field.is_analog() && field.enabled()) + { + // based on the type, determine if we enable autocenter + bool use_autocenter = false; + switch (field.type()) + { + case IPT_POSITIONAL: + case IPT_POSITIONAL_V: + use_autocenter = !field.analog_wraps(); + break; + + case IPT_AD_STICK_X: + case IPT_AD_STICK_Y: + case IPT_AD_STICK_Z: + case IPT_PADDLE: + case IPT_PADDLE_V: + case IPT_PEDAL: + case IPT_PEDAL2: + case IPT_PEDAL3: + use_autocenter = true; + break; + + default: + break; + } + + // iterate over types + for (int type = 0; type < ANALOG_ITEM_COUNT; type++) + { + if ((ANALOG_ITEM_CENTERSPEED != type) || use_autocenter) + m_item_data.emplace_back(field, type); + } + + m_field_data.emplace_back(field); + } + } + } + + // restrict live display to 40% height plus borders + if ((line_height() * m_field_data.size()) > 0.4F) + m_bottom_fields = unsigned(0.4F / line_height()); + else + m_bottom_fields = m_field_data.size(); +} + + +bool menu_analog::scroll_if_expired(std::chrono::steady_clock::time_point now) +{ + if (now < m_scroll_repeat) + return false; + + if (pointer_action::SCROLL_DOWN == m_pointer_action) + { + if ((m_top_field + m_visible_fields) < m_field_data.size()) + ++m_top_field; + if ((m_top_field + m_visible_fields) == m_field_data.size()) + m_pointer_action = pointer_action::NONE; + } + else + { + if (0 < m_top_field) + --m_top_field; + if (!m_top_field) + m_pointer_action = pointer_action::NONE; + } + return true; +} + + +bool menu_analog::update_scroll_drag(ui_event const &uievt) +{ + // set thresholds depending on the direction for hysteresis + float const y(pointer_location().second); + float const base(m_base_pointer.second + (line_height() * ((y > m_last_pointer.second) ? -0.3F : 0.3F))); + auto const target(int((base - y) / line_height())); + m_last_pointer.second = base + (float(target) * line_height()); + + // scroll if it moved + int newtop(std::clamp<int>(m_scroll_base + target, 0, m_field_data.size() - m_visible_fields)); + if (!m_hide_menu && (newtop != m_top_field)) + { + // if the menu is visible, keep the highlighted field on-screen + void *const selectedref(get_selection_ref()); + ioport_field *const selfield(selectedref ? &reinterpret_cast<item_data *>(selectedref)->field.get() : nullptr); + if (selfield) + { + auto const found( + std::find_if( + m_field_data.begin(), + m_field_data.end(), + [selfield] (field_data const &d) { return &d.field.get() == selfield; })); + if (m_field_data.end() != found) + { + auto const i(std::distance(m_field_data.begin(), found)); + newtop = std::clamp<int>(newtop, i + 1 - m_visible_fields, i); + } + } + } + bool const scrolled(newtop != m_top_field); + m_top_field = newtop; + + // catch the end of the gesture + if ((uievt.pointer_released & 0x01) || (uievt.pointer_pressed & ~u32(0x01))) + m_pointer_action = pointer_action::NONE; + return scrolled; +} + + +std::string menu_analog::item_text(int type, int value) +{ + switch (type) + { + default: + case ANALOG_ITEM_KEYSPEED: + return string_format("%d", value); + + case ANALOG_ITEM_CENTERSPEED: + return string_format("%d", value); + + case ANALOG_ITEM_REVERSE: + return value ? _("On") : _("Off"); + + case ANALOG_ITEM_SENSITIVITY: + return string_format("%d", value); + } +} + +} // namespace ui diff --git a/src/frontend/mame/ui/analogipt.h b/src/frontend/mame/ui/analogipt.h new file mode 100644 index 00000000000..8caba0e8f7d --- /dev/null +++ b/src/frontend/mame/ui/analogipt.h @@ -0,0 +1,115 @@ +// license:BSD-3-Clause +// copyright-holders:Nicola Salmoria, Aaron Giles, Nathan Woods, Vas Crabb +/*************************************************************************** + + ui/analogipt.h + + Analog inputs menu. + +***************************************************************************/ +#ifndef MAME_FRONTEND_UI_ANALOGIPT_H +#define MAME_FRONTEND_UI_ANALOGIPT_H + +#pragma once + +#include "ui/menu.h" + +#include <chrono> +#include <functional> +#include <tuple> +#include <utility> +#include <vector> + + +namespace ui { + +class menu_analog : public menu +{ +public: + menu_analog(mame_ui_manager &mui, render_container &container); + virtual ~menu_analog() override; + +protected: + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) override; + virtual std::tuple<int, bool, bool> custom_pointer_updated(bool changed, ui_event const &uievt) override; + virtual void menu_activated() override; + +private: + enum class pointer_action + { + NONE, + SCROLL_UP, + SCROLL_DOWN, + SCROLL_DRAG, + CHECK_TOGGLE_MENU + }; + + enum + { + ANALOG_ITEM_KEYSPEED = 0, + ANALOG_ITEM_CENTERSPEED, + ANALOG_ITEM_REVERSE, + ANALOG_ITEM_SENSITIVITY, + ANALOG_ITEM_COUNT + }; + + struct item_data + { + item_data(ioport_field &f, int t) noexcept; + + std::reference_wrapper<ioport_field> field; + int type; + int defvalue; + int min, max; + int cur; + }; + + struct field_data + { + field_data(ioport_field &f) noexcept; + + std::reference_wrapper<ioport_field> field; + float range; + float neutral; + float origin; + u8 shift; + bool show_neutral; + }; + + using item_data_vector = std::vector<item_data>; + using field_data_vector = std::vector<field_data>; + + virtual void populate() override; + virtual bool handle(event const *ev) override; + + void find_fields(); + bool scroll_if_expired(std::chrono::steady_clock::time_point now); + bool update_scroll_drag(ui_event const &uievt); + + static std::string item_text(int type, int value); + + item_data_vector m_item_data; + field_data_vector m_field_data; + std::string m_prompt; + unsigned m_bottom_fields; + int m_visible_fields; + int m_top_field; + bool m_hide_menu; + + float m_box_left; + float m_box_top; + float m_box_right; + float m_box_bottom; + + pointer_action m_pointer_action; + std::chrono::steady_clock::time_point m_scroll_repeat; + std::pair<float, float> m_base_pointer; + std::pair<float, float> m_last_pointer; + int m_scroll_base; + bool m_arrow_clicked_first; +}; + +} // namespace ui + +#endif // MAME_FRONTEND_UI_ANALOGIPT_H diff --git a/src/frontend/mame/ui/audio_effect_eq.cpp b/src/frontend/mame/ui/audio_effect_eq.cpp new file mode 100644 index 00000000000..d758480ad00 --- /dev/null +++ b/src/frontend/mame/ui/audio_effect_eq.cpp @@ -0,0 +1,365 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert +/********************************************************************* + + ui/audio_effect_eq.cpp + + Equalizer configuration + +*********************************************************************/ + +#include "emu.h" +#include "ui/audio_effect_eq.h" +#include "audio_effects/aeffect.h" +#include "audio_effects/eq.h" + +#include "ui/ui.h" + +namespace ui { + +const u32 menu_audio_effect_eq::freqs[3][43] = { + { 0, 32, 36, 40, 45, 50, 56, 63, 70, 80, 90, 100, 110, 125, 140, 160, 180, 200, 225, 250, 280, 315, 355, 400, 450, 500, 560, 630, 700, 800, 900, 1000, 1100, 1200, 1400, 1600, 1800, 2000 }, + { 0, 100, 110, 125, 140, 160, 180, 200, 225, 250, 280, 315, 355, 400, 450, 500, 560, 630, 700, 800, 900, 1000, 1100, 1200, 1400, 1600, 1800, 2000, 2200, 2500, 2800, 3200, 3600, 4000, 4500, 5000, 5600, 6300, 7000, 8000, 9000, 10000 }, + { 0, 500, 560, 630, 700, 800, 900, 1000, 1100, 1200, 1400, 1600, 1800, 2000, 2200, 2500, 2800, 3200, 3600, 4000, 4500, 5000, 5600, 6300, 7000, 8000, 9000, 10000, 11000, 12000, 14000, 16000 }, +}; + +menu_audio_effect_eq::menu_audio_effect_eq(mame_ui_manager &mui, render_container &container, u16 chain, u16 entry, audio_effect *effect) + : menu(mui, container) +{ + m_chain = chain; + m_entry = entry; + m_effect = static_cast<audio_effect_eq *>(effect); + set_heading(util::string_format("%s #%u", chain == 0xffff ? _("Default") : machine().sound().effect_chain_tag(chain), entry+1)); + set_process_flags(PROCESS_LR_REPEAT | PROCESS_LR_ALWAYS); +} + +menu_audio_effect_eq::~menu_audio_effect_eq() +{ +} + +std::pair<u32, u32> menu_audio_effect_eq::find_f(u32 band) const +{ + u32 variant = band == 0 ? 0 : band < 4 ? 1 : 2; + u32 bi = 0; + s32 dt = 40000; + s32 f = s32(m_effect->f(band) + 0.5); + for(u32 index = 1; freqs[variant][index]; index++) { + s32 d1 = f - freqs[variant][index]; + if(d1 < 0) + d1 = -d1; + if(d1 < dt) { + dt = d1; + bi = index; + } + } + return std::make_pair(variant, bi); +} + +void menu_audio_effect_eq::change_f(u32 band, s32 direction) +{ + auto [variant, bi] = find_f(band); + bi += direction; + if(!freqs[variant][bi]) + bi -= direction; + m_effect->set_f(band, freqs[variant][bi]); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); +} + +bool menu_audio_effect_eq::handle(event const *ev) +{ + if(!ev) + return false; + + u32 band = (uintptr_t(ev->itemref)) >> 16; + u32 entry = (uintptr_t(ev->itemref)) & 0xffff; + + switch(ev->iptkey) { + case IPT_UI_LEFT: { + switch(entry) { + case MODE: + m_effect->set_mode(0); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); + reset(reset_options::REMEMBER_POSITION); + return true; + + case SHELF: + if(band == 0) + m_effect->set_low_shelf(true); + else + m_effect->set_high_shelf(true); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); + reset(reset_options::REMEMBER_POSITION); + return true; + + case F: + change_f(band, -1); + reset(reset_options::REMEMBER_POSITION); + return true; + + case Q: { + float q = m_effect->q(band); + q = (int(q*10 + 0.5) - 1) / 10.0; + if(q < 0.1) + q = 0.1; + m_effect->set_q(band, q); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); + reset(reset_options::REMEMBER_POSITION); + return true; + } + + case DB: { + float db = m_effect->db(band); + db -= 1; + if(db < -12) + db = -12; + m_effect->set_db(band, db); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); + reset(reset_options::REMEMBER_POSITION); + return true; + } + } + break; + } + + case IPT_UI_RIGHT: { + switch(entry) { + case MODE: + m_effect->set_mode(1); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); + reset(reset_options::REMEMBER_POSITION); + return true; + + case SHELF: + if(band == 0) + m_effect->set_low_shelf(false); + else + m_effect->set_high_shelf(false); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); + reset(reset_options::REMEMBER_POSITION); + return true; + + case F: + change_f(band, +1); + reset(reset_options::REMEMBER_POSITION); + return true; + + case Q: { + float q = m_effect->q(band); + q = (int(q*10 + 0.5) + 1) / 10.0; + if(q > 12) + q = 12; + m_effect->set_q(band, q); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); + reset(reset_options::REMEMBER_POSITION); + return true; + } + + case DB: { + float db = m_effect->db(band); + db += 1; + if(db > 12) + db = 12; + m_effect->set_db(band, db); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); + reset(reset_options::REMEMBER_POSITION); + return true; + } + } + break; + } + case IPT_UI_CLEAR: { + switch(entry) { + case MODE: + m_effect->reset_mode(); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); + reset(reset_options::REMEMBER_POSITION); + return true; + + case SHELF: + if(band == 0) + m_effect->reset_low_shelf(); + else + m_effect->reset_high_shelf(); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); + reset(reset_options::REMEMBER_POSITION); + return true; + + case F: + m_effect->reset_f(band); + reset(reset_options::REMEMBER_POSITION); + return true; + + case Q: { + m_effect->reset_q(band); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); + reset(reset_options::REMEMBER_POSITION); + return true; + } + + case DB: { + m_effect->reset_db(band); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); + reset(reset_options::REMEMBER_POSITION); + return true; + } + } + break; + } + } + return false; +} + +std::string menu_audio_effect_eq::format_f(float f) +{ + return f >= 1000 ? util::string_format("%.1fkHz", f/1000) : util::string_format("%.0fHz", f); +} + +std::string menu_audio_effect_eq::format_q(float q) +{ + return util::string_format("%.1f", q); +} + +std::string menu_audio_effect_eq::format_db(float db) +{ + return util::string_format("%+.0fdB", db); +} + +u32 menu_audio_effect_eq::flag_mode() const +{ + u32 flag = 0; + if(!m_effect->isset_mode()) + flag |= FLAG_INVERT; + if(m_effect->mode() == 1) + flag |= FLAG_LEFT_ARROW; + if(m_effect->mode() == 0) + flag |= FLAG_RIGHT_ARROW; + return flag; +} + +u32 menu_audio_effect_eq::flag_low_shelf() const +{ + u32 flag = 0; + if(!m_effect->isset_low_shelf()) + flag |= FLAG_INVERT; + if(m_effect->low_shelf()) + flag |= FLAG_RIGHT_ARROW; + else + flag |= FLAG_LEFT_ARROW; + return flag; +} + +u32 menu_audio_effect_eq::flag_high_shelf() const +{ + u32 flag = 0; + if(!m_effect->isset_high_shelf()) + flag |= FLAG_INVERT; + if(m_effect->high_shelf()) + flag |= FLAG_RIGHT_ARROW; + else + flag |= FLAG_LEFT_ARROW; + return flag; +} + +u32 menu_audio_effect_eq::flag_f(u32 band) const +{ + u32 flag = 0; + if(!m_effect->isset_f(band)) + flag |= FLAG_INVERT; + auto [variant, bi] = find_f(band); + if(freqs[variant][bi-1]) + flag |= FLAG_LEFT_ARROW; + if(freqs[variant][bi+1]) + flag |= FLAG_RIGHT_ARROW; + return flag; +} + +u32 menu_audio_effect_eq::flag_q(u32 band) const +{ + u32 flag = 0; + if(!m_effect->isset_q(band)) + flag |= FLAG_INVERT; + float q = m_effect->q(band); + if(q < 10) + flag |= FLAG_LEFT_ARROW; + if(q > 0.1) + flag |= FLAG_RIGHT_ARROW; + return flag; +} + +u32 menu_audio_effect_eq::flag_db(u32 band) const +{ + u32 flag = 0; + if(!m_effect->isset_db(band)) + flag |= FLAG_INVERT; + float db = m_effect->db(band); + if(db < 12) + flag |= FLAG_LEFT_ARROW; + if(db > -12) + flag |= FLAG_RIGHT_ARROW; + return flag; +} + +void menu_audio_effect_eq::populate() +{ + item_append(_(audio_effect::effect_names[audio_effect::EQ]), FLAG_UI_HEADING | FLAG_DISABLE, nullptr); + item_append(_("Mode"), m_effect->mode() ? _("5-Band EQ") : _("Bypass"), flag_mode(), (void *)MODE); + item_append(_("Low band mode"), m_effect->low_shelf() ? _("Shelf") : _("Peak"), flag_low_shelf(), (void *)uintptr_t(SHELF | (0 << 16))); + item_append(_("Low band freq."), format_f(m_effect->f(0)), flag_f(0), (void *)uintptr_t(F | (0 << 16))); + if(!m_effect->low_shelf()) + item_append(_("Low band Q"), format_q(m_effect->q(0)), flag_q(0), (void *)uintptr_t(Q | (0 << 16))); + item_append(_("Low band dB"), format_db(m_effect->db(0)), flag_db(0), (void *)uintptr_t(DB | (0 << 16))); + + item_append(_("Lo mid band freq."), format_f(m_effect->f(1)), flag_f(1), (void *)uintptr_t(F | (1 << 16))); + item_append(_("Lo mid band Q"), format_q(m_effect->q(1)), flag_q(1), (void *)uintptr_t(Q | (1 << 16))); + item_append(_("Lo mid band dB"), format_db(m_effect->db(1)), flag_db(1), (void *)uintptr_t(DB | (1 << 16))); + + item_append(_("Mid band freq."), format_f(m_effect->f(2)), flag_f(2), (void *)uintptr_t(F | (2 << 16))); + item_append(_("Mid band Q"), format_q(m_effect->q(2)), flag_q(2), (void *)uintptr_t(Q | (2 << 16))); + item_append(_("Mid band dB"), format_db(m_effect->db(2)), flag_db(2), (void *)uintptr_t(DB | (2 << 16))); + + item_append(_("Hi mid band freq."), format_f(m_effect->f(3)), flag_f(3), (void *)uintptr_t(F | (3 << 16))); + item_append(_("Hi mid band Q"), format_q(m_effect->q(3)), flag_q(3), (void *)uintptr_t(Q | (3 << 16))); + item_append(_("Hi mid band dB"), format_db(m_effect->db(3)), flag_db(3), (void *)uintptr_t(DB | (3 << 16))); + + + item_append(_("High band mode"), m_effect->high_shelf() ? _("Shelf") : _("Peak"), flag_high_shelf(), (void *)uintptr_t(SHELF | (4 << 16))); + item_append(_("High band freq."), format_f(m_effect->f(4)), flag_f(4), (void *)uintptr_t(F | (4 << 16))); + if(!m_effect->high_shelf()) + item_append(_("High band Q"), format_q(m_effect->q(4)), flag_q(4), (void *)uintptr_t(Q | (4 << 16))); + item_append(_("High band dB"), format_db(m_effect->db(4)), flag_db(4), (void *)uintptr_t(DB | (4 << 16))); + item_append(menu_item_type::SEPARATOR); +} + +void menu_audio_effect_eq::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + menu::recompute_metrics(width, height, aspect); +} + +void menu_audio_effect_eq::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x1, float y1, float x2, float y2) +{ +} + +void menu_audio_effect_eq::menu_activated() +{ + // scripts or the other form of the menu could have changed something in the mean time + reset(reset_options::REMEMBER_POSITION); +} + +void menu_audio_effect_eq::menu_deactivated() +{ +} + +} diff --git a/src/frontend/mame/ui/audio_effect_eq.h b/src/frontend/mame/ui/audio_effect_eq.h new file mode 100644 index 00000000000..c11d4f3996c --- /dev/null +++ b/src/frontend/mame/ui/audio_effect_eq.h @@ -0,0 +1,61 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert +/*************************************************************************** + + ui/audio_effect_eq.h + + Equalizer configuration + +***************************************************************************/ + +#ifndef MAME_FRONTEND_UI_AUDIO_EFFECT_EQ_H +#define MAME_FRONTEND_UI_AUDIO_EFFECT_EQ_H + +#pragma once + +#include "ui/menu.h" + +class audio_effect_eq; + +namespace ui { + +class menu_audio_effect_eq : public menu +{ +public: + menu_audio_effect_eq(mame_ui_manager &mui, render_container &container, u16 chain, u16 entry, audio_effect *effect); + virtual ~menu_audio_effect_eq() override; + +protected: + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void menu_activated() override; + virtual void menu_deactivated() override; + +private: + enum { MODE = 1, SHELF, F, Q, DB }; + + static const u32 freqs[3][43]; + + u16 m_chain, m_entry; + audio_effect_eq *m_effect; + + virtual void populate() override; + virtual bool handle(event const *ev) override; + + static std::string format_f(float f); + static std::string format_q(float q); + static std::string format_db(float db); + u32 flag_mode() const; + u32 flag_low_shelf() const; + u32 flag_high_shelf() const; + u32 flag_f(u32 band) const; + u32 flag_q(u32 band) const; + u32 flag_db(u32 band) const; + + std::pair<u32, u32> find_f(u32 band) const; + void change_f(u32 band, s32 direction); +}; + +} // namespace ui + +#endif // MAME_FRONTEND_UI_AUDIO_EFFECT_EQ_H diff --git a/src/frontend/mame/ui/audio_effect_filter.cpp b/src/frontend/mame/ui/audio_effect_filter.cpp new file mode 100644 index 00000000000..9b6a90df980 --- /dev/null +++ b/src/frontend/mame/ui/audio_effect_filter.cpp @@ -0,0 +1,351 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert +/********************************************************************* + + ui/audio_effect_filter.cpp + + Filter configuration + +*********************************************************************/ + +#include "emu.h" +#include "ui/audio_effect_filter.h" +#include "audio_effects/aeffect.h" +#include "audio_effects/filter.h" + +#include "ui/ui.h" + +namespace ui { +const u32 menu_audio_effect_filter::freqs[2][38] = { + { 0, 20, 22, 24, 26, 28, 30, 32, 36, 40, 45, 50, 56, 63, 70, 80, 90, 100, 110, 125, 140, 160, 180, 200, 225, 250, 280, 315, 355, 400, 450, 500, 560, 630, 700, 800, 900, 1000 }, + { 0, 1000, 1100, 1200, 1400, 1600, 1800, 2000, 2200, 2500, 2800, 3200, 3600, 4000, 4500, 5000, 5600, 6300, 7000, 8000, 9000, 10000, 11000, 12000, 14000, 16000, 18000, 20000 }, +}; + +menu_audio_effect_filter::menu_audio_effect_filter(mame_ui_manager &mui, render_container &container, u16 chain, u16 entry, audio_effect *effect) + : menu(mui, container) +{ + m_chain = chain; + m_entry = entry; + m_effect = static_cast<audio_effect_filter *>(effect); + set_heading(util::string_format("%s #%u", chain == 0xffff ? _("Default") : machine().sound().effect_chain_tag(chain), entry+1)); + set_process_flags(PROCESS_LR_REPEAT | PROCESS_LR_ALWAYS); +} + +menu_audio_effect_filter::~menu_audio_effect_filter() +{ +} + +std::pair<u32, u32> menu_audio_effect_filter::find_f(bool lp) const +{ + u32 variant = lp ? 1 : 0; + u32 bi = 0; + s32 dt = 40000; + s32 f = s32((lp ? m_effect->fl() : m_effect->fh()) + 0.5); + for(u32 index = 1; freqs[variant][index]; index++) { + s32 d1 = f - freqs[variant][index]; + if(d1 < 0) + d1 = -d1; + if(d1 < dt) { + dt = d1; + bi = index; + } + } + return std::make_pair(variant, bi); +} + +void menu_audio_effect_filter::change_f(bool lp, s32 direction) +{ + auto [variant, bi] = find_f(lp); + bi += direction; + if(!freqs[variant][bi]) + bi -= direction; + if(lp) + m_effect->set_fl(freqs[variant][bi]); + else + m_effect->set_fh(freqs[variant][bi]); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); +} + +bool menu_audio_effect_filter::handle(event const *ev) +{ + if(!ev) + return false; + + switch(ev->iptkey) { + case IPT_UI_LEFT: { + switch(uintptr_t(ev->itemref)) { + case ACTIVE | HP: + m_effect->set_highpass_active(false); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); + reset(reset_options::REMEMBER_POSITION); + return true; + + case F | HP: + change_f(false, -1); + reset(reset_options::REMEMBER_POSITION); + return true; + + case Q | HP: { + float q = m_effect->qh(); + q = (int(q*10 + 0.5) - 1) / 10.0; + if(q < 0.1) + q = 0.1; + m_effect->set_qh(q); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); + reset(reset_options::REMEMBER_POSITION); + return true; + } + + case ACTIVE | LP: + m_effect->set_lowpass_active(false); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); + reset(reset_options::REMEMBER_POSITION); + return true; + + case F | LP: + change_f(true, -1); + reset(reset_options::REMEMBER_POSITION); + return true; + + case Q | LP: { + float q = m_effect->ql(); + q = (int(q*10 + 0.5) - 1) / 10.0; + if(q < 0.1) + q = 0.1; + m_effect->set_ql(q); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); + reset(reset_options::REMEMBER_POSITION); + return true; + } + } + break; + } + + case IPT_UI_RIGHT: { + switch(uintptr_t(ev->itemref)) { + case ACTIVE | HP: + m_effect->set_highpass_active(true); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); + reset(reset_options::REMEMBER_POSITION); + return true; + + case F | HP: + change_f(false, +1); + reset(reset_options::REMEMBER_POSITION); + return true; + + case Q | HP: { + float q = m_effect->qh(); + q = (int(q*10 + 0.5) + 1) / 10.0; + if(q > 10) + q = 10; + m_effect->set_qh(q); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); + reset(reset_options::REMEMBER_POSITION); + return true; + } + + case ACTIVE | LP: + m_effect->set_lowpass_active(true); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); + reset(reset_options::REMEMBER_POSITION); + return true; + + case F | LP: + change_f(true, +1); + reset(reset_options::REMEMBER_POSITION); + return true; + + case Q | LP: { + float q = m_effect->ql(); + q = (int(q*10 + 0.5) + 1) / 10.0; + if(q > 10) + q = 10; + m_effect->set_ql(q); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); + reset(reset_options::REMEMBER_POSITION); + return true; + } + } + break; + } + + case IPT_UI_CLEAR: { + switch(uintptr_t(ev->itemref)) { + case ACTIVE | HP: + m_effect->reset_highpass_active(); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); + reset(reset_options::REMEMBER_POSITION); + return true; + + case F | HP: + m_effect->reset_fh(); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); + reset(reset_options::REMEMBER_POSITION); + return true; + + case Q | HP: + m_effect->reset_qh(); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); + reset(reset_options::REMEMBER_POSITION); + return true; + + case ACTIVE | LP: + m_effect->reset_lowpass_active(); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); + reset(reset_options::REMEMBER_POSITION); + return true; + + case F | LP: + m_effect->reset_fl(); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); + reset(reset_options::REMEMBER_POSITION); + return true; + + case Q | LP: + m_effect->reset_ql(); + if(m_chain == 0xffff) + machine().sound().default_effect_changed(m_entry); + reset(reset_options::REMEMBER_POSITION); + return true; + } + break; + } + } + return false; +} + +std::string menu_audio_effect_filter::format_f(float f) +{ + return f >= 1000 ? util::string_format("%.1fkHz", f/1000) : util::string_format("%.0fHz", f); +} + +std::string menu_audio_effect_filter::format_q(float q) +{ + return util::string_format("%.1f", q); +} + +u32 menu_audio_effect_filter::flag_highpass_active() const +{ + u32 flag = 0; + if(!m_effect->isset_highpass_active()) + flag |= FLAG_INVERT; + if(m_effect->highpass_active()) + flag |= FLAG_LEFT_ARROW; + else + flag |= FLAG_RIGHT_ARROW; + return flag; +} + +u32 menu_audio_effect_filter::flag_fh() const +{ + u32 flag = 0; + if(!m_effect->isset_fh()) + flag |= FLAG_INVERT; + auto [variant, bi] = find_f(false); + if(freqs[variant][bi-1]) + flag |= FLAG_LEFT_ARROW; + if(freqs[variant][bi+1]) + flag |= FLAG_RIGHT_ARROW; + return flag; +} + +u32 menu_audio_effect_filter::flag_qh() const +{ + u32 flag = 0; + if(!m_effect->isset_qh()) + flag |= FLAG_INVERT; + float q = m_effect->qh(); + if(q > 0.1) + flag |= FLAG_LEFT_ARROW; + if(q < 10) + flag |= FLAG_RIGHT_ARROW; + return flag; +} + +u32 menu_audio_effect_filter::flag_lowpass_active() const +{ + u32 flag = 0; + if(!m_effect->isset_lowpass_active()) + flag |= FLAG_INVERT; + if(m_effect->lowpass_active()) + flag |= FLAG_LEFT_ARROW; + else + flag |= FLAG_RIGHT_ARROW; + return flag; +} + +u32 menu_audio_effect_filter::flag_fl() const +{ + u32 flag = 0; + if(!m_effect->isset_fl()) + flag |= FLAG_INVERT; + auto [variant, bi] = find_f(true); + if(freqs[variant][bi-1]) + flag |= FLAG_LEFT_ARROW; + if(freqs[variant][bi+1]) + flag |= FLAG_RIGHT_ARROW; + return flag; +} + +u32 menu_audio_effect_filter::flag_ql() const +{ + u32 flag = 0; + if(!m_effect->isset_ql()) + flag |= FLAG_INVERT; + float q = m_effect->ql(); + if(q > 0.1) + flag |= FLAG_LEFT_ARROW; + if(q < 10) + flag |= FLAG_RIGHT_ARROW; + return flag; +} + +void menu_audio_effect_filter::populate() +{ + item_append(_(audio_effect::effect_names[audio_effect::FILTER]), FLAG_UI_HEADING | FLAG_DISABLE, nullptr); + item_append(_("Highpass (DC removal)"), m_effect->highpass_active() ? _("Active") : _("Bypass"), flag_highpass_active(), (void *)(ACTIVE | HP)); + item_append(_("Highpass cutoff"), format_f(m_effect->fh()), flag_fh(), (void *)(F | HP)); + item_append(_("Highpass Q"), format_q(m_effect->qh()), flag_qh(), (void *)(Q | HP)); + + item_append(_("Lowpass"), m_effect->lowpass_active() ? _("Active") : _("Bypass"), flag_lowpass_active(), (void *)(ACTIVE | LP)); + item_append(_("Lowpass cutoff"), format_f(m_effect->fl()), flag_fl(), (void *)(F | LP)); + item_append(_("Lowpass Q"), format_q(m_effect->ql()), flag_ql(), (void *)(Q | LP)); + + item_append(menu_item_type::SEPARATOR); +} + +void menu_audio_effect_filter::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + menu::recompute_metrics(width, height, aspect); +} + +void menu_audio_effect_filter::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x1, float y1, float x2, float y2) +{ +} + +void menu_audio_effect_filter::menu_activated() +{ + // scripts or the other form of the menu could have changed something in the mean time + reset(reset_options::REMEMBER_POSITION); +} + +void menu_audio_effect_filter::menu_deactivated() +{ +} + +} diff --git a/src/frontend/mame/ui/audio_effect_filter.h b/src/frontend/mame/ui/audio_effect_filter.h new file mode 100644 index 00000000000..7a4d84e4122 --- /dev/null +++ b/src/frontend/mame/ui/audio_effect_filter.h @@ -0,0 +1,60 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert +/*************************************************************************** + + ui/audio_effect_filter.h + + Filter configuration + +***************************************************************************/ + +#ifndef MAME_FRONTEND_UI_AUDIO_EFFECT_FILTER_H +#define MAME_FRONTEND_UI_AUDIO_EFFECT_FILTER_H + +#pragma once + +#include "ui/menu.h" + +class audio_effect_filter; + +namespace ui { + +class menu_audio_effect_filter : public menu +{ +public: + menu_audio_effect_filter(mame_ui_manager &mui, render_container &container, u16 chain, u16 entry, audio_effect *effect); + virtual ~menu_audio_effect_filter() override; + +protected: + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void menu_activated() override; + virtual void menu_deactivated() override; + +private: + enum { ACTIVE = 1, F = 2, Q = 3, HP = 0, LP = 8 }; + + static const u32 freqs[2][38]; + + u16 m_chain, m_entry; + audio_effect_filter *m_effect; + + virtual void populate() override; + virtual bool handle(event const *ev) override; + + static std::string format_f(float f); + static std::string format_q(float q); + u32 flag_highpass_active() const; + u32 flag_fh() const; + u32 flag_qh() const; + u32 flag_lowpass_active() const; + u32 flag_fl() const; + u32 flag_ql() const; + + std::pair<u32, u32> find_f(bool lp) const; + void change_f(bool lp, s32 direction); +}; + +} // namespace ui + +#endif // MAME_FRONTEND_UI_AUDIO_EFFECT_FILTER_H diff --git a/src/frontend/mame/ui/audioeffects.cpp b/src/frontend/mame/ui/audioeffects.cpp new file mode 100644 index 00000000000..750ef69815b --- /dev/null +++ b/src/frontend/mame/ui/audioeffects.cpp @@ -0,0 +1,260 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert +/********************************************************************* + + ui/audioeffects.cpp + + Audio effects control + +*********************************************************************/ + +#include "emu.h" +#include "ui/audioeffects.h" +#include "audio_effects/aeffect.h" + +#include "audio_effect_eq.h" +#include "audio_effect_filter.h" + +#include "ui/ui.h" + +#include "osdepend.h" +#include "speaker.h" + +namespace ui { + +menu_audio_effects::menu_audio_effects(mame_ui_manager &mui, render_container &container) + : menu(mui, container) +{ + set_heading(_("Audio Effects")); +} + +menu_audio_effects::~menu_audio_effects() +{ +} + +double menu_audio_effects::change_f(const double *table, double value, int change) +{ + u32 bi = 0; + double dt = 1e300; + u32 index; + for(index = 0; table[index]; index++) { + double d1 = value - table[index]; + if(d1 < 0) + d1 = -d1; + if(d1 < dt) { + dt = d1; + bi = index; + } + } + if((change != -1 || bi != 0) && (change != 1 || bi != index-1)) + bi += change; + return table[bi]; +} + +u32 menu_audio_effects::change_u32(const u32 *table, u32 value, int change) +{ + u32 bi = 0; + s32 dt = 2e9; + u32 index; + for(index = 0; table[index]; index++) { + s32 d1 = value - table[index]; + if(d1 < 0) + d1 = -d1; + if(d1 < dt) { + dt = d1; + bi = index; + } + } + if((change != -1 || bi != 0) && (change != 1 || bi != index-1)) + bi += change; + return table[bi]; +} + +bool menu_audio_effects::handle(event const *ev) +{ + static const double latencies[] = { + 0.0005, 0.0010, 0.0025, 0.0050, 0.0100, 0.0250, 0.0500, 0 + }; + + static const u32 lengths[] = { + 10, 20, 30, 40, 50, 75, 100, 200, 300, 400, 500, 0 + }; + + static const u32 phases[] = { + 10, 20, 30, 40, 50, 75, 100, 200, 300, 400, 500, 1000, 0 + }; + + + if(!ev) + return false; + + switch(ev->iptkey) { + case IPT_UI_SELECT: { + u16 chain = (uintptr_t(ev->itemref)) >> 16; + u16 entry = (uintptr_t(ev->itemref)) & 0xffff; + audio_effect *eff = chain == 0xffff ? machine().sound().default_effect_chain()[entry] : machine().sound().effect_chain(chain)[entry]; + switch(eff->type()) { + case audio_effect::FILTER: + menu::stack_push<menu_audio_effect_filter>(ui(), container(), chain, entry, eff); + break; + + case audio_effect::EQ: + menu::stack_push<menu_audio_effect_eq>(ui(), container(), chain, entry, eff); + break; + } + return true; + } + + case IPT_UI_LEFT: { + switch(uintptr_t(ev->itemref)) { + case RS_TYPE: + machine().sound().set_resampler_type(sound_manager::RESAMPLER_LOFI); + reset(reset_options::REMEMBER_POSITION); + return true; + + case RS_LATENCY: + machine().sound().set_resampler_hq_latency(change_f(latencies, machine().sound().resampler_hq_latency(), -1)); + reset(reset_options::REMEMBER_POSITION); + return true; + + case RS_LENGTH: + machine().sound().set_resampler_hq_length(change_u32(lengths, machine().sound().resampler_hq_length(), -1)); + reset(reset_options::REMEMBER_POSITION); + return true; + + case RS_PHASES: + machine().sound().set_resampler_hq_phases(change_u32(phases, machine().sound().resampler_hq_phases(), -1)); + reset(reset_options::REMEMBER_POSITION); + return true; + } + break; + } + + case IPT_UI_RIGHT: { + switch(uintptr_t(ev->itemref)) { + case RS_TYPE: + machine().sound().set_resampler_type(sound_manager::RESAMPLER_HQ); + reset(reset_options::REMEMBER_POSITION); + return true; + + case RS_LATENCY: + machine().sound().set_resampler_hq_latency(change_f(latencies, machine().sound().resampler_hq_latency(), 1)); + reset(reset_options::REMEMBER_POSITION); + return true; + + case RS_LENGTH: + machine().sound().set_resampler_hq_length(change_u32(lengths, machine().sound().resampler_hq_length(), 1)); + reset(reset_options::REMEMBER_POSITION); + return true; + + case RS_PHASES: + machine().sound().set_resampler_hq_phases(change_u32(phases, machine().sound().resampler_hq_phases(), 1)); + reset(reset_options::REMEMBER_POSITION); + return true; + } + break; + } + } + + return false; +} + + +std::string menu_audio_effects::format_lat(double latency) +{ + return util::string_format("%3.1fms", 1000*latency); +} + +std::string menu_audio_effects::format_u32(u32 val) +{ + return util::string_format("%u", val); +} + +u32 menu_audio_effects::flag_type() const +{ + u32 flag = 0; + u32 type = machine().sound().resampler_type(); + if(type != sound_manager::RESAMPLER_LOFI) + flag |= FLAG_LEFT_ARROW; + if(type != sound_manager::RESAMPLER_HQ) + flag |= FLAG_RIGHT_ARROW; + return flag; +} + +u32 menu_audio_effects::flag_lat() const +{ + u32 flag = 0; + double latency = machine().sound().resampler_hq_latency(); + if(latency > 0.0005) + flag |= FLAG_LEFT_ARROW; + if(latency < 0.0500) + flag |= FLAG_RIGHT_ARROW; + return flag; +} + +u32 menu_audio_effects::flag_length() const +{ + u32 flag = 0; + double latency = machine().sound().resampler_hq_length(); + if(latency > 10) + flag |= FLAG_LEFT_ARROW; + if(latency < 500) + flag |= FLAG_RIGHT_ARROW; + return flag; +} + +u32 menu_audio_effects::flag_phases() const +{ + u32 flag = 0; + double latency = machine().sound().resampler_hq_phases(); + if(latency > 10) + flag |= FLAG_LEFT_ARROW; + if(latency < 1000) + flag |= FLAG_RIGHT_ARROW; + return flag; +} + +void menu_audio_effects::populate() +{ + auto &sound = machine().sound(); + for(s32 chain = 0; chain != sound.effect_chains(); chain++) { + std::string tag = sound.effect_chain_tag(chain); + item_append(tag, FLAG_UI_HEADING | FLAG_DISABLE, nullptr); + auto eff = sound.effect_chain(chain); + for(u32 e = 0; e != eff.size(); e++) + item_append(_(audio_effect::effect_names[eff[e]->type()]), 0, (void *)intptr_t((chain << 16) | e)); + } + item_append(_("Default"), FLAG_UI_HEADING | FLAG_DISABLE, nullptr); + auto eff = sound.default_effect_chain(); + for(u32 e = 0; e != eff.size(); e++) + item_append(_(audio_effect::effect_names[eff[e]->type()]), 0, (void *)intptr_t((0xffff << 16) | e)); + item_append(_("Resampler"), FLAG_UI_HEADING | FLAG_DISABLE, nullptr); + item_append(_("Type"), sound.resampler_type_names(sound.resampler_type()), flag_type(), (void *)RS_TYPE); + item_append(_("HQ latency"), format_lat(sound.resampler_hq_latency()), flag_lat(), (void *)RS_LATENCY); + item_append(_("HQ filter max size"), format_u32(sound.resampler_hq_length()), flag_length(), (void *)RS_LENGTH); + item_append(_("HQ filter max phases"), format_u32(sound.resampler_hq_phases()), flag_phases(), (void *)RS_PHASES); + item_append(menu_item_type::SEPARATOR); +} + +void menu_audio_effects::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + menu::recompute_metrics(width, height, aspect); +} + +void menu_audio_effects::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x1, float y1, float x2, float y2) +{ +} + +void menu_audio_effects::menu_activated() +{ + // scripts or the other form of the menu could have changed something in the mean time + reset(reset_options::REMEMBER_POSITION); +} + +void menu_audio_effects::menu_deactivated() +{ +} + + +} // namespace ui + diff --git a/src/frontend/mame/ui/audioeffects.h b/src/frontend/mame/ui/audioeffects.h new file mode 100644 index 00000000000..f61d82fe825 --- /dev/null +++ b/src/frontend/mame/ui/audioeffects.h @@ -0,0 +1,53 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert +/*************************************************************************** + + ui/audioeffects.h + + Audio effects control + +***************************************************************************/ + +#ifndef MAME_FRONTEND_UI_AUDIOEFFECTS_H +#define MAME_FRONTEND_UI_AUDIOEFFECTS_H + +#pragma once + +#include "ui/menu.h" + + +namespace ui { + +class menu_audio_effects : public menu +{ +public: + menu_audio_effects(mame_ui_manager &mui, render_container &container); + virtual ~menu_audio_effects() override; + +protected: + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void menu_activated() override; + virtual void menu_deactivated() override; + +private: + enum { RS_TYPE, RS_LATENCY, RS_LENGTH, RS_PHASES }; + + virtual void populate() override; + virtual bool handle(event const *ev) override; + + u32 flag_type() const; + u32 flag_lat() const; + u32 flag_length() const; + u32 flag_phases() const; + + static double change_f(const double *table, double value, int change); + static u32 change_u32(const u32 *table, u32 value, int change); + + static std::string format_lat(double latency); + static std::string format_u32(u32 val); +}; + +} // namespace ui + +#endif // MAME_FRONTEND_UI_AUDIOEFFECTS_H diff --git a/src/frontend/mame/ui/audiomix.cpp b/src/frontend/mame/ui/audiomix.cpp new file mode 100644 index 00000000000..75b5ee334d5 --- /dev/null +++ b/src/frontend/mame/ui/audiomix.cpp @@ -0,0 +1,1053 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert +/********************************************************************* + + ui/audiomix.cpp + + Audio mixing/mapping control + +*********************************************************************/ + +#include "emu.h" +#include "ui/audiomix.h" + +#include "ui/ui.h" + +#include "osdepend.h" +#include "speaker.h" + +namespace ui { + +menu_audio_mixer::menu_audio_mixer(mame_ui_manager &mui, render_container &container) + : menu(mui, container) +{ + set_heading(_("Audio Mixer")); + m_generation = 0; + m_current_selection.m_maptype = MT_UNDEFINED; + m_current_selection.m_dev = nullptr; + m_current_selection.m_guest_channel = 0; + m_current_selection.m_node = 0; + m_current_selection.m_node_channel = 0; + m_current_group = GRP_NODE; + + set_process_flags(PROCESS_LR_ALWAYS); +} + +menu_audio_mixer::~menu_audio_mixer() +{ +} + +bool menu_audio_mixer::handle(event const *ev) +{ + if(!ev) { + if(m_generation != machine().sound().get_osd_info().m_generation) { + reset(reset_options::REMEMBER_POSITION); + return true; + } + return false; + } + + bool const alt_pressed = machine().input().code_pressed(KEYCODE_LALT) || machine().input().code_pressed(KEYCODE_RALT); + bool const ctrl_pressed = machine().input().code_pressed(KEYCODE_LCONTROL) || machine().input().code_pressed(KEYCODE_RCONTROL); + bool const shift_pressed = machine().input().code_pressed(KEYCODE_LSHIFT) || machine().input().code_pressed(KEYCODE_RSHIFT); + + switch(ev->iptkey) { + case IPT_UI_MIXER_ADD_FULL: + if(m_current_selection.m_maptype == MT_INTERNAL) + return false; + + if(full_mapping_available(m_current_selection.m_dev, 0)) { + m_current_selection.m_node = 0; + machine().sound().config_add_sound_io_connection_default(m_current_selection.m_dev, 0.0); + + } else { + uint32_t node = find_next_available_node(m_current_selection.m_dev, 0); + if(node == 0xffffffff) + return false; + m_current_selection.m_node = node; + machine().sound().config_add_sound_io_connection_node(m_current_selection.m_dev, find_node_name(node), 0.0); + } + + m_current_selection.m_maptype = MT_FULL; + m_current_selection.m_guest_channel = 0; + m_current_selection.m_node_channel = 0; + m_current_selection.m_db = 0.0; + m_generation --; + return true; + + case IPT_UI_MIXER_ADD_CHANNEL: { + if(m_current_selection.m_maptype == MT_INTERNAL) + return false; + + // Find a possible triplet, any triplet + const auto &info = machine().sound().get_osd_info(); + u32 guest_channel; + u32 node_index, node_id; + u32 node_channel; + u32 default_osd_id = m_current_selection.m_dev->is_output() ? info.m_default_sink : info.m_default_source; + for(node_index = default_osd_id == 0 ? 0 : 0xffffffff; node_index != info.m_nodes.size(); node_index++) { + node_id = node_index == 0xffffffff ? 0 : info.m_nodes[node_index].m_id; + u32 guest_channel_count = m_current_selection.m_dev->inputs(); + u32 node_channel_count = 0; + if(node_index == 0xffffffff) { + for(u32 i = 0; i != info.m_nodes.size(); i++) + if(info.m_nodes[i].m_id == default_osd_id) { + node_channel_count = m_current_selection.m_dev->is_output() ? info.m_nodes[i].m_sinks : info.m_nodes[i].m_sources; + break; + } + } else + node_channel_count = m_current_selection.m_dev->is_output() ? info.m_nodes[node_index].m_sinks : info.m_nodes[node_index].m_sources; + + for(guest_channel = 0; guest_channel != guest_channel_count; guest_channel ++) + for(node_channel = 0; node_channel != node_channel_count; node_channel ++) + if(channel_mapping_available(m_current_selection.m_dev, guest_channel, node_id, node_channel)) + goto found; + } + return false; + + found: + if(node_id) + machine().sound().config_add_sound_io_channel_connection_node(m_current_selection.m_dev, guest_channel, info.m_nodes[node_index].name(), node_channel, 0.0); + else + machine().sound().config_add_sound_io_channel_connection_default(m_current_selection.m_dev, guest_channel, node_channel, 0.0); + m_current_selection.m_maptype = MT_CHANNEL; + m_current_selection.m_guest_channel = guest_channel; + m_current_selection.m_node = node_id; + m_current_selection.m_node_channel = node_channel; + m_current_selection.m_db = 0.0; + m_generation --; + return true; + } + + case IPT_UI_CLEAR: { + if(m_current_selection.m_maptype == MT_NONE || m_current_selection.m_maptype == MT_INTERNAL) + return false; + + if(m_current_selection.m_maptype == MT_FULL) { + if(m_current_selection.m_node == 0) + machine().sound().config_remove_sound_io_connection_default(m_current_selection.m_dev); + else + machine().sound().config_remove_sound_io_connection_node(m_current_selection.m_dev, find_node_name(m_current_selection.m_node)); + } else { + if(m_current_selection.m_node == 0) + machine().sound().config_remove_sound_io_channel_connection_default(m_current_selection.m_dev, m_current_selection.m_guest_channel, m_current_selection.m_node_channel); + else + machine().sound().config_remove_sound_io_channel_connection_node(m_current_selection.m_dev, m_current_selection.m_guest_channel, find_node_name(m_current_selection.m_node), m_current_selection.m_node_channel); + } + + // Find where the selection was + uint32_t cursel_index = 0; + for(uint32_t i = 0; i != m_selections.size(); i++) + if(m_selections[i] == m_current_selection) { + cursel_index = i; + break; + } + + // If the next item exists and is the same speaker, go there (visually, the cursor stays on the same line) + // Otherwise if the previous item exists and is the same speaker, go there (visually, the cursor goes up once) + // Otherwise create a MT_NONE, because one is going to appear at the same place + + if(cursel_index + 1 < m_selections.size() && m_selections[cursel_index+1].m_dev == m_current_selection.m_dev) + m_current_selection = m_selections[cursel_index+1]; + else if(cursel_index != 0 && m_selections[cursel_index-1].m_dev == m_current_selection.m_dev) + m_current_selection = m_selections[cursel_index-1]; + else { + m_current_selection.m_maptype = MT_NONE; + m_current_selection.m_guest_channel = 0; + m_current_selection.m_node = 0; + m_current_selection.m_node_channel = 0; + m_current_selection.m_db = 0.0; + } + + m_generation --; + return true; + } + + case IPT_UI_UP: + case IPT_UI_DOWN: + if(!ev->itemref) { + m_current_selection.m_maptype = MT_INTERNAL; + m_generation --; + return true; + } + + m_current_selection = *(select_entry *)(ev->itemref); + if(m_current_selection.m_maptype == MT_FULL) { + if(m_current_group == GRP_GUEST_CHANNEL || m_current_group == GRP_NODE_CHANNEL) + m_current_group = GRP_NODE; + } + m_generation --; + return true; + + case IPT_UI_NEXT_GROUP: + if(m_current_selection.m_maptype == MT_NONE || m_current_selection.m_maptype == MT_INTERNAL) + return false; + + if(m_current_selection.m_maptype == MT_FULL) { + if(m_current_group == GRP_NODE) + m_current_group = GRP_DB; + else + m_current_group = GRP_NODE; + + } else if(m_current_selection.m_maptype == MT_CHANNEL) { + if(m_current_group == GRP_NODE) + m_current_group = GRP_NODE_CHANNEL; + else if(m_current_group == GRP_NODE_CHANNEL) + m_current_group = GRP_DB; + else if(m_current_group == GRP_DB) + m_current_group = GRP_GUEST_CHANNEL; + else + m_current_group = GRP_NODE; + } + m_generation --; + return true; + + case IPT_UI_PREV_GROUP: + if(m_current_selection.m_maptype == MT_NONE || m_current_selection.m_maptype == MT_INTERNAL) + return false; + + if(m_current_selection.m_maptype == MT_FULL) { + if(m_current_group == GRP_NODE) + m_current_group = GRP_DB; + else + m_current_group = GRP_NODE; + + } else if(m_current_selection.m_maptype == MT_CHANNEL) { + if(m_current_group == GRP_NODE) + m_current_group = GRP_GUEST_CHANNEL; + else if(m_current_group == GRP_GUEST_CHANNEL) + m_current_group = GRP_DB; + else if(m_current_group == GRP_DB) + m_current_group = GRP_NODE_CHANNEL; + else + m_current_group = GRP_NODE; + } + m_generation --; + return true; + + case IPT_UI_LEFT: { + if(m_current_selection.m_maptype == MT_NONE || m_current_selection.m_maptype == MT_INTERNAL) + return false; + + switch(m_current_group) { + case GRP_NODE: { + if(m_current_selection.m_maptype == MT_FULL) { + uint32_t prev_node = m_current_selection.m_node; + uint32_t next_node = find_previous_available_node(m_current_selection.m_dev, prev_node); + if(next_node != 0xffffffff) { + m_current_selection.m_node = next_node; + if(prev_node) + machine().sound().config_remove_sound_io_connection_node(m_current_selection.m_dev, find_node_name(prev_node)); + else + machine().sound().config_remove_sound_io_connection_default(m_current_selection.m_dev); + if(next_node) + machine().sound().config_add_sound_io_connection_node(m_current_selection.m_dev, find_node_name(next_node), m_current_selection.m_db); + else + machine().sound().config_add_sound_io_connection_default(m_current_selection.m_dev, m_current_selection.m_db); + m_generation --; + return true; + } + } else if(m_current_selection.m_maptype == MT_CHANNEL) { + uint32_t prev_node = m_current_selection.m_node; + uint32_t next_node = find_previous_available_channel_node(m_current_selection.m_dev, m_current_selection.m_guest_channel, prev_node, m_current_selection.m_node_channel); + if(next_node != 0xffffffff) { + m_current_selection.m_node = next_node; + if(prev_node) + machine().sound().config_remove_sound_io_channel_connection_node(m_current_selection.m_dev, m_current_selection.m_guest_channel, find_node_name(prev_node), m_current_selection.m_node_channel); + else + machine().sound().config_remove_sound_io_channel_connection_default(m_current_selection.m_dev, m_current_selection.m_guest_channel, m_current_selection.m_node_channel); + if(next_node) + machine().sound().config_add_sound_io_channel_connection_node(m_current_selection.m_dev, m_current_selection.m_guest_channel, find_node_name(next_node), m_current_selection.m_node_channel, m_current_selection.m_db); + else + machine().sound().config_add_sound_io_channel_connection_default(m_current_selection.m_dev, m_current_selection.m_guest_channel, m_current_selection.m_node_channel, m_current_selection.m_db); + m_generation --; + return true; + } + } + break; + } + + case GRP_DB: { + if(shift_pressed) + m_current_selection.m_db -= 0.1f; + else if(ctrl_pressed) + m_current_selection.m_db -= 10.0f; + else if(alt_pressed) { + if(m_current_selection.m_db > 0.0f) + m_current_selection.m_db = 0.0f; + else + m_current_selection.m_db = -96.0f; + } + else + m_current_selection.m_db -= 1.0f; + + m_current_selection.m_db = floorf(m_current_selection.m_db * 10.0f) / 10.0f; + m_current_selection.m_db = std::clamp(m_current_selection.m_db, -96.0f, 12.0f); + + if(m_current_selection.m_maptype == MT_FULL) { + if(m_current_selection.m_node == 0) + machine().sound().config_set_volume_sound_io_connection_default(m_current_selection.m_dev, m_current_selection.m_db); + else + machine().sound().config_set_volume_sound_io_connection_node(m_current_selection.m_dev, find_node_name(m_current_selection.m_node), m_current_selection.m_db); + } else { + if(m_current_selection.m_node == 0) + machine().sound().config_set_volume_sound_io_channel_connection_default(m_current_selection.m_dev, m_current_selection.m_guest_channel, m_current_selection.m_node_channel, m_current_selection.m_db); + else + machine().sound().config_set_volume_sound_io_channel_connection_node(m_current_selection.m_dev, m_current_selection.m_guest_channel, find_node_name(m_current_selection.m_node), m_current_selection.m_node_channel, m_current_selection.m_db); + } + m_generation --; + return true; + } + + case GRP_GUEST_CHANNEL: { + if(m_current_selection.m_maptype != MT_CHANNEL) + return false; + + u32 guest_channel_count = m_current_selection.m_dev->inputs(); + if(guest_channel_count == 1) + return false; + u32 guest_channel = m_current_selection.m_guest_channel; + for(;;) { + if(guest_channel == 0) + guest_channel = guest_channel_count - 1; + else + guest_channel --; + if(guest_channel == m_current_selection.m_guest_channel) + return false; + if(channel_mapping_available(m_current_selection.m_dev, guest_channel, m_current_selection.m_node, m_current_selection.m_node_channel)) { + if(m_current_selection.m_node) { + std::string node = find_node_name(m_current_selection.m_node); + machine().sound().config_remove_sound_io_channel_connection_node(m_current_selection.m_dev, m_current_selection.m_guest_channel, node, m_current_selection.m_node_channel); + machine().sound().config_add_sound_io_channel_connection_node(m_current_selection.m_dev, guest_channel, node, m_current_selection.m_node_channel, m_current_selection.m_db); + } else { + machine().sound().config_remove_sound_io_channel_connection_default(m_current_selection.m_dev, m_current_selection.m_guest_channel, m_current_selection.m_node_channel); + machine().sound().config_add_sound_io_channel_connection_default(m_current_selection.m_dev, guest_channel, m_current_selection.m_node_channel, m_current_selection.m_db); + } + m_current_selection.m_guest_channel = guest_channel; + m_generation --; + return true; + } + } + break; + } + + case GRP_NODE_CHANNEL: { + if(m_current_selection.m_maptype != MT_CHANNEL) + return false; + + u32 node_channel_count = find_node_channel_count(m_current_selection.m_node, m_current_selection.m_dev->is_output()); + if(node_channel_count == 1) + return false; + u32 node_channel = m_current_selection.m_node_channel; + for(;;) { + if(node_channel == 0) + node_channel = node_channel_count - 1; + else + node_channel --; + if(node_channel == m_current_selection.m_node_channel) + return false; + if(channel_mapping_available(m_current_selection.m_dev, m_current_selection.m_guest_channel, m_current_selection.m_node, node_channel)) { + if(m_current_selection.m_node) { + std::string node = find_node_name(m_current_selection.m_node); + machine().sound().config_remove_sound_io_channel_connection_node(m_current_selection.m_dev, m_current_selection.m_guest_channel, node, m_current_selection.m_node_channel); + machine().sound().config_add_sound_io_channel_connection_node(m_current_selection.m_dev, m_current_selection.m_guest_channel, node, node_channel, m_current_selection.m_db); + } else { + machine().sound().config_remove_sound_io_channel_connection_default(m_current_selection.m_dev, m_current_selection.m_guest_channel, m_current_selection.m_node_channel); + machine().sound().config_add_sound_io_channel_connection_default(m_current_selection.m_dev, m_current_selection.m_guest_channel, node_channel, m_current_selection.m_db); + } + m_current_selection.m_node_channel = node_channel; + m_generation --; + return true; + } + } + break; + } + } + break; + } + + case IPT_UI_RIGHT: { + if(m_current_selection.m_maptype == MT_NONE || m_current_selection.m_maptype == MT_INTERNAL) + return false; + + switch(m_current_group) { + case GRP_NODE: { + if(m_current_selection.m_maptype == MT_FULL) { + uint32_t prev_node = m_current_selection.m_node; + uint32_t next_node = find_next_available_node(m_current_selection.m_dev, prev_node); + if(next_node != 0xffffffff) { + m_current_selection.m_node = next_node; + if(prev_node) + machine().sound().config_remove_sound_io_connection_node(m_current_selection.m_dev, find_node_name(prev_node)); + else + machine().sound().config_remove_sound_io_connection_default(m_current_selection.m_dev); + if(next_node) + machine().sound().config_add_sound_io_connection_node(m_current_selection.m_dev, find_node_name(next_node), m_current_selection.m_db); + else + machine().sound().config_add_sound_io_connection_default(m_current_selection.m_dev, m_current_selection.m_db); + m_generation --; + return true; + } + } else if(m_current_selection.m_maptype == MT_CHANNEL) { + uint32_t prev_node = m_current_selection.m_node; + uint32_t next_node = find_next_available_channel_node(m_current_selection.m_dev, m_current_selection.m_guest_channel, prev_node, m_current_selection.m_node_channel); + if(next_node != 0xffffffff) { + m_current_selection.m_node = next_node; + if(prev_node) + machine().sound().config_remove_sound_io_channel_connection_node(m_current_selection.m_dev, m_current_selection.m_guest_channel, find_node_name(prev_node), m_current_selection.m_node_channel); + else + machine().sound().config_remove_sound_io_channel_connection_default(m_current_selection.m_dev, m_current_selection.m_guest_channel, m_current_selection.m_node_channel); + if(next_node) + machine().sound().config_add_sound_io_channel_connection_node(m_current_selection.m_dev, m_current_selection.m_guest_channel, find_node_name(next_node), m_current_selection.m_node_channel, m_current_selection.m_db); + else + machine().sound().config_add_sound_io_channel_connection_default(m_current_selection.m_dev, m_current_selection.m_guest_channel, m_current_selection.m_node_channel, m_current_selection.m_db); + m_generation --; + return true; + } + } + break; + } + + case GRP_DB: { + if(shift_pressed) + m_current_selection.m_db += 0.1f; + else if(ctrl_pressed) + m_current_selection.m_db += 10.0f; + else if(alt_pressed) { + if(m_current_selection.m_db < 0.0f) + m_current_selection.m_db = 0.0f; + else + m_current_selection.m_db = 12.0f; + } + else + m_current_selection.m_db += 1.0f; + + m_current_selection.m_db = floorf(m_current_selection.m_db * 10.0f) / 10.0f; + m_current_selection.m_db = std::clamp(m_current_selection.m_db, -96.0f, 12.0f); + + if(m_current_selection.m_maptype == MT_FULL) { + if(m_current_selection.m_node == 0) + machine().sound().config_set_volume_sound_io_connection_default(m_current_selection.m_dev, m_current_selection.m_db); + else + machine().sound().config_set_volume_sound_io_connection_node(m_current_selection.m_dev, find_node_name(m_current_selection.m_node), m_current_selection.m_db); + } else { + if(m_current_selection.m_node == 0) + machine().sound().config_set_volume_sound_io_channel_connection_default(m_current_selection.m_dev, m_current_selection.m_guest_channel, m_current_selection.m_node_channel, m_current_selection.m_db); + else + machine().sound().config_set_volume_sound_io_channel_connection_node(m_current_selection.m_dev, m_current_selection.m_guest_channel, find_node_name(m_current_selection.m_node), m_current_selection.m_node_channel, m_current_selection.m_db); + } + m_generation --; + return true; + } + + case GRP_GUEST_CHANNEL: { + if(m_current_selection.m_maptype != MT_CHANNEL) + return false; + + u32 guest_channel_count = m_current_selection.m_dev->inputs(); + if(guest_channel_count == 1) + return false; + u32 guest_channel = m_current_selection.m_guest_channel; + for(;;) { + guest_channel ++; + if(guest_channel == guest_channel_count) + guest_channel = 0; + if(guest_channel == m_current_selection.m_guest_channel) + return false; + if(channel_mapping_available(m_current_selection.m_dev, guest_channel, m_current_selection.m_node, m_current_selection.m_node_channel)) { + if(m_current_selection.m_node) { + std::string node = find_node_name(m_current_selection.m_node); + machine().sound().config_remove_sound_io_channel_connection_node(m_current_selection.m_dev, m_current_selection.m_guest_channel, node, m_current_selection.m_node_channel); + machine().sound().config_add_sound_io_channel_connection_node(m_current_selection.m_dev, guest_channel, node, m_current_selection.m_node_channel, m_current_selection.m_db); + } else { + machine().sound().config_remove_sound_io_channel_connection_default(m_current_selection.m_dev, m_current_selection.m_guest_channel, m_current_selection.m_node_channel); + machine().sound().config_add_sound_io_channel_connection_default(m_current_selection.m_dev, guest_channel, m_current_selection.m_node_channel, m_current_selection.m_db); + } + m_current_selection.m_guest_channel = guest_channel; + m_generation --; + return true; + } + } + break; + } + + case GRP_NODE_CHANNEL: { + if(m_current_selection.m_maptype != MT_CHANNEL) + return false; + + u32 node_channel_count = find_node_channel_count(m_current_selection.m_node, m_current_selection.m_dev->is_output()); + if(node_channel_count == 1) + return false; + u32 node_channel = m_current_selection.m_node_channel; + for(;;) { + node_channel ++; + if(node_channel == node_channel_count) + node_channel = 0; + if(node_channel == m_current_selection.m_node_channel) + return false; + if(channel_mapping_available(m_current_selection.m_dev, m_current_selection.m_guest_channel, m_current_selection.m_node, node_channel)) { + if(m_current_selection.m_node) { + std::string node = find_node_name(m_current_selection.m_node); + machine().sound().config_remove_sound_io_channel_connection_node(m_current_selection.m_dev, m_current_selection.m_guest_channel, node, m_current_selection.m_node_channel); + machine().sound().config_add_sound_io_channel_connection_node(m_current_selection.m_dev, m_current_selection.m_guest_channel, node, node_channel, m_current_selection.m_db); + } else { + machine().sound().config_remove_sound_io_channel_connection_default(m_current_selection.m_dev, m_current_selection.m_guest_channel, m_current_selection.m_node_channel); + machine().sound().config_add_sound_io_channel_connection_default(m_current_selection.m_dev, m_current_selection.m_guest_channel, node_channel, m_current_selection.m_db); + } + m_current_selection.m_node_channel = node_channel; + m_generation --; + return true; + } + } + break; + } + } + break; + } + } + + return false; +} + + +//------------------------------------------------- +// menu_audio_mixer_populate - populate the audio_mixer +// menu +//------------------------------------------------- + +void menu_audio_mixer::populate() +{ + const auto &mapping = machine().sound().get_mappings(); + const auto &info = machine().sound().get_osd_info(); + m_generation = info.m_generation; + + auto find_node = [&info](u32 node_id) -> const osd::audio_info::node_info * { + for(const auto &node : info.m_nodes) + if(node.m_id == node_id) + return &node; + // Never happens + return nullptr; + }; + + // Rebuild the selections list + m_selections.clear(); + for(const auto &omap : mapping) { + for(const auto &nmap : omap.m_node_mappings) + m_selections.emplace_back(select_entry { MT_FULL, omap.m_dev, 0, nmap.m_is_system_default ? 0 : nmap.m_node, 0, nmap.m_db }); + for(const auto &cmap : omap.m_channel_mappings) + m_selections.emplace_back(select_entry { MT_CHANNEL, omap.m_dev, cmap.m_guest_channel, cmap.m_is_system_default ? 0 : cmap.m_node, cmap.m_node_channel, cmap.m_db }); + if(omap.m_node_mappings.empty() && omap.m_channel_mappings.empty()) + m_selections.emplace_back(select_entry { MT_NONE, omap.m_dev, 0, 0, 0, 0 }); + } + + // If there's nothing, get out of there + if(m_selections.empty()) + return; + + // Find the line of the current selection, if any. + // Otherwise default to the first line + + u32 cursel_line = 0xffffffff; + + for(u32 i = 0; i != m_selections.size(); i++) { + if(m_current_selection == m_selections[i]) { + cursel_line = i; + break; + } + } + + if(cursel_line == 0xffffffff) + for(u32 i = 0; i != m_selections.size(); i++) { + if(m_current_selection.m_dev == m_selections[i].m_dev) { + cursel_line = i; + break; + } + } + + if(cursel_line == 0xffffffff) + cursel_line = 0; + + if(m_current_selection.m_maptype == MT_INTERNAL) + cursel_line = 0xffffffff; + else + m_current_selection = m_selections[cursel_line]; + + if(m_current_selection.m_maptype == MT_FULL) { + if(m_current_group == GRP_GUEST_CHANNEL || m_current_group == GRP_NODE_CHANNEL) + m_current_group = GRP_NODE; + } + + // (Re)build the menu + u32 curline = 0; + for(const auto &omap : mapping) { + item_append(omap.m_dev->tag(), FLAG_UI_HEADING | FLAG_DISABLE, nullptr); + for(const auto &nmap : omap.m_node_mappings) { + const auto &node = find_node(nmap.m_node); + std::string lnode = nmap.m_is_system_default || node->m_name == "" ? "[default]" : node->m_name; + if(!omap.m_dev->is_output() && node->m_sinks) + lnode = util::string_format("Monitor of %s", lnode); + if(curline == cursel_line && m_current_group == GRP_NODE) + lnode = u8"\u25c4" + lnode + u8"\u25ba"; + + std::string line = (omap.m_dev->is_output() ? "> " : "< ") + lnode; + + std::string db = util::string_format("%g dB", nmap.m_db); + if(curline == cursel_line && m_current_group == GRP_DB) + db = u8"\u25c4" + db + u8"\u25ba"; + + item_append(line, db, 0, m_selections.data() + curline); + curline ++; + } + for(const auto &cmap : omap.m_channel_mappings) { + const auto &node = find_node(cmap.m_node); + std::string guest_channel = omap.m_dev->get_position_name(cmap.m_guest_channel); + if(curline == cursel_line && m_current_group == GRP_GUEST_CHANNEL) + guest_channel = u8"\u25c4" + guest_channel + u8"\u25ba"; + + std::string lnode = cmap.m_is_system_default || node->m_name == "" ? "[default]" : node->m_name; + if(!omap.m_dev->is_output() && node->m_sinks) + lnode = util::string_format("Monitor of %s", lnode); + if(curline == cursel_line && m_current_group == GRP_NODE) + lnode = u8"\u25c4" + lnode + u8"\u25ba"; + + std::string lnode_channel = node->m_port_names[cmap.m_node_channel]; + if(curline == cursel_line && m_current_group == GRP_NODE_CHANNEL) + lnode_channel = u8"\u25c4" + lnode_channel + u8"\u25ba"; + + std::string line = guest_channel + " > " + lnode + ":" + lnode_channel; + + std::string db = util::string_format("%g dB", cmap.m_db); + if(curline == cursel_line && m_current_group == GRP_DB) + db = u8"\u25c4" + db + u8"\u25ba"; + + item_append(line, db, 0, m_selections.data() + curline); + curline ++; + } + if(omap.m_node_mappings.empty() && omap.m_channel_mappings.empty()) { + item_append("[no mapping]", 0, m_selections.data() + curline); + curline ++; + } + } + item_append(menu_item_type::SEPARATOR); + item_append(util::string_format("%s: add a full mapping", ui().get_general_input_setting(IPT_UI_MIXER_ADD_FULL)), FLAG_DISABLE, nullptr); + item_append(util::string_format("%s: add a channel mapping", ui().get_general_input_setting(IPT_UI_MIXER_ADD_CHANNEL)), FLAG_DISABLE, nullptr); + item_append(util::string_format("%s: remove a mapping", ui().get_general_input_setting(IPT_UI_CLEAR)), FLAG_DISABLE, nullptr); + item_append(menu_item_type::SEPARATOR); + + if(cursel_line != 0xffffffff) + set_selection(m_selections.data() + cursel_line); +} + + +//------------------------------------------------- +// recompute_metrics - recompute metrics +//------------------------------------------------- + +void menu_audio_mixer::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + menu::recompute_metrics(width, height, aspect); + + // set_custom_space(0.0f, 2.0f * line_height() + 2.0f * tb_border()); +} + + +//------------------------------------------------- +// menu_audio_mixer_custom_render - perform our special +// rendering +//------------------------------------------------- + +void menu_audio_mixer::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x1, float y1, float x2, float y2) +{ +} + + +//------------------------------------------------- +// menu_activated - handle menu gaining focus +//------------------------------------------------- + +void menu_audio_mixer::menu_activated() +{ + // scripts or the other form of the menu could have changed something in the mean time + reset(reset_options::REMEMBER_POSITION); +} + + +//------------------------------------------------- +// menu_deactivated - handle menu losing focus +//------------------------------------------------- + +void menu_audio_mixer::menu_deactivated() +{ +} + +uint32_t menu_audio_mixer::find_node_index(uint32_t node) const +{ + const auto &info = machine().sound().get_osd_info(); + for(uint32_t i = 0; i != info.m_nodes.size(); i++) + if(info.m_nodes[i].m_id == node) + return i; + // Can't happen in theory + return 0xffffffff; +} + +std::string menu_audio_mixer::find_node_name(uint32_t node) const +{ + const auto &info = machine().sound().get_osd_info(); + for(uint32_t i = 0; i != info.m_nodes.size(); i++) + if(info.m_nodes[i].m_id == node) + return info.m_nodes[i].name(); + // Can't happen in theory + return ""; +} + +uint32_t menu_audio_mixer::find_node_channel_count(uint32_t node, bool is_output) const +{ + const auto &info = machine().sound().get_osd_info(); + if(!node) + node = info.m_default_sink; + for(uint32_t i = 0; i != info.m_nodes.size(); i++) + if(info.m_nodes[i].m_id == node) + return is_output ? info.m_nodes[i].m_sinks : info.m_nodes[i].m_sources; + // Can't happen in theory + return 0; +} + +uint32_t menu_audio_mixer::find_next_sink_node_index(uint32_t index) const +{ + if(index == 0xffffffff) + return index; + + const auto &info = machine().sound().get_osd_info(); + for(uint32_t idx = index + 1; idx != info.m_nodes.size(); idx++) + if(info.m_nodes[idx].m_sinks) + return idx; + return 0xffffffff; +} + +uint32_t menu_audio_mixer::find_next_source_node_index(uint32_t index) const +{ + if(index == 0xffffffff) + return index; + + const auto &info = machine().sound().get_osd_info(); + for(uint32_t idx = index + 1; idx != info.m_nodes.size(); idx++) + if(info.m_nodes[idx].m_sources) + return idx; + return 0xffffffff; +} + +uint32_t menu_audio_mixer::find_previous_sink_node_index(uint32_t index) const +{ + if(index == 0xffffffff) + return index; + + const auto &info = machine().sound().get_osd_info(); + for(uint32_t idx = index - 1; idx != 0xffffffff; idx--) + if(info.m_nodes[idx].m_sinks) + return idx; + return 0xffffffff; +} + +uint32_t menu_audio_mixer::find_previous_source_node_index(uint32_t index) const +{ + if(index == 0xffffffff) + return index; + + const auto &info = machine().sound().get_osd_info(); + for(uint32_t idx = index - 1; idx != 0xffffffff; idx--) + if(info.m_nodes[idx].m_sources) + return idx; + return 0xffffffff; +} + +uint32_t menu_audio_mixer::find_first_sink_node_index() const +{ + const auto &info = machine().sound().get_osd_info(); + for(uint32_t index = 0; index != info.m_nodes.size(); index ++) + if(info.m_nodes[index].m_sinks) + return index; + return 0xffffffff; +} + +uint32_t menu_audio_mixer::find_first_source_node_index() const +{ + const auto &info = machine().sound().get_osd_info(); + for(uint32_t index = 0; index != info.m_nodes.size(); index ++) + if(info.m_nodes[index].m_sources) + return index; + return 0xffffffff; +} + +uint32_t menu_audio_mixer::find_last_sink_node_index() const +{ + const auto &info = machine().sound().get_osd_info(); + for(uint32_t index = info.m_nodes.size() - 1; index != 0xffffffff; index --) + if(info.m_nodes[index].m_sinks) + return index; + return 0xffffffff; +} + +uint32_t menu_audio_mixer::find_last_source_node_index() const +{ + const auto &info = machine().sound().get_osd_info(); + for(uint32_t index = info.m_nodes.size() - 1; index != 0xffffffff; index --) + if(info.m_nodes[index].m_sources) + return index; + return 0xffffffff; +} + +bool menu_audio_mixer::full_mapping_available(sound_io_device *dev, uint32_t node) const +{ + if(dev->is_output() && !node && machine().sound().get_osd_info().m_default_sink == 0) + return false; + if(!dev->is_output() && !node && machine().sound().get_osd_info().m_default_source == 0) + return false; + + const auto &mapping = machine().sound().get_mappings(); + for(const auto &omap : mapping) + if(omap.m_dev == dev) { + for(const auto &nmap : omap.m_node_mappings) + if((node != 0 && nmap.m_node == node && !nmap.m_is_system_default) || (node == 0 && nmap.m_is_system_default)) + return false; + return true; + } + return true; +} + +bool menu_audio_mixer::channel_mapping_available(sound_io_device *dev, uint32_t guest_channel, uint32_t node, uint32_t node_channel) const +{ + if(dev->is_output() && !node && machine().sound().get_osd_info().m_default_sink == 0) + return false; + if(!dev->is_output() && !node && machine().sound().get_osd_info().m_default_source == 0) + return false; + + const auto &mapping = machine().sound().get_mappings(); + for(const auto &omap : mapping) + if(omap.m_dev == dev) { + for(const auto &cmap : omap.m_channel_mappings) + if(cmap.m_guest_channel == guest_channel && + ((node != 0 && cmap.m_node == node && !cmap.m_is_system_default) || (node == 0 && cmap.m_is_system_default)) + && cmap.m_node_channel == node_channel) + return false; + return true; + } + return true; +} + +uint32_t menu_audio_mixer::find_next_available_node(sound_io_device *dev, uint32_t node) const +{ + const auto &info = machine().sound().get_osd_info(); + + if(dev->is_output()) { + if(node == 0) { + uint32_t index = find_first_sink_node_index(); + while(index != 0xffffffff && !full_mapping_available(dev, info.m_nodes[index].m_id)) + index = find_next_sink_node_index(index); + return index == 0xffffffff ? 0xffffffff : info.m_nodes[index].m_id; + } + + uint32_t index = find_node_index(node); + while(index != 0xffffffff) { + index = find_next_sink_node_index(index); + if(index != 0xffffffff && full_mapping_available(dev, info.m_nodes[index].m_id)) + return info.m_nodes[index].m_id; + } + + if(info.m_default_sink != 0 && full_mapping_available(dev, 0)) + return 0; + + index = find_first_sink_node_index(); + while(index != 0xffffffff && !full_mapping_available(dev, info.m_nodes[index].m_id)) + index = find_next_sink_node_index(index); + return index == 0xffffffff ? 0xffffffff : info.m_nodes[index].m_id; + } else { + if(node == 0) { + uint32_t index = find_first_source_node_index(); + while(index != 0xffffffff && !full_mapping_available(dev, info.m_nodes[index].m_id)) + index = find_next_source_node_index(index); + return index == 0xffffffff ? 0xffffffff : info.m_nodes[index].m_id; + } + + uint32_t index = find_node_index(node); + while(index != 0xffffffff) { + index = find_next_source_node_index(index); + if(index != 0xffffffff && full_mapping_available(dev, info.m_nodes[index].m_id)) + return info.m_nodes[index].m_id; + } + + if(info.m_default_source != 0 && full_mapping_available(dev, 0)) + return 0; + + index = find_first_source_node_index(); + while(index != 0xffffffff && !full_mapping_available(dev, info.m_nodes[index].m_id)) + index = find_next_source_node_index(index); + return index == 0xffffffff ? 0xffffffff : info.m_nodes[index].m_id; + } +} + +uint32_t menu_audio_mixer::find_previous_available_node(sound_io_device *dev, uint32_t node) const +{ + const auto &info = machine().sound().get_osd_info(); + + if(dev->is_output()) { + if(node == 0) { + uint32_t index = find_last_sink_node_index(); + while(index != 0xffffffff && !full_mapping_available(dev, info.m_nodes[index].m_id)) + index = find_previous_sink_node_index(index); + return index == 0xffffffff ? 0xffffffff : info.m_nodes[index].m_id; + } + + uint32_t index = find_node_index(node); + while(index != 0xffffffff) { + index = find_previous_sink_node_index(index); + if(index != 0xffffffff && full_mapping_available(dev, info.m_nodes[index].m_id)) + return info.m_nodes[index].m_id; + } + + if(info.m_default_sink != 0 && full_mapping_available(dev, 0)) + return 0; + + index = find_last_sink_node_index(); + while(index != 0xffffffff && !full_mapping_available(dev, info.m_nodes[index].m_id)) + index = find_previous_sink_node_index(index); + return index == 0xffffffff ? 0xffffffff : info.m_nodes[index].m_id; + + } else { + if(node == 0) { + uint32_t index = find_last_source_node_index(); + while(index != 0xffffffff && !full_mapping_available(dev, info.m_nodes[index].m_id)) + index = find_previous_source_node_index(index); + return index == 0xffffffff ? 0xffffffff : info.m_nodes[index].m_id; + } + + uint32_t index = find_node_index(node); + while(index != 0xffffffff) { + index = find_previous_source_node_index(index); + if(index != 0xffffffff && full_mapping_available(dev, info.m_nodes[index].m_id)) + return info.m_nodes[index].m_id; + } + + if(info.m_default_source != 0 && full_mapping_available(dev, 0)) + return 0; + + index = find_last_source_node_index(); + while(index != 0xffffffff && !full_mapping_available(dev, info.m_nodes[index].m_id)) + index = find_previous_source_node_index(index); + return index == 0xffffffff ? 0xffffffff : info.m_nodes[index].m_id; + } +} + +uint32_t menu_audio_mixer::find_next_available_channel_node(sound_io_device *dev, uint32_t guest_channel, uint32_t node, uint32_t node_channel) const +{ + const auto &info = machine().sound().get_osd_info(); + + if(dev->is_output()) { + if(node == 0) { + uint32_t index = find_first_sink_node_index(); + while(index != 0xffffffff && !channel_mapping_available(dev, guest_channel, info.m_nodes[index].m_id, node_channel)) + index = find_next_sink_node_index(index); + return index == 0xffffffff ? 0xffffffff : info.m_nodes[index].m_id; + } + + uint32_t index = find_node_index(node); + while(index != 0xffffffff) { + index = find_next_sink_node_index(index); + if(index != 0xffffffff && channel_mapping_available(dev, guest_channel, info.m_nodes[index].m_id, node_channel)) + return info.m_nodes[index].m_id; + } + + if(dev->is_output() && info.m_default_sink != 0 && channel_mapping_available(dev, guest_channel, 0, node_channel)) + return 0; + if(!dev->is_output() && info.m_default_source != 0 && channel_mapping_available(dev, guest_channel, 0, node_channel)) + return 0; + + index = find_first_sink_node_index(); + while(index != 0xffffffff && !channel_mapping_available(dev, guest_channel, info.m_nodes[index].m_id, node_channel)) + index = find_next_sink_node_index(index); + return index == 0xffffffff ? 0xffffffff : info.m_nodes[index].m_id; + + } else { + if(node == 0) { + uint32_t index = find_first_source_node_index(); + while(index != 0xffffffff && !channel_mapping_available(dev, guest_channel, info.m_nodes[index].m_id, node_channel)) + index = find_next_source_node_index(index); + return index == 0xffffffff ? 0xffffffff : info.m_nodes[index].m_id; + } + + uint32_t index = find_node_index(node); + while(index != 0xffffffff) { + index = find_next_source_node_index(index); + if(index != 0xffffffff && channel_mapping_available(dev, guest_channel, info.m_nodes[index].m_id, node_channel)) + return info.m_nodes[index].m_id; + } + + if(dev->is_output() && info.m_default_source != 0 && channel_mapping_available(dev, guest_channel, 0, node_channel)) + return 0; + if(!dev->is_output() && info.m_default_source != 0 && channel_mapping_available(dev, guest_channel, 0, node_channel)) + return 0; + + index = find_first_source_node_index(); + while(index != 0xffffffff && !channel_mapping_available(dev, guest_channel, info.m_nodes[index].m_id, node_channel)) + index = find_next_source_node_index(index); + return index == 0xffffffff ? 0xffffffff : info.m_nodes[index].m_id; + } +} + +uint32_t menu_audio_mixer::find_previous_available_channel_node(sound_io_device *dev, uint32_t guest_channel, uint32_t node, uint32_t node_channel) const +{ + const auto &info = machine().sound().get_osd_info(); + + if(dev->is_output()) { + if(node == 0) { + uint32_t index = find_last_sink_node_index(); + while(index != 0xffffffff && !channel_mapping_available(dev, guest_channel, info.m_nodes[index].m_id, node_channel)) + index = find_previous_sink_node_index(index); + return index == 0xffffffff ? 0xffffffff : info.m_nodes[index].m_id; + } + + uint32_t index = find_node_index(node); + while(index != 0xffffffff) { + index = find_previous_sink_node_index(index); + if(index != 0xffffffff && channel_mapping_available(dev, guest_channel, info.m_nodes[index].m_id, node_channel)) + return info.m_nodes[index].m_id; + } + + if(dev->is_output() && info.m_default_sink != 0 && channel_mapping_available(dev, guest_channel, 0, node_channel)) + return 0; + if(!dev->is_output() && info.m_default_source != 0 && channel_mapping_available(dev, guest_channel, 0, node_channel)) + return 0; + + index = find_last_sink_node_index(); + while(index != 0xffffffff && !channel_mapping_available(dev, guest_channel, info.m_nodes[index].m_id, node_channel)) + index = find_previous_sink_node_index(index); + return index == 0xffffffff ? 0xffffffff : info.m_nodes[index].m_id; + + } else { + if(node == 0) { + uint32_t index = find_last_source_node_index(); + while(index != 0xffffffff && !channel_mapping_available(dev, guest_channel, info.m_nodes[index].m_id, node_channel)) + index = find_previous_source_node_index(index); + return index == 0xffffffff ? 0xffffffff : info.m_nodes[index].m_id; + } + + uint32_t index = find_node_index(node); + while(index != 0xffffffff) { + index = find_previous_source_node_index(index); + if(index != 0xffffffff && channel_mapping_available(dev, guest_channel, info.m_nodes[index].m_id, node_channel)) + return info.m_nodes[index].m_id; + } + + if(dev->is_output() && info.m_default_source != 0 && channel_mapping_available(dev, guest_channel, 0, node_channel)) + return 0; + if(!dev->is_output() && info.m_default_source != 0 && channel_mapping_available(dev, guest_channel, 0, node_channel)) + return 0; + + index = find_last_source_node_index(); + while(index != 0xffffffff && !channel_mapping_available(dev, guest_channel, info.m_nodes[index].m_id, node_channel)) + index = find_previous_source_node_index(index); + return index == 0xffffffff ? 0xffffffff : info.m_nodes[index].m_id; + } +} + +} // namespace ui + diff --git a/src/frontend/mame/ui/audiomix.h b/src/frontend/mame/ui/audiomix.h new file mode 100644 index 00000000000..5f8468f01f0 --- /dev/null +++ b/src/frontend/mame/ui/audiomix.h @@ -0,0 +1,95 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert +/*************************************************************************** + + ui/audiomix.h + + Audio mixing/mapping control + +***************************************************************************/ + +#ifndef MAME_FRONTEND_UI_AUDIOMIX_H +#define MAME_FRONTEND_UI_AUDIOMIX_H + +#pragma once + +#include "ui/menu.h" + + +namespace ui { + +class menu_audio_mixer : public menu +{ +public: + menu_audio_mixer(mame_ui_manager &mui, render_container &container); + virtual ~menu_audio_mixer() override; + +protected: + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void menu_activated() override; + virtual void menu_deactivated() override; + +private: + enum { + MT_UNDEFINED, // At startup + MT_NONE, // [no mapping] + MT_FULL, // Full mapping to node + MT_CHANNEL, // Channel-to-channel mapping + MT_INTERNAL // Go back to previous menu or other non-mapping entry + }; + + enum { + GRP_GUEST_CHANNEL, + GRP_NODE, + GRP_NODE_CHANNEL, + GRP_DB + }; + + struct select_entry { + u32 m_maptype; + sound_io_device *m_dev; + u32 m_guest_channel; + u32 m_node; + u32 m_node_channel; + float m_db; + + inline bool operator ==(const select_entry &sel) { + return sel.m_maptype == m_maptype && sel.m_dev == m_dev && sel.m_guest_channel == m_guest_channel && sel.m_node == m_node && sel.m_node_channel == m_node_channel; + } + }; + + uint32_t m_generation; + select_entry m_current_selection; + uint32_t m_current_group; + std::vector<select_entry> m_selections; + + virtual void populate() override; + virtual bool handle(event const *ev) override; + + uint32_t find_node_index(uint32_t node) const; + std::string find_node_name(uint32_t node) const; + uint32_t find_node_channel_count(uint32_t node, bool is_output) const; + + uint32_t find_next_sink_node_index(uint32_t index) const; + uint32_t find_next_source_node_index(uint32_t index) const; + uint32_t find_previous_sink_node_index(uint32_t index) const; + uint32_t find_previous_source_node_index(uint32_t index) const; + + uint32_t find_first_sink_node_index() const; + uint32_t find_first_source_node_index() const; + uint32_t find_last_sink_node_index() const; + uint32_t find_last_source_node_index() const; + + bool full_mapping_available(sound_io_device *dev, uint32_t node) const; + bool channel_mapping_available(sound_io_device *dev, uint32_t guest_channel, uint32_t node, uint32_t node_channel) const; + + uint32_t find_next_available_node(sound_io_device *dev, uint32_t node) const; + uint32_t find_previous_available_node(sound_io_device *dev, uint32_t node) const; + uint32_t find_next_available_channel_node(sound_io_device *dev, uint32_t guest_channel, uint32_t node, uint32_t node_channel) const; + uint32_t find_previous_available_channel_node(sound_io_device *dev, uint32_t guest_channel, uint32_t node, uint32_t node_channel) const; +}; + +} // namespace ui + +#endif // MAME_FRONTEND_UI_AUDIOMIX_H diff --git a/src/frontend/mame/ui/auditmenu.cpp b/src/frontend/mame/ui/auditmenu.cpp index 2289762ae49..22d4eb9e8c2 100644 --- a/src/frontend/mame/ui/auditmenu.cpp +++ b/src/frontend/mame/ui/auditmenu.cpp @@ -11,12 +11,21 @@ #include "emu.h" #include "ui/auditmenu.h" +#include "ui/systemlist.h" #include "ui/ui.h" #include "audit.h" + #include "drivenum.h" +#include "fileio.h" +#include "main.h" +#include "uiinput.h" + +#include "util/corestr.h" #include <numeric> +#include <sstream> +#include <thread> extern const char UI_VERSION_TAG[]; @@ -25,173 +34,192 @@ namespace ui { namespace { -void *const ITEMREF_START = reinterpret_cast<void *>(std::uintptr_t(1)); +void *const ITEMREF_START_FULL = reinterpret_cast<void *>(std::uintptr_t(1)); +void *const ITEMREF_START_FAST = reinterpret_cast<void *>(std::uintptr_t(2)); } // anonymous namespace -bool sorted_game_list(const game_driver *x, const game_driver *y) -{ - bool clonex = (x->parent[0] != '0') || x->parent[1]; - int cx = -1; - if (clonex) - { - cx = driver_list::find(x->parent); - if ((0 > cx) || (driver_list::driver(cx).flags & machine_flags::IS_BIOS_ROOT)) - clonex = false; - } - - bool cloney = (y->parent[0] != '0') || y->parent[1]; - int cy = -1; - if (cloney) - { - cy = driver_list::find(y->parent); - if ((0 > cy) || (driver_list::driver(cy).flags & machine_flags::IS_BIOS_ROOT)) - cloney = false; - } - - if (!clonex && !cloney) - { - return (core_stricmp(x->type.fullname(), y->type.fullname()) < 0); - } - else if (clonex && cloney) - { - if (!core_stricmp(x->parent, y->parent)) - return (core_stricmp(x->type.fullname(), y->type.fullname()) < 0); - else - return (core_stricmp(driver_list::driver(cx).type.fullname(), driver_list::driver(cy).type.fullname()) < 0); - } - else if (!clonex && cloney) - { - if (!core_stricmp(x->name, y->parent)) - return true; - else - return (core_stricmp(x->type.fullname(), driver_list::driver(cy).type.fullname()) < 0); - } - else - { - if (!core_stricmp(x->parent, y->name)) - return false; - else - return (core_stricmp(driver_list::driver(cx).type.fullname(), y->type.fullname()) < 0); - } -} - - -menu_audit::menu_audit(mame_ui_manager &mui, render_container &container, std::vector<ui_system_info> &availablesorted, mode audit_mode) +menu_audit::menu_audit(mame_ui_manager &mui, render_container &container) : menu(mui, container) - , m_worker_thread() - , m_audit_mode(audit_mode) - , m_total((mode::FAST == audit_mode) - ? std::accumulate(availablesorted.begin(), availablesorted.end(), std::size_t(0), [] (std::size_t n, ui_system_info const &info) { return n + (info.available ? 0 : 1); }) - : availablesorted.size()) - , m_availablesorted(availablesorted) + , m_availablesorted(system_list::instance().sorted_list()) + , m_unavailable( + std::accumulate( + m_availablesorted.begin(), + m_availablesorted.end(), + std::size_t(0), + [] (std::size_t n, ui_system_info const &info) { return n + (info.available ? 0 : 1); })) + , m_future() + , m_next(0) , m_audited(0) , m_current(nullptr) - , m_phase(phase::CONSENT) + , m_cancel(false) + , m_phase(phase::CONFIRMATION) + , m_fast(true) { - switch (m_audit_mode) - { - case mode::FAST: - m_prompt[0] = util::string_format(_("Audit ROMs for %1$u machines marked unavailable?"), m_total); - break; - case mode::ALL: - m_prompt[0] = util::string_format(_("Audit ROMs for all %1$u machines?"), m_total); - break; - } + set_heading(_("Audit Media")); + std::string filename(emulator_info::get_configname()); filename += "_avail.ini"; - m_prompt[1] = util::string_format(_("(results will be saved to %1$s)"), filename); + m_prompt = util::string_format(_("Results will be saved to %1$s"), filename); } menu_audit::~menu_audit() { + m_cancel.store(true); + for (auto &future : m_future) + { + if (future.valid()) + future.wait(); + } +} + + +void menu_audit::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + menu::recompute_metrics(width, height, aspect); + + set_custom_space(0.0F, (line_height() * 1.0F) + (tb_border() * 3.0F)); } -void menu_audit::custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) + +void menu_audit::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { switch (m_phase) { - case phase::CONSENT: - draw_text_box( - std::begin(m_prompt), std::end(m_prompt), - x, x2, y - top, y - ui().box_tb_border(), - ui::text_layout::CENTER, ui::text_layout::NEVER, false, - ui().colors().text_color(), UI_GREEN_COLOR, 1.0f); + case phase::CONFIRMATION: + if ((ITEMREF_START_FAST == selectedref) || (ITEMREF_START_FULL == selectedref)) + { + draw_text_box( + &m_prompt, &m_prompt + 1, + origx1, origx2, origy2 + tb_border(), origy2 + bottom, + text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, false, + ui().colors().text_color(), UI_GREEN_COLOR); + } break; case phase::AUDIT: { // there's a race here between the total audited being updated and the next driver pointer being loaded // it doesn't matter because we redraw on every frame anyway so it sorts itself out very quickly - game_driver const *const driver(m_current.load()); + ui_system_info const *const system(m_current.load()); std::size_t const audited(m_audited.load()); - std::string const text(util::string_format( - _("Auditing ROMs for machine %2$u of %3$u...\n%1$s"), - driver ? driver->type.fullname() : "", - audited + 1, - m_total)); - ui().draw_text_box(container(), text.c_str(), ui::text_layout::CENTER, 0.5f, 0.5f, UI_GREEN_COLOR); + std::size_t const total(m_fast ? m_unavailable : m_availablesorted.size()); + std::ostringstream text; + util::stream_format(text, + _("Auditing media for system %2$u of %3$u...\n%1$s"), + system ? std::string_view(system->description) : std::string_view(), + (std::min)(audited + 1, total), + total); + text << '\n' << m_prompt; + ui().draw_text_box( + container(), + std::move(text).str(), + text_layout::text_justify::CENTER, + 0.5F, 0.5F, + ui().colors().background_color()); } break; + + case phase::CANCELLATION: + ui().draw_text_box( + container(), + util::string_format( + _("Cancel audit?\n\nPress %1$s to cancel\nPress %2$s to continue"), + ui().get_general_input_setting(IPT_UI_SELECT), + ui().get_general_input_setting(IPT_UI_BACK)), + text_layout::text_justify::CENTER, + 0.5F, 0.5F, + UI_RED_COLOR); + break; } } -void menu_audit::populate(float &customtop, float &custombottom) + +bool menu_audit::custom_ui_back() +{ + return m_phase != phase::CONFIRMATION; +} + + +void menu_audit::populate() { - item_append(_("Start Audit"), "", 0, ITEMREF_START); - customtop = (ui().get_line_height() * 2.0f) + (ui().box_tb_border() * 3.0f); + if (m_unavailable && (m_availablesorted.size() != m_unavailable)) + item_append(util::string_format(_("Audit media for %1$u systems marked unavailable"), m_unavailable), 0, ITEMREF_START_FAST); + item_append(util::string_format(_("Audit media for all %1$u systems"), m_availablesorted.size()), 0, ITEMREF_START_FULL); + item_append(menu_item_type::SEPARATOR, 0); } -void menu_audit::handle() +bool menu_audit::handle(event const *ev) { switch (m_phase) { - case phase::CONSENT: + case phase::CONFIRMATION: + if (ev && (IPT_UI_SELECT == ev->iptkey)) { - event const *const menu_event(process(0)); - if (menu_event && (ITEMREF_START == menu_event->itemref) && (IPT_UI_SELECT == menu_event->iptkey)) + if ((ITEMREF_START_FULL == ev->itemref) || (ITEMREF_START_FAST == ev->itemref)) { + set_process_flags(PROCESS_CUSTOM_ONLY | PROCESS_NOINPUT); m_phase = phase::AUDIT; - m_worker_thread = std::thread( - [this] () - { - switch (m_audit_mode) - { - case mode::FAST: - audit_fast(); - return; - case mode::ALL: - audit_all(); - return; - } - throw false; - }); + m_fast = ITEMREF_START_FAST == ev->itemref; + m_prompt = util::string_format(_("Press %1$s to cancel\n"), ui().get_general_input_setting(IPT_UI_BACK)); + m_future.resize(std::thread::hardware_concurrency()); + for (auto &future : m_future) + future = std::async(std::launch::async, [this] () { return do_audit(); }); + return true; } } break; case phase::AUDIT: - process(PROCESS_CUSTOM_ONLY | PROCESS_NOINPUT); - - if (m_audited.load() >= m_total) + case phase::CANCELLATION: + if ((m_next.load() >= m_availablesorted.size()) || m_cancel.load()) { - m_worker_thread.join(); - save_available_machines(); - reset_parent(reset_options::SELECT_FIRST); + bool done(true); + for (auto &future : m_future) + done = future.get() && done; + m_future.clear(); + if (done) + { + save_available_machines(); + reset_parent(reset_options::SELECT_FIRST); + } stack_pop(); } + else if (machine().ui_input().pressed(IPT_UI_BACK)) + { + if (phase::AUDIT == m_phase) + m_phase = phase::CANCELLATION; + else + m_phase = phase::AUDIT; + return true; + } + else if ((phase::CANCELLATION == m_phase) && machine().ui_input().pressed(IPT_UI_SELECT)) + { + m_cancel.store(true); + return true; + } break; } + + return false; } -void menu_audit::audit_fast() +bool menu_audit::do_audit() { - for (ui_system_info &info : m_availablesorted) + while (true) { - if (!info.available) + std::size_t const i(m_next.fetch_add(1)); + if (m_availablesorted.size() <= i) + return true; + + ui_system_info &info(m_availablesorted[i]); + if (!m_fast || !info.available) { - m_current.store(info.driver); + if (m_cancel.load()) + return false; + + m_current.store(&info); driver_enumerator enumerator(machine().options(), info.driver->name); enumerator.next(); media_auditor auditor(enumerator); @@ -205,30 +233,11 @@ void menu_audit::audit_fast() } } -void menu_audit::audit_all() -{ - driver_enumerator enumerator(machine().options()); - media_auditor auditor(enumerator); - std::vector<bool> available(driver_list::total(), false); - while (enumerator.next()) - { - m_current.store(&enumerator.driver()); - media_auditor::summary const summary(auditor.audit_media(AUDIT_VALIDATE_FAST)); - - // if everything looks good, include the driver - available[enumerator.current()] = (summary == media_auditor::CORRECT) || (summary == media_auditor::BEST_AVAILABLE) || (summary == media_auditor::NONE_NEEDED); - ++m_audited; - } - - for (ui_system_info &info : m_availablesorted) - info.available = available[info.index]; -} - void menu_audit::save_available_machines() { // attempt to open the output file emu_file file(ui().options().ui_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); - if (file.open(emulator_info::get_configname(), "_avail.ini") == osd_file::error::NONE) + if (!file.open(std::string(emulator_info::get_configname()) + "_avail.ini")) { // generate header file.printf("#\n%s%s\n#\n\n", UI_VERSION_TAG, emulator_info::get_bare_build_version()); diff --git a/src/frontend/mame/ui/auditmenu.h b/src/frontend/mame/ui/auditmenu.h index 435bf52cd34..7341340804a 100644 --- a/src/frontend/mame/ui/auditmenu.h +++ b/src/frontend/mame/ui/auditmenu.h @@ -16,7 +16,7 @@ #include "ui/utils.h" #include <atomic> -#include <thread> +#include <future> #include <vector> @@ -25,36 +25,35 @@ namespace ui { class menu_audit : public menu { public: - enum class mode { FAST, ALL }; - - menu_audit(mame_ui_manager &mui, render_container &container, std::vector<ui_system_info> &availablesorted, mode audit_mode); + menu_audit(mame_ui_manager &mui, render_container &container); virtual ~menu_audit() override; protected: - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) override; + virtual bool custom_ui_back() override; private: - enum class phase { CONSENT, AUDIT }; + enum class phase { CONFIRMATION, AUDIT, CANCELLATION }; - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; + virtual bool handle(event const *ev) override; - void audit_fast(); - void audit_all(); + bool do_audit(); void save_available_machines(); - std::thread m_worker_thread; - mode const m_audit_mode; - std::size_t const m_total; - std::string m_prompt[2]; - std::vector<ui_system_info> &m_availablesorted; + std::string m_prompt; + std::vector<std::reference_wrapper<ui_system_info> > const &m_availablesorted; + std::size_t const m_unavailable; + std::vector<std::future<bool> > m_future; + std::atomic<std::size_t> m_next; std::atomic<std::size_t> m_audited; - std::atomic<game_driver const *> m_current; + std::atomic<ui_system_info const *> m_current; + std::atomic<bool> m_cancel; phase m_phase; + bool m_fast; }; -bool sorted_game_list(const game_driver *x, const game_driver *y); - } // namespace ui #endif // MAME_FRONTEND_UI_AUDITMENU_H diff --git a/src/frontend/mame/ui/barcode.cpp b/src/frontend/mame/ui/barcode.cpp index 2cda13f4a51..979203f0133 100644 --- a/src/frontend/mame/ui/barcode.cpp +++ b/src/frontend/mame/ui/barcode.cpp @@ -9,12 +9,14 @@ ***************************************************************************/ #include "emu.h" - #include "ui/barcode.h" + #include "ui/ui.h" #include "ui/utils.h" + namespace ui { + // itemrefs for key menu items #define ITEMREF_NEW_BARCODE ((void *) 0x0001) #define ITEMREF_ENTER_BARCODE ((void *) 0x0002) @@ -35,6 +37,8 @@ namespace ui { menu_barcode_reader::menu_barcode_reader(mame_ui_manager &mui, render_container &container, barcode_reader_device *device) : menu_device_control<barcode_reader_device>(mui, container, device) { + set_heading(_("Barcode Reader")); + set_process_flags(PROCESS_LR_REPEAT); } @@ -50,34 +54,19 @@ menu_barcode_reader::~menu_barcode_reader() // populate - populates the barcode input menu //------------------------------------------------- -void menu_barcode_reader::populate(float &customtop, float &custombottom) +void menu_barcode_reader::populate() { if (current_device()) { - std::string buffer; - const char *new_barcode; - // selected device - item_append(current_display_name(), "", current_display_flags(), ITEMREF_SELECT_READER); + item_append(std::string(current_display_name()), std::string(current_device()->tag() + 1), current_display_flags(), ITEMREF_SELECT_READER); // append the "New Barcode" item - if (get_selection_ref() == ITEMREF_NEW_BARCODE) - { - buffer.append(m_barcode_buffer); - new_barcode = buffer.c_str(); - } - else - { - new_barcode = m_barcode_buffer.c_str(); - } - - item_append(_("New Barcode:"), new_barcode, 0, ITEMREF_NEW_BARCODE); + item_append(_("New Barcode:"), m_barcode_buffer, 0, ITEMREF_NEW_BARCODE); // finish up the menu + item_append(_("Enter Code"), 0, ITEMREF_ENTER_BARCODE); item_append(menu_item_type::SEPARATOR); - item_append(_("Enter Code"), "", 0, ITEMREF_ENTER_BARCODE); - - customtop = ui().get_line_height() + 3.0f * ui().box_tb_border(); } } @@ -86,61 +75,74 @@ void menu_barcode_reader::populate(float &customtop, float &custombottom) // handle - manages inputs in the barcode input menu //------------------------------------------------- -void menu_barcode_reader::handle() +bool menu_barcode_reader::handle(event const *ev) { - // rebuild the menu (so to update the selected device, if the user has pressed L or R) - repopulate(reset_options::REMEMBER_POSITION); + if (!ev) + return false; - // process the menu - const event *event = process(PROCESS_LR_REPEAT); - - // process the event - if (event) + switch (ev->iptkey) { - // handle selections - switch (event->iptkey) + case IPT_UI_LEFT: + if (ev->itemref == ITEMREF_SELECT_READER) + return previous(); + break; + + case IPT_UI_RIGHT: + if (ev->itemref == ITEMREF_SELECT_READER) + return next(); + break; + + case IPT_UI_SELECT: + if (ev->itemref == ITEMREF_ENTER_BARCODE) { - case IPT_UI_LEFT: - if (event->itemref == ITEMREF_SELECT_READER) - previous(); - break; - - case IPT_UI_RIGHT: - if (event->itemref == ITEMREF_SELECT_READER) - next(); - break; - - case IPT_UI_SELECT: - if (event->itemref == ITEMREF_ENTER_BARCODE) + //osd_printf_verbose("code %s\n", m_barcode_buffer); + if (!current_device()->is_valid(m_barcode_buffer.length())) { - std::string tmp_file(m_barcode_buffer); - //printf("code %s\n", m_barcode_buffer); - if (!current_device()->is_valid(tmp_file.length())) - ui().popup_time(5, "%s", _("Barcode length invalid!")); - else - { - current_device()->write_code(tmp_file.c_str(), tmp_file.length()); - // if sending was successful, reset char buffer - m_barcode_buffer.clear(); - reset(reset_options::REMEMBER_POSITION); - } + ui().popup_time(5, "%s", _("Barcode length invalid!")); } - break; - - case IPT_SPECIAL: - if (get_selection_ref() == ITEMREF_NEW_BARCODE) + else { - if (input_character(m_barcode_buffer, event->unichar, uchar_is_digit)) - reset(reset_options::REMEMBER_POSITION); + current_device()->write_code(m_barcode_buffer.c_str(), m_barcode_buffer.length()); + // if sending was successful, reset char buffer + m_barcode_buffer.clear(); + reset(reset_options::REMEMBER_POSITION); } - break; + } + break; - case IPT_UI_CANCEL: - // reset the char buffer also in this case + case IPT_UI_CLEAR: + if (ev->itemref == ITEMREF_NEW_BARCODE) + { m_barcode_buffer.clear(); - break; + ev->item->set_subtext(m_barcode_buffer); + return true; + } + break; + + case IPT_UI_PASTE: + if (get_selection_ref() == ITEMREF_NEW_BARCODE) + { + if (paste_text(m_barcode_buffer, uchar_is_digit)) + { + ev->item->set_subtext(m_barcode_buffer); + return true; + } } + break; + + case IPT_SPECIAL: + if (get_selection_ref() == ITEMREF_NEW_BARCODE) + { + if (input_character(m_barcode_buffer, ev->unichar, uchar_is_digit)) + { + ev->item->set_subtext(m_barcode_buffer); + return true; + } + } + break; } + + return false; } } // namespace ui diff --git a/src/frontend/mame/ui/barcode.h b/src/frontend/mame/ui/barcode.h index 7f48d4f78ed..7fa9881c3dd 100644 --- a/src/frontend/mame/ui/barcode.h +++ b/src/frontend/mame/ui/barcode.h @@ -13,18 +13,23 @@ #pragma once -#include "machine/bcreader.h" #include "ui/devctrl.h" +#include "machine/bcreader.h" + +#include <string> + + namespace ui { + class menu_barcode_reader : public menu_device_control<barcode_reader_device> { public: menu_barcode_reader(mame_ui_manager &mui, render_container &container, barcode_reader_device *device); virtual ~menu_barcode_reader() 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; std::string m_barcode_buffer; }; diff --git a/src/frontend/mame/ui/cheatopt.cpp b/src/frontend/mame/ui/cheatopt.cpp index 1cf87b7f9f3..c8457a1403b 100644 --- a/src/frontend/mame/ui/cheatopt.cpp +++ b/src/frontend/mame/ui/cheatopt.cpp @@ -9,112 +9,107 @@ *********************************************************************/ #include "emu.h" -#include "cheat.h" -#include "mame.h" +#include "ui/cheatopt.h" #include "ui/ui.h" -#include "ui/menu.h" -#include "ui/cheatopt.h" + +#include "cheat.h" +#include "mame.h" namespace ui { -// itemrefs for key menu items -#define ITEMREF_CHEATS_RESET_ALL ((void *) 0x0001) -#define ITEMREF_CHEATS_RELOAD_ALL ((void *) 0x0002) -#define ITEMREF_CHEATS_AUTOFIRE_SETTINGS ((void *) 0x0003) -#define ITEMREF_CHEATS_FIRST_ITEM ((void *) 0x0004) // itemrefs for key menu items -#define ITEMREF_AUTOFIRE_STATUS ((void *) 0x0001) -#define ITEMREF_AUTOFIRE_DELAY ((void *) 0x0002) -#define ITEMREF_AUTOFIRE_FIRST_BUTTON ((void *) 0x0003) +#define ITEMREF_CHEATS_ENABLE ((void *) 0x0001) +#define ITEMREF_CHEATS_RESET_ALL ((void *) 0x0002) +#define ITEMREF_CHEATS_RELOAD_ALL ((void *) 0x0003) +#define ITEMREF_CHEATS_FIRST_ITEM ((void *) 0x0004) /*------------------------------------------------- menu_cheat - handle the cheat menu -------------------------------------------------*/ -void menu_cheat::handle() +bool menu_cheat::handle(event const *ev) { - /* process the menu */ - const event *menu_event = process(PROCESS_LR_REPEAT); + if (!ev || !ev->itemref) + return false; + bool changed = false; - /* handle events */ - if (menu_event != nullptr && menu_event->itemref != nullptr) - { - bool changed = false; - - /* clear cheat comment on any movement or keypress */ - machine().popmessage(); + // clear cheat comment on any movement or keypress + machine().popmessage(); - /* handle reset all + reset all cheats for reload all option */ - if ((menu_event->itemref == ITEMREF_CHEATS_RESET_ALL || menu_event->itemref == ITEMREF_CHEATS_RELOAD_ALL) && menu_event->iptkey == IPT_UI_SELECT) + if (ev->itemref == ITEMREF_CHEATS_ENABLE) + { + if ((ev->iptkey == IPT_UI_LEFT) || (ev->iptkey == IPT_UI_RIGHT) || (ev->iptkey == IPT_UI_CLEAR)) { - for (auto &curcheat : mame_machine_manager::instance()->cheat().entries()) - if (curcheat->select_default_state()) - changed = true; + // handle global enable toggle + mame_machine_manager::instance()->cheat().set_enable(ev->iptkey == IPT_UI_RIGHT || (ev->iptkey == IPT_UI_CLEAR), false); + changed = true; } - - /* handle individual cheats */ - else if (menu_event->itemref >= ITEMREF_CHEATS_FIRST_ITEM) + } + if ((ev->itemref == ITEMREF_CHEATS_RESET_ALL || ev->itemref == ITEMREF_CHEATS_RELOAD_ALL) && ev->iptkey == IPT_UI_SELECT) + { + // handle reset all + reset all cheats for reload all option + for (auto &curcheat : mame_machine_manager::instance()->cheat().entries()) + if (curcheat->select_default_state()) + changed = true; + } + else if (ev->itemref >= ITEMREF_CHEATS_FIRST_ITEM) + { + // handle individual cheats + cheat_entry *curcheat = reinterpret_cast<cheat_entry *>(ev->itemref); + const char *string; + switch (ev->iptkey) { - cheat_entry *curcheat = reinterpret_cast<cheat_entry *>(menu_event->itemref); - const char *string; - switch (menu_event->iptkey) - { - /* if selected, activate a oneshot */ - case IPT_UI_SELECT: - changed = curcheat->activate(); - break; - - /* if cleared, reset to default value */ - case IPT_UI_CLEAR: - changed = curcheat->select_default_state(); - break; - - /* left decrements */ - case IPT_UI_LEFT: - changed = curcheat->select_previous_state(); - break; - - /* right increments */ - case IPT_UI_RIGHT: - changed = curcheat->select_next_state(); - break; - - /* bring up display comment if one exists */ - case IPT_UI_DISPLAY_COMMENT: - case IPT_UI_UP: - case IPT_UI_DOWN: - string = curcheat->comment(); - if (string != nullptr && string[0] != 0) - machine().popmessage(_("Cheat Comment:\n%s"), string); - break; - } + // if selected, activate a oneshot + case IPT_UI_SELECT: + changed = curcheat->activate(); + break; + + // if cleared, reset to default value + case IPT_UI_CLEAR: + changed = curcheat->select_default_state(); + break; + + // left decrements + case IPT_UI_LEFT: + changed = curcheat->select_previous_state(); + break; + + // right increments + case IPT_UI_RIGHT: + changed = curcheat->select_next_state(); + break; + + // bring up display comment if one exists + case IPT_UI_DISPLAY_COMMENT: + case IPT_UI_UP: + case IPT_UI_DOWN: + string = curcheat->comment(); + if (string && *string) + machine().popmessage(_("Cheat Comment:\n%s"), string); + break; } + } - /* handle reload all */ - if (menu_event->itemref == ITEMREF_CHEATS_RELOAD_ALL && menu_event->iptkey == IPT_UI_SELECT) - { - /* re-init cheat engine and thus reload cheats/cheats have already been turned off by here */ - mame_machine_manager::instance()->cheat().reload(); + // handle reload all + if (ev->itemref == ITEMREF_CHEATS_RELOAD_ALL && ev->iptkey == IPT_UI_SELECT) + { + // re-init cheat engine and thus reload cheats/cheats have already been turned off by here + mame_machine_manager::instance()->cheat().reload(); - /* display the reloaded cheats */ - reset(reset_options::REMEMBER_REF); - machine().popmessage(_("All cheats reloaded")); - } + // display the reloaded cheats + machine().popmessage(_("All cheats reloaded")); + changed = true; + } - /* handle autofire menu */ - if (menu_event->itemref == ITEMREF_CHEATS_AUTOFIRE_SETTINGS && menu_event->iptkey == IPT_UI_SELECT) - { - menu::stack_push<menu_autofire>(ui(), container()); - } + // if things changed, update + if (changed) + reset(reset_options::REMEMBER_REF); - /* if things changed, update */ - if (changed) - reset(reset_options::REMEMBER_REF); - } + return false; // always triggers an item reset if the menu needs to be redrawn } @@ -124,22 +119,25 @@ void menu_cheat::handle() menu_cheat::menu_cheat(mame_ui_manager &mui, render_container &container) : menu(mui, container) { + set_heading(_("Cheat Options")); + set_process_flags(PROCESS_LR_REPEAT); } -void menu_cheat::populate(float &customtop, float &custombottom) +void menu_cheat::menu_activated() { - /* iterate over cheats */ - std::string text; - std::string subtext; + reset(reset_options::REMEMBER_REF); +} - // add the autofire menu - item_append(_("Autofire Settings"), "", 0, (void *)ITEMREF_CHEATS_AUTOFIRE_SETTINGS); +void menu_cheat::populate() +{ + const bool empty = mame_machine_manager::instance()->cheat().entries().empty(); - /* add a separator */ - item_append(menu_item_type::SEPARATOR); + // iterate over cheats + if (!empty) + { + std::string text; + std::string subtext; - // add other cheats - if (!mame_machine_manager::instance()->cheat().entries().empty()) { for (auto &curcheat : mame_machine_manager::instance()->cheat().entries()) { uint32_t flags; @@ -149,182 +147,31 @@ void menu_cheat::populate(float &customtop, float &custombottom) else item_append(text, subtext, flags, curcheat.get()); } - - /* add a separator */ - item_append(menu_item_type::SEPARATOR); - - /* add a reset all option */ - item_append(_("Reset All"), "", 0, (void *)ITEMREF_CHEATS_RESET_ALL); - - /* add a reload all cheats option */ - item_append(_("Reload All"), "", 0, (void *)ITEMREF_CHEATS_RELOAD_ALL); - } -} - -menu_cheat::~menu_cheat() -{ -} - - - - - -/*------------------------------------------------- - menu_autofire - handle the autofire settings - menu --------------------------------------------------*/ - -menu_autofire::menu_autofire(mame_ui_manager &mui, render_container &container) : menu(mui, container), last_toggle(false) -{ - const screen_device *screen = screen_device_iterator(mui.machine().root_device()).first(); - - if (screen == nullptr) - { - refresh = 60.0; } else { - refresh = ATTOSECONDS_TO_HZ(screen->refresh_attoseconds()); + // indicate that none were found + item_append(_("[no cheats found]"), FLAG_DISABLE, nullptr); } -} -menu_autofire::~menu_autofire() -{ -} - -void menu_autofire::handle() -{ - ioport_field *field; - bool changed = false; - - /* process the menu */ - const event *menu_event = process(0); + item_append(menu_item_type::SEPARATOR); - /* handle events */ - if (menu_event != nullptr && menu_event->itemref != nullptr) + if (!empty) { - // menu item is changed using left/right keys only - if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) - { - if (menu_event->itemref == ITEMREF_AUTOFIRE_STATUS) - { - // toggle autofire status - bool autofire_toggle = machine().ioport().get_autofire_toggle(); // (menu_event->iptkey == IPT_UI_LEFT); - machine().ioport().set_autofire_toggle(!autofire_toggle); - changed = true; - } - else if (menu_event->itemref == ITEMREF_AUTOFIRE_DELAY) - { - // change autofire frequency - int autofire_delay = machine().ioport().get_autofire_delay(); - if (menu_event->iptkey == IPT_UI_LEFT) - { - autofire_delay--; - if (autofire_delay < 1) - autofire_delay = 1; - } - else - { - autofire_delay++; - if (autofire_delay > 30) - autofire_delay = 30; - } - machine().ioport().set_autofire_delay(autofire_delay); - changed = true; - } - else - { - // enable autofire on specific button - field = (ioport_field *)menu_event->itemref; - ioport_field::user_settings settings; - field->get_user_settings(settings); - settings.autofire = (menu_event->iptkey == IPT_UI_RIGHT); - field->set_user_settings(settings); - changed = true; - } - } - } + // add global enable toggle + item_append_on_off(_("Enable Cheats"), mame_machine_manager::instance()->cheat().enabled(), 0, (void *)ITEMREF_CHEATS_ENABLE); + item_append(menu_item_type::SEPARATOR); - // if toggle settings changed, redraw menu to reflect new options - if (!changed) - { - changed = (last_toggle != machine().ioport().get_autofire_toggle()); + // add a reset all option + item_append(_("Reset All"), 0, (void *)ITEMREF_CHEATS_RESET_ALL); } - /* if something changed, rebuild the menu */ - if (changed) - { - reset(reset_options::REMEMBER_REF); - } + // add a reload all cheats option + item_append(_("Reload All"), 0, (void *)ITEMREF_CHEATS_RELOAD_ALL); } - -/*------------------------------------------------- - menu_autofire_populate - populate the autofire - menu --------------------------------------------------*/ - -void menu_autofire::populate(float &customtop, float &custombottom) +menu_cheat::~menu_cheat() { - char temp_text[64]; - - /* add autofire toggle item */ - bool autofire_toggle = machine().ioport().get_autofire_toggle(); - item_append(_("Autofire Status"), (autofire_toggle ? _("Disabled") : _("Enabled")), - (autofire_toggle ? FLAG_RIGHT_ARROW : FLAG_LEFT_ARROW), (void *)ITEMREF_AUTOFIRE_STATUS); - - /* iterate over the input ports and add autofire toggle items */ - int menu_items = 0; - for (auto &port : machine().ioport().ports()) - { - bool is_first_button = true; - for (ioport_field &field : port.second->fields()) - { - if (field.type() >= IPT_BUTTON1 && field.type() <= IPT_BUTTON16) - { - menu_items++; - ioport_field::user_settings settings; - field.get_user_settings(settings); - - if (is_first_button) - { - /* add a separator for each player */ - item_append(menu_item_type::SEPARATOR); - is_first_button = false; - } - - /* add an autofire item */ - item_append_on_off(field.name(), settings.autofire, (autofire_toggle ? FLAG_DISABLE | FLAG_INVERT : 0), (void *)&field); - } - } - } - - /* add text item if no buttons found */ - if (menu_items==0) - { - item_append(menu_item_type::SEPARATOR); - item_append(_("No buttons found on this machine!"), "", FLAG_DISABLE, nullptr); - } - - /* add a separator */ - item_append(menu_item_type::SEPARATOR); - - /* add autofire delay item */ - int value = machine().ioport().get_autofire_delay(); - snprintf(temp_text, ARRAY_LENGTH(temp_text), "%d = %.2f Hz", value, (float)refresh/value); - if (!autofire_toggle) - { - item_append(_("Autofire Delay"), temp_text, FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW, (void *)ITEMREF_AUTOFIRE_DELAY); - } - else - { - item_append(_("Autofire Delay"), temp_text, FLAG_DISABLE | FLAG_INVERT, nullptr); - } - - /* add a separator */ - item_append(menu_item_type::SEPARATOR); - - last_toggle = autofire_toggle; } } // namespace ui diff --git a/src/frontend/mame/ui/cheatopt.h b/src/frontend/mame/ui/cheatopt.h index e95e765cb30..77847f80950 100644 --- a/src/frontend/mame/ui/cheatopt.h +++ b/src/frontend/mame/ui/cheatopt.h @@ -7,15 +7,16 @@ Internal menu for the cheat interface. ***************************************************************************/ - -#pragma once - #ifndef MAME_FRONTEND_UI_CHEATOPT_H #define MAME_FRONTEND_UI_CHEATOPT_H +#pragma once + #include "ui/menu.h" + namespace ui { + class menu_cheat : public menu { public: @@ -23,25 +24,11 @@ public: virtual ~menu_cheat() override; private: - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; -}; - - -class menu_autofire : public menu -{ -public: - menu_autofire(mame_ui_manager &mui, render_container &container); - virtual ~menu_autofire() override; - -private: - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; - - float refresh; - bool last_toggle; + virtual void populate() override; + virtual void menu_activated() override; + virtual bool handle(event const *ev) override; }; } // namespace ui -#endif /* MAME_FRONTEND_UI_CHEATOPT_H */ +#endif // MAME_FRONTEND_UI_CHEATOPT_H diff --git a/src/frontend/mame/ui/confswitch.cpp b/src/frontend/mame/ui/confswitch.cpp new file mode 100644 index 00000000000..cf69bb7cfbb --- /dev/null +++ b/src/frontend/mame/ui/confswitch.cpp @@ -0,0 +1,541 @@ +// license:BSD-3-Clause +// copyright-holders:Nicola Salmoria, Aaron Giles, Nathan Woods, Vas Crabb +/********************************************************************* + + ui/confswitch.cpp + + Configuration/DIP switches menu. + +*********************************************************************/ + +#include "emu.h" +#include "ui/confswitch.h" + +#include "uiinput.h" + +#include <algorithm> +#include <cstring> + + +namespace ui { + +namespace { + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +// DIP switch rendering parameters in terms of line height +constexpr float DIP_SWITCH_HEIGHT = 1.6f; +constexpr float DIP_SWITCH_SPACING = DIP_SWITCH_HEIGHT / 5.0f; +constexpr float SINGLE_TOGGLE_SWITCH_FIELD_WIDTH = DIP_SWITCH_HEIGHT / 2.0f; +constexpr float SINGLE_TOGGLE_SWITCH_WIDTH = SINGLE_TOGGLE_SWITCH_FIELD_WIDTH * 0.8f; +// make the switch nub 80% of the width space and 1/2 of the switch height +constexpr float SINGLE_TOGGLE_SWITCH_HEIGHT = (DIP_SWITCH_HEIGHT / 2.0f) * 0.8f; + +} // anonymous namespace + + + +/*------------------------------------------------- + menu_confswitch +-------------------------------------------------*/ + +menu_confswitch::field_descriptor::field_descriptor(ioport_field &f) noexcept + : field(f) +{ +} + + +menu_confswitch::switch_group_descriptor::switch_group_descriptor(ioport_field const &f, ioport_diplocation const &loc) noexcept + : name(loc.name()) + , owner(f.device()) + , mask(0U) + , state(0U) +{ + std::fill(std::begin(toggles), std::end(toggles), toggle{ nullptr, 0U }); +} + + +inline bool menu_confswitch::switch_group_descriptor::matches(ioport_field const &f, ioport_diplocation const &loc) const noexcept +{ + return (&owner.get() == &f.device()) && !strcmp(loc.name(), name); +} + + +inline unsigned menu_confswitch::switch_group_descriptor::switch_count() const noexcept +{ + return (sizeof(mask) * 8) - count_leading_zeros_32(mask); +} + + +menu_confswitch::menu_confswitch(mame_ui_manager &mui, render_container &container, uint32_t type) + : menu(mui, container) + , m_fields() + , m_switch_groups() + , m_active_switch_groups(0U) + , m_type(type) + , m_changed(false) +{ +} + + +menu_confswitch::~menu_confswitch() +{ +} + + +void menu_confswitch::menu_activated() +{ + // switches can have input assignments, and scripts are a thing + reset(reset_options::REMEMBER_REF); +} + + +void menu_confswitch::populate() +{ + // locate relevant fields if necessary + if (m_fields.empty()) + find_fields(); + + // reset switch group masks + m_active_switch_groups = 0U; + for (switch_group_descriptor &group : m_switch_groups) + group.mask = group.state = 0U; + + // loop over input ports and set up the current values + device_t *prev_owner(nullptr); + for (field_descriptor &desc : m_fields) + { + ioport_field &field(desc.field); + if (field.enabled()) + { + if (!field.settings().empty()) + { + // add a device heading if necessary + if (&field.device() != prev_owner) + { + prev_owner = &field.device(); + if (prev_owner->owner()) + item_append(string_format(_("%1$s [root%2$s]"), prev_owner->type().fullname(), prev_owner->tag()), FLAG_UI_HEADING | FLAG_DISABLE, nullptr); + else + item_append(string_format(_("[root%1$s]"), prev_owner->tag()), FLAG_UI_HEADING | FLAG_DISABLE, nullptr); + } + + // set the left/right flags appropriately + uint32_t flags(0U); + if (field.has_previous_setting()) + flags |= FLAG_LEFT_ARROW; + if (field.has_next_setting()) + flags |= FLAG_RIGHT_ARROW; + + // add the menu item + item_append(field.name(), field.setting_name(), flags, &field); + } + + // track switch groups + if (!field.diplocations().empty()) + { + // get current settings + ioport_field::user_settings settings; + field.get_user_settings(settings); + + // iterate over each bit in the field + ioport_value accummask(field.mask()); + for (ioport_diplocation const &loc : field.diplocations()) + { + // find the matching switch group + switch_group_descriptor &group( + *std::find_if( + m_switch_groups.begin(), + m_switch_groups.end(), [&field, &loc] (switch_group_descriptor const &sw) { return sw.matches(field, loc); })); + + // count if this is the first switch in the group + if (!group.mask) + ++m_active_switch_groups; + + // apply the bits + ioport_value const mask(accummask & ~(accummask - 1)); + group.toggles[loc.number() - 1].field = &field; + group.toggles[loc.number() - 1].mask = mask; + group.mask |= uint32_t(1) << (loc.number() - 1); + if (((settings.value & mask) && !loc.inverted()) || (!(settings.value & mask) && loc.inverted())) + group.state |= uint32_t(1) << (loc.number() - 1); + + // clear the relevant bit in the accumulated mask + accummask &= ~mask; + } + } + } + } + + item_append(menu_item_type::SEPARATOR); + item_append(_("Reset System"), 0, (void *)1); +} + + +bool menu_confswitch::handle(event const *ev) +{ + bool const was_changed(std::exchange(m_changed, false)); + bool need_update(false); + if (ev && (IPT_CUSTOM == ev->iptkey)) + { + // clicked a switch + m_changed = true; + } + else if (!ev || !ev->itemref) + { + // no user input + } + else if (uintptr_t(ev->itemref) == 1U) + { + // reset + if (ev->iptkey == IPT_UI_SELECT) + machine().schedule_hard_reset(); + } + else + { + // actual settings + ioport_field &field(*reinterpret_cast<ioport_field *>(ev->itemref)); + + switch (ev->iptkey) + { + // left goes to previous setting + case IPT_UI_LEFT: + field.select_previous_setting(); + m_changed = true; + break; + + // right goes to next setting + case IPT_UI_SELECT: + case IPT_UI_RIGHT: + field.select_next_setting(); + m_changed = true; + break; + + // if cleared, reset to default value + case IPT_UI_CLEAR: + { + ioport_field::user_settings settings; + field.get_user_settings(settings); + if (field.defvalue() != settings.value) + { + settings.value = field.defvalue(); + field.set_user_settings(settings); + m_changed = true; + } + } + break; + + // trick to get previous group - depend on headings having null reference + case IPT_UI_PREV_GROUP: + { + auto current = selected_index(); + bool found_break = false; + while (0 < current) + { + if (!found_break) + { + if (!item(--current).ref()) + found_break = true; + } + else if (!item(current - 1).ref()) + { + set_selected_index(current); + set_top_line(current - 1); + need_update = true; + break; + } + else + { + --current; + } + } + } + break; + + // trick to get next group - depend on special item references + case IPT_UI_NEXT_GROUP: + { + auto current = selected_index(); + while (item_count() > ++current) + { + if (!item(current).ref()) + { + if ((item_count() > (current + 1)) && (uintptr_t(item(current + 1).ref()) != 1)) + { + set_selected_index(current + 1); + set_top_line(current); + need_update = true; + } + break; + } + } + } + break; + } + } + + // changing settings triggers an item rebuild because it can affect whether things are enabled + if (m_changed || was_changed) + reset(reset_options::REMEMBER_REF); + return need_update; +} + + +void menu_confswitch::find_fields() +{ + assert(m_fields.empty()); + assert(m_switch_groups.empty()); + + // find relevant input ports + for (auto &port : machine().ioport().ports()) + { + for (ioport_field &field : port.second->fields()) + { + if (field.type() == m_type) + { + m_fields.emplace_back(field); + + // iterate over locations + for (ioport_diplocation const &loc : field.diplocations()) + { + auto const found( + std::find_if( + m_switch_groups.begin(), + m_switch_groups.end(), [&field, &loc] (switch_group_descriptor const &sw) { return sw.matches(field, loc); })); + if (m_switch_groups.end() == found) + m_switch_groups.emplace_back(field, loc); + } + } + } + } +} + + + +/*------------------------------------------------- + menu_settings_dip_switches +-------------------------------------------------*/ + +menu_settings_dip_switches::menu_settings_dip_switches(mame_ui_manager &mui, render_container &container) + : menu_confswitch(mui, container, IPT_DIPSWITCH) + , m_switch_group_y() + , m_visible_switch_groups(0U) + , m_single_width(0.0f) + , m_nub_width(0.0f) + , m_first_nub(0.0f) + , m_clickable_height(0.0f) +{ + set_heading(_("DIP Switches")); +} + + +menu_settings_dip_switches::~menu_settings_dip_switches() +{ +} + + +void menu_settings_dip_switches::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + menu_confswitch::recompute_metrics(width, height, aspect); + + set_custom_space( + 0.0f, + m_visible_switch_groups + ? ((m_visible_switch_groups * (DIP_SWITCH_HEIGHT * line_height())) + ((m_visible_switch_groups - 1) * (DIP_SWITCH_SPACING * line_height())) + (tb_border() * 3.0f)) + : 0.0f); +} + + +void menu_settings_dip_switches::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +{ + // catch if no DIP locations have to be drawn + if (!m_visible_switch_groups) + return; + + // calculate optimal width + float const maxwidth(1.0f - (lr_border() * 2.0f)); + m_single_width = (line_height() * SINGLE_TOGGLE_SWITCH_FIELD_WIDTH * x_aspect()); + float width(0.0f); + unsigned maxswitches(0U); + for (switch_group_descriptor const &group : switch_groups()) + { + if (group.mask) + { + maxswitches = (std::max)(group.switch_count(), maxswitches); + float const namewidth(get_string_width(group.name)); + float const switchwidth(m_single_width * maxswitches); + width = (std::min)((std::max)(namewidth + switchwidth + (line_height() * x_aspect()), width), maxwidth); + } + } + + // draw extra menu area + float const boxwidth((std::max)(width + (lr_border() * 2.0f), origx2 - origx1)); + float const boxleft((1.0f - boxwidth) * 0.5f); + ui().draw_outlined_box(container(), boxleft, origy2 + tb_border(), boxleft + boxwidth, origy2 + bottom, ui().colors().background_color()); + + // calculate centred layout + float const nameleft((1.0f - width) * 0.5f); + float const switchleft(nameleft + width - (m_single_width * maxswitches)); + float const namewidth(width - (m_single_width * maxswitches) - (line_height() * x_aspect())); + + // iterate over switch groups + ioport_field *const field((uintptr_t(selectedref) != 1U) ? reinterpret_cast<ioport_field *>(selectedref) : nullptr); + float const nubheight(line_height() * SINGLE_TOGGLE_SWITCH_HEIGHT); + m_nub_width = line_height() * SINGLE_TOGGLE_SWITCH_WIDTH * x_aspect(); + float const ygap(line_height() * ((DIP_SWITCH_HEIGHT * 0.5f) - SINGLE_TOGGLE_SWITCH_HEIGHT) * 0.5f); + float const xgap((m_single_width + (UI_LINE_WIDTH * 0.5f) - m_nub_width) * 0.5f); + m_first_nub = switchleft + xgap; + m_clickable_height = (line_height() * DIP_SWITCH_HEIGHT) - (ygap * 2.0f); + unsigned line(0U); + for (unsigned n = 0; switch_groups().size() > n; ++n) + { + switch_group_descriptor const &group(switch_groups()[n]); + if (group.mask) + { + // determine the mask of selected bits + uint32_t selectedmask(0U); + if (field) + { + for (ioport_diplocation const &loc : field->diplocations()) + if (group.matches(*field, loc)) + selectedmask |= uint32_t(1) << (loc.number() - 1); + } + + // draw the name + float const liney(origy2 + (tb_border() * 2.0f) + (line_height() * (DIP_SWITCH_HEIGHT + DIP_SWITCH_SPACING) * line)); + draw_text_normal( + group.name, + nameleft, liney + (line_height() * (DIP_SWITCH_HEIGHT - 1.0f) / 2.0f), namewidth, + text_layout::text_justify::RIGHT, text_layout::word_wrapping::NEVER, + ui().colors().text_color()); + + // draw the group outline + float const switchbottom(liney + (DIP_SWITCH_HEIGHT * line_height())); + unsigned const cnt(group.switch_count()); + ui().draw_outlined_box( + container(), + switchleft, liney, switchleft + (m_single_width * cnt), switchbottom, + ui().colors().background_color()); + for (unsigned i = 1; cnt > i; ++i) + { + container().add_line( + switchleft + (m_single_width * i), liney, switchleft + (m_single_width * i), switchbottom, + UI_LINE_WIDTH, ui().colors().text_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + } + + // compute top and bottom for on and off positions + float const yoff(liney + UI_LINE_WIDTH + ygap); + float const yon(switchbottom - UI_LINE_WIDTH - ygap - nubheight); + m_switch_group_y[n] = yoff; + + // draw the switch nubs + for (unsigned toggle = 0; cnt > toggle; ++toggle) + { + float const nubleft(switchleft + (m_single_width * toggle) + xgap); + if (BIT(group.mask, toggle)) + { + float const nubtop(BIT(group.state, toggle) ? yon : yoff); + container().add_rect( + nubleft, nubtop, nubleft + m_nub_width, nubtop + nubheight, + BIT(selectedmask, toggle) ? ui().colors().dipsw_color() : ui().colors().text_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + } + else + { + container().add_rect( + nubleft, yoff, nubleft + m_nub_width, yon + nubheight, + ui().colors().unavailable_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + } + } + + // limit the number of visible switch groups + if (++line >= m_visible_switch_groups) + break; + } + } +} + + +std::tuple<int, bool, bool> menu_settings_dip_switches::custom_pointer_updated(bool changed, ui_event const &uievt) +{ + if (!m_visible_switch_groups || !(uievt.pointer_pressed & 0x01) || (uievt.pointer_buttons & ~u32(0x01))) + return std::make_tuple(IPT_INVALID, false, false); + + auto const [cx, y] = pointer_location(); + if (cx < m_first_nub) + return std::make_tuple(IPT_INVALID, false, false); + + float const x(cx - m_first_nub); + for (unsigned n = 0U, line = 0U; (switch_groups().size() > n) && (m_visible_switch_groups > line); ++n) + { + switch_group_descriptor const &group(switch_groups()[n]); + if (group.mask) + { + ++line; + if ((y >= m_switch_group_y[n]) && (y < (m_switch_group_y[n] + m_clickable_height))) + { + unsigned const cnt(group.switch_count()); + for (unsigned i = 0U; cnt > i; ++i) + { + if (BIT(group.mask, i)) + { + float const xstart(float(i) * m_single_width); + if ((x >= xstart) && (x < (xstart + m_nub_width))) + { + ioport_field::user_settings settings; + group.toggles[i].field->get_user_settings(settings); + settings.value ^= group.toggles[i].mask; + group.toggles[i].field->set_user_settings(settings); + return std::make_tuple(IPT_CUSTOM, true, true); + } + } + } + } + } + } + + return std::make_tuple(IPT_INVALID, false, false); +} + + +void menu_settings_dip_switches::populate() +{ + // let the base class add items + menu_confswitch::populate(); + + // use up to about 70% of height for DIP switch display + if (active_switch_groups()) + { + m_switch_group_y.resize(switch_groups().size()); + float const groupheight(DIP_SWITCH_HEIGHT * line_height()); + float const groupspacing(DIP_SWITCH_SPACING * line_height()); + if ((active_switch_groups() * (groupheight + groupspacing)) > 0.7f) + m_visible_switch_groups = unsigned(0.7f / (groupheight + groupspacing)); + else + m_visible_switch_groups = active_switch_groups(); + set_custom_space(0.0f, (m_visible_switch_groups * groupheight) + ((m_visible_switch_groups - 1) * groupspacing) + (tb_border() * 3.0f)); + } + else + { + m_visible_switch_groups = 0U; + set_custom_space(0.0f, 0.0f); + } +} + + + +/*------------------------------------------------- + menu_settings_machine_config +-------------------------------------------------*/ + +menu_settings_machine_config::menu_settings_machine_config(mame_ui_manager &mui, render_container &container) : menu_confswitch(mui, container, IPT_CONFIG) +{ + set_heading(_("Machine Configuration")); +} + +menu_settings_machine_config::~menu_settings_machine_config() +{ +} + +} // namespace ui diff --git a/src/frontend/mame/ui/confswitch.h b/src/frontend/mame/ui/confswitch.h new file mode 100644 index 00000000000..3a3aba3c97e --- /dev/null +++ b/src/frontend/mame/ui/confswitch.h @@ -0,0 +1,111 @@ +// license:BSD-3-Clause +// copyright-holders:Nicola Salmoria, Aaron Giles, Nathan Woods, Vas Crabb +/*************************************************************************** + + ui/confswitch.h + + Configuration/DIP switches menu. + +***************************************************************************/ +#ifndef MAME_FRONTEND_UI_CONFSWITCH_H +#define MAME_FRONTEND_UI_CONFSWITCH_H + +#include "ui/menu.h" + +#include <functional> +#include <vector> + + +namespace ui { + +class menu_confswitch : public menu +{ +public: + virtual ~menu_confswitch() override; + +protected: + struct field_descriptor + { + field_descriptor(ioport_field &f) noexcept; + + std::reference_wrapper<ioport_field> field; + }; + + struct switch_group_descriptor + { + struct toggle + { + ioport_field *field; + ioport_value mask; + }; + + switch_group_descriptor(ioport_field const &f, ioport_diplocation const &loc) noexcept; + + bool matches(ioport_field const &f, ioport_diplocation const &loc) const noexcept; + unsigned switch_count() const noexcept; + + char const *name; + std::reference_wrapper<device_t> owner; + toggle toggles[32]; + uint32_t mask; + uint32_t state; + }; + + using field_vector = std::vector<field_descriptor>; + using switch_group_vector = std::vector<switch_group_descriptor>; + + menu_confswitch(mame_ui_manager &mui, render_container &container, uint32_t type); + + virtual void menu_activated() override; + virtual void populate() override; + + field_vector const &fields() { return m_fields; } + switch_group_vector const &switch_groups() { return m_switch_groups; } + unsigned active_switch_groups() const { return m_active_switch_groups; } + +private: + virtual bool handle(event const *ev) override; + + void find_fields(); + + field_vector m_fields; + switch_group_vector m_switch_groups; + unsigned m_active_switch_groups; + int const m_type; + bool m_changed; +}; + + +class menu_settings_dip_switches : public menu_confswitch +{ +public: + menu_settings_dip_switches(mame_ui_manager &mui, render_container &container); + virtual ~menu_settings_dip_switches() override; + +protected: + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) override; + virtual std::tuple<int, bool, bool> custom_pointer_updated(bool changed, ui_event const &uievt) override; + +private: + virtual void populate() override; + + std::vector<float> m_switch_group_y; + unsigned m_visible_switch_groups; + float m_single_width; + float m_nub_width; + float m_first_nub; + float m_clickable_height; +}; + + +class menu_settings_machine_config : public menu_confswitch +{ +public: + menu_settings_machine_config(mame_ui_manager &mui, render_container &container); + virtual ~menu_settings_machine_config(); +}; + +} // namespace ui + +#endif // MAME_FRONTEND_UI_CONFSWITCH_H diff --git a/src/frontend/mame/ui/custui.cpp b/src/frontend/mame/ui/custui.cpp index 65b1ad2fa5e..9cfd7284d9b 100644 --- a/src/frontend/mame/ui/custui.cpp +++ b/src/frontend/mame/ui/custui.cpp @@ -9,182 +9,337 @@ *********************************************************************/ #include "emu.h" - #include "ui/custui.h" -#include "ui/ui.h" #include "ui/selector.h" +#include "ui/ui.h" #include "ui/utils.h" #include "drivenum.h" #include "emuopts.h" -#include "osdepend.h" +#include "fileio.h" #include "uiinput.h" +#include "corestr.h" +#include "osdepend.h" +#include "path.h" + #include <algorithm> +#include <iterator> +#include <locale> +#include <sstream> #include <utility> namespace ui { -const char *const menu_custom_ui::HIDE_STATUS[] = { - __("Show All"), - __("Hide Filters"), - __("Hide Info/Image"), - __("Hide Both") }; +namespace { + +enum +{ + LANGUAGE_MENU = 1, + SYSNAMES_MENU, + FONT_MENU, + COLORS_MENU, + HIDE_MENU, + + INFOS_SIZE = 1, + FONT_SIZE, + MUI_FNT, + MUI_BOLD, + MUI_ITALIC +}; + +const char *const HIDE_STATUS[] = { + N_("Show All"), + N_("Hide Filters"), + N_("Hide Info/Image"), + N_("Hide Both") }; + +template <typename T, typename U> +T parse_number(U &&s) +{ + T result(T(0)); + std::istringstream ss(std::forward<U>(s)); + ss.imbue(std::locale::classic()); + ss >> result; + return result; +} + +} // anonymous namespace + //------------------------------------------------- // ctor //------------------------------------------------- -menu_custom_ui::menu_custom_ui(mame_ui_manager &mui, render_container &container) : menu(mui, container) +menu_custom_ui::menu_custom_ui(mame_ui_manager &mui, render_container &container, std::function<void ()> &&handler) + : menu(mui, container) + , m_handler(std::move(handler)) + , m_currlang(0) + , m_currsysnames(0) + , m_currpanels(ui().options().hide_panels()) { - // load languages - file_enumerator path(mui.machine().options().language_path()); - auto lang = mui.machine().options().language(); - const osd::directory::entry *dirent; - std::size_t cnt = 0; - while ((dirent = path.next())) - { - if (dirent->type == osd::directory::entry::entry_type::DIR && strcmp(dirent->name, ".") != 0 && strcmp(dirent->name, "..") != 0) - { - auto name = std::string(dirent->name); - auto i = strreplace(name, "_", " ("); - if (i > 0) name = name.append(")"); - m_lang.push_back(name); - if (strcmp(name.c_str(), lang) == 0) - m_currlang = cnt; - ++cnt; - } - } + set_process_flags(PROCESS_LR_REPEAT); + set_heading(_("Customize UI")); + + find_languages(); + find_sysnames(); } //------------------------------------------------- -// dtor +// menu dismissed //------------------------------------------------- -menu_custom_ui::~menu_custom_ui() +void menu_custom_ui::menu_dismissed() { - ui().options().set_value(OPTION_HIDE_PANELS, ui_globals::panels_status, OPTION_PRIORITY_CMDLINE); - if (!m_lang.empty()) - { - machine().options().set_value(OPTION_LANGUAGE, m_lang[m_currlang].c_str(), OPTION_PRIORITY_CMDLINE); - load_translation(machine().options()); - } + ui().options().set_value(OPTION_HIDE_PANELS, m_currpanels, OPTION_PRIORITY_CMDLINE); + + machine().options().set_value(OPTION_LANGUAGE, m_currlang ? m_languages[m_currlang] : "", OPTION_PRIORITY_CMDLINE); + load_translation(machine().options()); + + ui().options().set_value(OPTION_SYSTEM_NAMES, m_currsysnames ? m_sysnames[m_currsysnames] : "", OPTION_PRIORITY_CMDLINE); + ui_globals::reset = true; + + if (m_handler) + m_handler(); } //------------------------------------------------- // handle //------------------------------------------------- -void menu_custom_ui::handle() +bool menu_custom_ui::handle(event const *ev) { - bool changed = false; - - // process the menu - const event *menu_event = process(0); + if (!ev || !ev->itemref) + return false; - if (menu_event != nullptr && menu_event->itemref != nullptr) + switch ((uintptr_t)ev->itemref) { - switch ((uintptr_t)menu_event->itemref) + case FONT_MENU: + if (ev->iptkey == IPT_UI_SELECT) + menu::stack_push<menu_font_ui>(ui(), container(), nullptr); + break; + case COLORS_MENU: + if (ev->iptkey == IPT_UI_SELECT) + menu::stack_push<menu_colors_ui>(ui(), container()); + break; + case LANGUAGE_MENU: + if ((ev->iptkey == IPT_UI_LEFT) || (ev->iptkey == IPT_UI_RIGHT) || (ev->iptkey == IPT_UI_CLEAR)) { - case FONT_MENU: - if (menu_event->iptkey == IPT_UI_SELECT) - menu::stack_push<menu_font_ui>(ui(), container()); - break; - case COLORS_MENU: - if (menu_event->iptkey == IPT_UI_SELECT) - menu::stack_push<menu_colors_ui>(ui(), container()); - break; - case HIDE_MENU: - if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) - { - changed = true; - (menu_event->iptkey == IPT_UI_RIGHT) ? ui_globals::panels_status++ : ui_globals::panels_status--; - } - else if (menu_event->iptkey == IPT_UI_SELECT) - { - std::vector<std::string> s_sel(ARRAY_LENGTH(HIDE_STATUS)); - std::transform(std::begin(HIDE_STATUS), std::end(HIDE_STATUS), s_sel.begin(), [](auto &s) { return _(s); }); - menu::stack_push<menu_selector>( - ui(), container(), std::move(s_sel), ui_globals::panels_status, - [this] (int selection) - { - ui_globals::panels_status = selection; - reset(reset_options::REMEMBER_REF); - }); - } - break; - case LANGUAGE_MENU: - if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) - { - changed = true; - (menu_event->iptkey == IPT_UI_RIGHT) ? m_currlang++ : m_currlang--; - } - else if (menu_event->iptkey == IPT_UI_SELECT) - { - // copying list of language names - expensive - menu::stack_push<menu_selector>( - ui(), container(), std::vector<std::string>(m_lang), m_currlang, - [this] (int selection) - { - m_currlang = selection; - reset(reset_options::REMEMBER_REF); - }); - } - break; + if (ev->iptkey == IPT_UI_LEFT) + --m_currlang; + else if (ev->iptkey == IPT_UI_RIGHT) + ++m_currlang; + else + m_currlang = 0; + ev->item->set_subtext(m_languages[m_currlang]); + ev->item->set_flags(get_arrow_flags<std::size_t>(0, m_languages.size() - 1, m_currlang)); + return true; + } + else if (ev->iptkey == IPT_UI_SELECT) + { + // copying list of language names - expensive + menu::stack_push<menu_selector>( + ui(), container(), _("UI Language"), std::vector<std::string>(m_languages), m_currlang, + [this, item = ev->item] (int selection) + { + m_currlang = selection; + item->set_subtext(m_languages[selection]); + item->set_flags(get_arrow_flags<std::size_t>(0, m_languages.size() - 1, selection)); + }); + } + break; + case SYSNAMES_MENU: + if ((ev->iptkey == IPT_UI_LEFT) || (ev->iptkey == IPT_UI_RIGHT) || (ev->iptkey == IPT_UI_CLEAR)) + { + if (ev->iptkey == IPT_UI_LEFT) + --m_currsysnames; + else if (ev->iptkey == IPT_UI_RIGHT) + ++m_currsysnames; + else + m_currsysnames = 0; + ev->item->set_subtext(m_sysnames[m_currsysnames]); + ev->item->set_flags(get_arrow_flags<std::size_t>(0, m_sysnames.size() - 1, m_currsysnames)); + return true; + } + else if (ev->iptkey == IPT_UI_SELECT) + { + // copying list of file names - expensive + menu::stack_push<menu_selector>( + ui(), container(), _("System Names"), std::vector<std::string>(m_sysnames), m_currsysnames, + [this, item = ev->item] (int selection) + { + m_currsysnames = selection; + item->set_subtext(m_sysnames[selection]); + item->set_flags(get_arrow_flags<std::size_t>(0, m_sysnames.size() - 1, selection)); + }); } + break; + case HIDE_MENU: + if ((ev->iptkey == IPT_UI_LEFT) || (ev->iptkey == IPT_UI_RIGHT) || (ev->iptkey == IPT_UI_CLEAR)) + { + if (ev->iptkey == IPT_UI_LEFT) + --m_currpanels; + else if (ev->iptkey == IPT_UI_RIGHT) + ++m_currpanels; + else + m_currpanels = 0; + ev->item->set_subtext(_(HIDE_STATUS[m_currpanels])); + ev->item->set_flags(get_arrow_flags<uint16_t>(0, HIDE_BOTH, m_currpanels)); + return true; + } + else if (ev->iptkey == IPT_UI_SELECT) + { + std::vector<std::string> s_sel(std::size(HIDE_STATUS)); + std::transform(std::begin(HIDE_STATUS), std::end(HIDE_STATUS), s_sel.begin(), [](auto &s) { return _(s); }); + menu::stack_push<menu_selector>( + ui(), container(), _("Show Side Panels"), std::move(s_sel), m_currpanels, + [this, item = ev->item] (int selection) + { + m_currpanels = selection; + item->set_subtext(_(HIDE_STATUS[selection])); + item->set_flags(get_arrow_flags<uint16_t>(0, HIDE_BOTH, selection)); + }); + } + break; } - if (changed) - reset(reset_options::REMEMBER_REF); + return false; } //------------------------------------------------- // populate //------------------------------------------------- -void menu_custom_ui::populate(float &customtop, float &custombottom) +void menu_custom_ui::populate() { uint32_t arrow_flags; - item_append(_("Fonts"), "", 0, (void *)(uintptr_t)FONT_MENU); - item_append(_("Colors"), "", 0, (void *)(uintptr_t)COLORS_MENU); + item_append(_("Fonts"), 0, (void *)(uintptr_t)FONT_MENU); + item_append(_("Colors"), 0, (void *)(uintptr_t)COLORS_MENU); - if (!m_lang.empty()) - { - arrow_flags = get_arrow_flags<std::uint16_t>(0, m_lang.size() - 1, m_currlang); - item_append(_("Language"), m_lang[m_currlang].c_str(), arrow_flags, (void *)(uintptr_t)LANGUAGE_MENU); - } + arrow_flags = get_arrow_flags<std::size_t>(0, m_languages.size() - 1, m_currlang); + item_append(_("Language"), m_languages[m_currlang], arrow_flags, (void *)(uintptr_t)LANGUAGE_MENU); - arrow_flags = get_arrow_flags<uint16_t>(0, HIDE_BOTH, ui_globals::panels_status); - item_append(_("Show side panels"), _(HIDE_STATUS[ui_globals::panels_status]), arrow_flags, (void *)(uintptr_t)HIDE_MENU); + arrow_flags = get_arrow_flags<std::size_t>(0, m_sysnames.size() - 1, m_currsysnames); + item_append(_("System Names"), m_sysnames[m_currsysnames], arrow_flags, (void *)(uintptr_t)SYSNAMES_MENU); + + arrow_flags = get_arrow_flags<uint16_t>(0, HIDE_BOTH, m_currpanels); + item_append(_("Show Side Panels"), _(HIDE_STATUS[m_currpanels]), arrow_flags, (void *)(uintptr_t)HIDE_MENU); item_append(menu_item_type::SEPARATOR); - customtop = ui().get_line_height() + 3.0f * ui().box_tb_border(); } //------------------------------------------------- -// perform our special rendering +// find UI translation files //------------------------------------------------- -void menu_custom_ui::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_custom_ui::find_languages() { - char const *const text[] = { _("Custom UI Settings") }; - 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); + m_languages.emplace_back(_("[built-in]")); + + file_enumerator path(machine().options().language_path()); + osd::directory::entry const *dirent; + std::string name; + while ((dirent = path.next())) + { + if (dirent->type == osd::directory::entry::entry_type::DIR && strcmp(dirent->name, ".") != 0 && strcmp(dirent->name, "..") != 0) + { + name = dirent->name; + auto i = strreplace(name, "_", " ("); + if (i > 0) + name.append(")"); + m_languages.emplace_back(std::move(name)); + } + } + std::sort( + std::next(m_languages.begin()), + m_languages.end(), + [] (std::string const &x, std::string const &y) { return 0 > core_stricmp(x, y); }); + + char const *const lang = machine().options().language(); + if (*lang) + { + auto const found = std::lower_bound( + std::next(m_languages.begin()), + m_languages.end(), + lang, + [] (std::string const &x, char const *y) { return 0 > core_stricmp(x, y); }); + if ((m_languages.end() != found) && !core_stricmp(*found, lang)) + m_currlang = std::distance(m_languages.begin(), found); + } + else + { + m_currlang = 0; + } +} + +//------------------------------------------------- +// find translated system names +//------------------------------------------------- + +void menu_custom_ui::find_sysnames() +{ + m_sysnames.emplace_back(_("[built-in]")); + + path_iterator search(ui().options().history_path()); + std::string path; + while (search.next(path)) + { + file_enumerator dir(path); + osd::directory::entry const *dirent; + while ((dirent = dir.next())) + { + if (dirent->type == osd::directory::entry::entry_type::FILE && core_filename_ends_with(dirent->name, ".lst")) + m_sysnames.emplace_back(dirent->name); + } + } + std::sort( + m_sysnames.begin(), + m_sysnames.end(), + [] (std::string const &x, std::string const &y) { return 0 > core_stricmp(x, y); }); + + char const *const names = ui().options().system_names(); + if (*names) + { + auto const found = std::lower_bound( + std::next(m_sysnames.begin()), + m_sysnames.end(), + names, + [] (std::string const &x, char const *y) { return 0 > core_stricmp(x, y); }); + m_currsysnames = std::distance(m_sysnames.begin(), found); + if ((m_sysnames.end() == found) || core_stricmp(*found, names)) + m_sysnames.emplace(found, names); + } + else + { + m_currsysnames = 0; + } } + //------------------------------------------------- // ctor //------------------------------------------------- -menu_font_ui::menu_font_ui(mame_ui_manager &mui, render_container &container) : menu(mui, container) +menu_font_ui::menu_font_ui(mame_ui_manager &mui, render_container &container, std::function<void (bool)> &&handler) + : menu(mui, container) + , m_handler(std::move(handler)) + , m_fonts() + , m_font_min(parse_number<int>(mui.options().get_entry(OPTION_FONT_ROWS)->minimum())) + , m_font_max(parse_number<int>(mui.options().get_entry(OPTION_FONT_ROWS)->maximum())) + , m_font_size(mui.options().font_rows()) + , m_info_min(parse_number<float>(mui.options().get_entry(OPTION_INFOS_SIZE)->minimum())) + , m_info_max(parse_number<float>(mui.options().get_entry(OPTION_INFOS_SIZE)->maximum())) + , m_info_size(mui.options().infos_size()) + , m_face_changed(false) + , m_changed(false) + , m_actual(0U) { - ui_options &moptions = mui.options(); + set_process_flags(PROCESS_LR_REPEAT); + set_heading(_("UI Fonts")); + std::string name(mui.machine().options().ui_font()); list(); @@ -192,7 +347,6 @@ menu_font_ui::menu_font_ui(mame_ui_manager &mui, render_container &container) : m_bold = (strreplace(name, "[B]", "") + strreplace(name, "[b]", "") > 0); m_italic = (strreplace(name, "[I]", "") + strreplace(name, "[i]", "") > 0); #endif - m_actual = 0; for (std::size_t index = 0; index < m_fonts.size(); index++) { @@ -202,13 +356,6 @@ menu_font_ui::menu_font_ui(mame_ui_manager &mui, render_container &container) : break; } } - - m_info_size = moptions.infos_size(); - m_font_size = moptions.font_rows(); - m_info_max = atof(moptions.get_entry(OPTION_INFOS_SIZE)->maximum()); - m_info_min = atof(moptions.get_entry(OPTION_INFOS_SIZE)->minimum()); - m_font_max = atof(moptions.get_entry(OPTION_FONT_ROWS)->maximum()); - m_font_min = atof(moptions.get_entry(OPTION_FONT_ROWS)->minimum()); } //------------------------------------------------- @@ -224,112 +371,146 @@ void menu_font_ui::list() } //------------------------------------------------- -// dtor +// menu dismissed //------------------------------------------------- -menu_font_ui::~menu_font_ui() +void menu_font_ui::menu_dismissed() { - std::string error_string; - ui_options &moptions = ui().options(); + if (m_changed) + { + ui_options &moptions = ui().options(); - std::string name(m_fonts[m_actual].first); + if (m_face_changed) + { + std::string name(m_fonts[m_actual].first); #ifdef UI_WINDOWS - if (name != "default") - { - if (m_italic) - name.insert(0, "[I]"); - if (m_bold) - name.insert(0, "[B]"); - } + if (name != "default") + { + if (m_italic) + name.insert(0, "[I]"); + if (m_bold) + name.insert(0, "[B]"); + } #endif - machine().options().set_value(OPTION_UI_FONT, name, OPTION_PRIORITY_CMDLINE); - moptions.set_value(OPTION_INFOS_SIZE, m_info_size, OPTION_PRIORITY_CMDLINE); - moptions.set_value(OPTION_FONT_ROWS, m_font_size, OPTION_PRIORITY_CMDLINE); + machine().options().set_value(OPTION_UI_FONT, name, OPTION_PRIORITY_CMDLINE); + } + moptions.set_value(OPTION_INFOS_SIZE, m_info_size, OPTION_PRIORITY_CMDLINE); + moptions.set_value(OPTION_FONT_ROWS, m_font_size, OPTION_PRIORITY_CMDLINE); + + // OPTION_FONT_ROWS was changed; update the font info + ui().update_target_font_height(); + } - // OPTION_FONT_ROWS was changed; update the font info - ui().update_target_font_height(); + if (m_handler) + m_handler(m_changed); } //------------------------------------------------- // handle //------------------------------------------------- -void menu_font_ui::handle() +bool menu_font_ui::handle(event const *ev) { - bool changed = false; - - // process the menu - const event *menu_event = process(PROCESS_LR_REPEAT); + if (!ev || !ev->itemref) + return false; - if (menu_event != nullptr && menu_event->itemref != nullptr) - switch ((uintptr_t)menu_event->itemref) + switch ((uintptr_t)ev->itemref) + { + case FONT_SIZE: + if ((ev->iptkey == IPT_UI_LEFT) || (ev->iptkey == IPT_UI_RIGHT) || (ev->iptkey == IPT_UI_CLEAR)) { - case INFOS_SIZE: - if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) - { - (menu_event->iptkey == IPT_UI_RIGHT) ? m_info_size += 0.05f : m_info_size -= 0.05f; - changed = true; - } - break; - - case FONT_SIZE: - if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) - { - (menu_event->iptkey == IPT_UI_RIGHT) ? m_font_size++ : m_font_size--; - changed = true; - } - break; + m_changed = true; + if (ev->iptkey == IPT_UI_LEFT) + --m_font_size; + else if (ev->iptkey == IPT_UI_RIGHT) + ++m_font_size; + else + m_font_size = parse_number<int>(ui().options().get_entry(OPTION_FONT_ROWS)->default_value().c_str()); + ev->item->set_subtext(string_format("%d", m_font_size)); + ev->item->set_flags(get_arrow_flags(m_font_min, m_font_max, m_font_size)); + return true; + } + break; + case INFOS_SIZE: + if ((ev->iptkey == IPT_UI_LEFT) || (ev->iptkey == IPT_UI_RIGHT) || (ev->iptkey == IPT_UI_CLEAR)) + { + m_changed = true; + if (ev->iptkey == IPT_UI_LEFT) + m_info_size -= 0.05f; + else if (ev->iptkey == IPT_UI_RIGHT) + m_info_size += 0.05f; + else + m_info_size = parse_number<float>(ui().options().get_entry(OPTION_INFOS_SIZE)->default_value().c_str()); + ev->item->set_subtext(string_format("%.2f", m_info_size)); + ev->item->set_flags(get_arrow_flags(m_info_min, m_info_max, m_info_size)); + return true; + } + break; - case MUI_FNT: - if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) - { - (menu_event->iptkey == IPT_UI_RIGHT) ? m_actual++ : m_actual--; - changed = true; - } - else if (menu_event->iptkey == IPT_UI_SELECT) - { - std::vector<std::string> display_names; - display_names.reserve(m_fonts.size()); - for (auto const &font : m_fonts) - display_names.emplace_back(font.second); - menu::stack_push<menu_selector>( - ui(), container(), std::move(display_names), m_actual, - [this] (int selection) - { - m_actual = selection; - reset(reset_options::REMEMBER_REF); - }); - changed = true; - } - break; + case MUI_FNT: + if ((ev->iptkey == IPT_UI_LEFT) || (ev->iptkey == IPT_UI_RIGHT) || (ev->iptkey == IPT_UI_CLEAR)) + { + m_face_changed = true; + m_changed = true; + if (ev->iptkey == IPT_UI_LEFT) + --m_actual; + else if (ev->iptkey == IPT_UI_RIGHT) + ++m_actual; + else + m_actual = 0; + reset(reset_options::REMEMBER_REF); + } + else if (ev->iptkey == IPT_UI_SELECT) + { + std::vector<std::string> display_names; + display_names.reserve(m_fonts.size()); + for (auto const &font : m_fonts) + display_names.emplace_back(font.second); + menu::stack_push<menu_selector>( + ui(), container(), _("UI Font"), std::move(display_names), m_actual, + [this] (int selection) + { + m_face_changed = true; + m_changed = true; + m_actual = selection; + reset(reset_options::REMEMBER_REF); + }); + } + break; #ifdef UI_WINDOWS - case MUI_BOLD: - case MUI_ITALIC: - if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT || menu_event->iptkey == IPT_UI_SELECT) - { - ((uintptr_t)menu_event->itemref == MUI_BOLD) ? m_bold = !m_bold : m_italic = !m_italic; - changed = true; - } - break; -#endif + case MUI_BOLD: + case MUI_ITALIC: + if ((ev->iptkey == IPT_UI_LEFT) || (ev->iptkey == IPT_UI_RIGHT) || (ev->iptkey == IPT_UI_SELECT) || (ev->iptkey == IPT_UI_CLEAR)) + { + m_face_changed = true; + m_changed = true; + bool &val = ((uintptr_t)ev->itemref == MUI_BOLD) ? m_bold : m_italic; + if (ev->iptkey == IPT_UI_CLEAR) + val = false; + else + val = !val; + ev->item->set_subtext(val ? _("On") : _("Off")); + ev->item->set_flags(val ? FLAG_LEFT_ARROW : FLAG_RIGHT_ARROW); + return true; } + break; +#endif + } - if (changed) - reset(reset_options::REMEMBER_REF); + return false; } //------------------------------------------------- // populate //------------------------------------------------- -void menu_font_ui::populate(float &customtop, float &custombottom) +void menu_font_ui::populate() { // set filter arrow uint32_t arrow_flags; - // add fonts option arrow_flags = get_arrow_flags<std::uint16_t>(0, m_fonts.size() - 1, m_actual); item_append(_("UI Font"), m_fonts[m_actual].second, arrow_flags, (void *)(uintptr_t)MUI_FNT); @@ -342,41 +523,41 @@ void menu_font_ui::populate(float &customtop, float &custombottom) #endif arrow_flags = get_arrow_flags(m_font_min, m_font_max, m_font_size); - item_append(_("Lines"), string_format("%2d", m_font_size), arrow_flags, (void *)(uintptr_t)FONT_SIZE); + item_append(_("Lines"), string_format("%d", m_font_size), arrow_flags, (void *)(uintptr_t)FONT_SIZE); item_append(menu_item_type::SEPARATOR); - // add item arrow_flags = get_arrow_flags(m_info_min, m_info_max, m_info_size); - item_append(_("Infos text size"), string_format("%3.2f", m_info_size), arrow_flags, (void *)(uintptr_t)INFOS_SIZE); + item_append(_("Infos text size"), string_format("%.2f", m_info_size), arrow_flags, (void *)(uintptr_t)INFOS_SIZE); item_append(menu_item_type::SEPARATOR); +} - custombottom = customtop = ui().get_line_height() + 3.0f * ui().box_tb_border(); +//------------------------------------------------- +// recompute metrics +//------------------------------------------------- + +void menu_font_ui::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + menu::recompute_metrics(width, height, aspect); + + set_custom_space(0.0f, line_height() + 3.0f * tb_border()); } //------------------------------------------------- // perform our special rendering //------------------------------------------------- -void menu_font_ui::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_font_ui::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { - // top text - char const *const toptext[] = { _("UI Fonts Settings") }; - draw_text_box( - std::begin(toptext), std::end(toptext), - 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); - if (uintptr_t(selectedref) == INFOS_SIZE) { char const *const bottomtext[] = { _("Sample text - Lorem ipsum dolor sit amet, consectetur adipiscing elit.") }; draw_text_box( std::begin(bottomtext), std::end(bottomtext), - origx1, origx2, origy2 + ui().box_tb_border(), origy2 + bottom, - ui::text_layout::LEFT, ui::text_layout::NEVER, false, - ui().colors().text_color(), UI_GREEN_COLOR, m_info_size); + origx1, origx2, origy2 + tb_border(), origy2 + bottom, + text_layout::text_justify::LEFT, text_layout::word_wrapping::NEVER, false, + ui().colors().text_color(), UI_GREEN_COLOR, ui().get_line_height(m_info_size)); } } @@ -387,6 +568,8 @@ void menu_font_ui::custom_render(void *selectedref, float top, float bottom, flo menu_colors_ui::menu_colors_ui(mame_ui_manager &mui, render_container &container) : menu(mui, container) { + set_heading(_("UI Colors")); + SET_COLOR_UI(m_color_table, UI_BACKGROUND_COLOR); SET_COLOR_UI(m_color_table, UI_BORDER_COLOR); SET_COLOR_UI(m_color_table, UI_CLONE_COLOR); @@ -406,16 +589,16 @@ menu_colors_ui::menu_colors_ui(mame_ui_manager &mui, render_container &container } //------------------------------------------------- -// dtor +// menu dismissed //------------------------------------------------- -menu_colors_ui::~menu_colors_ui() +void menu_colors_ui::menu_dismissed() { std::string dec_color; for (int index = 1; index < MUI_RESTORE; index++) { dec_color = string_format("%x", (uint32_t)m_color_table[index].color); - ui().options().set_value(m_color_table[index].option, dec_color.c_str(), OPTION_PRIORITY_CMDLINE); + ui().options().set_value(m_color_table[index].option, dec_color, OPTION_PRIORITY_CMDLINE); } // refresh our cached colors @@ -426,164 +609,188 @@ menu_colors_ui::~menu_colors_ui() // handle //------------------------------------------------- -void menu_colors_ui::handle() +bool menu_colors_ui::handle(event const *ev) { - bool changed = false; - - // process the menu - const event *menu_event = process(0); - - if (menu_event != nullptr && menu_event->itemref != nullptr && menu_event->iptkey == IPT_UI_SELECT) + if (ev && ev->itemref && ev->iptkey == IPT_UI_SELECT) { - if ((uintptr_t)menu_event->itemref != MUI_RESTORE) - menu::stack_push<menu_rgb_ui>(ui(), container(), &m_color_table[(uintptr_t)menu_event->itemref].color, selected_item().text); + if ((uintptr_t)ev->itemref != MUI_RESTORE) + { + menu::stack_push<menu_rgb_ui>(ui(), container(), &m_color_table[(uintptr_t)ev->itemref].color, std::string(selected_item().text())); + } else { - changed = true; restore_colors(); + return true; } } - if (changed) - reset(reset_options::REMEMBER_REF); + return false; } //------------------------------------------------- // populate //------------------------------------------------- -void menu_colors_ui::populate(float &customtop, float &custombottom) +void menu_colors_ui::populate() { - item_append(_("Normal text"), "", 0, (void *)(uintptr_t)MUI_TEXT_COLOR); - item_append(_("Selected color"), "", 0, (void *)(uintptr_t)MUI_SELECTED_COLOR); - item_append(_("Normal text background"), "", 0, (void *)(uintptr_t)MUI_TEXT_BG_COLOR); - item_append(_("Selected background color"), "", 0, (void *)(uintptr_t)MUI_SELECTED_BG_COLOR); - item_append(_("Subitem color"), "", 0, (void *)(uintptr_t)MUI_SUBITEM_COLOR); - item_append(_("Clone"), "", 0, (void *)(uintptr_t)MUI_CLONE_COLOR); - item_append(_("Border"), "", 0, (void *)(uintptr_t)MUI_BORDER_COLOR); - item_append(_("Background"), "", 0, (void *)(uintptr_t)MUI_BACKGROUND_COLOR); - item_append(_("Dipswitch"), "", 0, (void *)(uintptr_t)MUI_DIPSW_COLOR); - item_append(_("Unavailable color"), "", 0, (void *)(uintptr_t)MUI_UNAVAILABLE_COLOR); - item_append(_("Slider color"), "", 0, (void *)(uintptr_t)MUI_SLIDER_COLOR); - item_append(_("Gfx viewer background"), "", 0, (void *)(uintptr_t)MUI_GFXVIEWER_BG_COLOR); - item_append(_("Mouse over color"), "", 0, (void *)(uintptr_t)MUI_MOUSEOVER_COLOR); - item_append(_("Mouse over background color"), "", 0, (void *)(uintptr_t)MUI_MOUSEOVER_BG_COLOR); - item_append(_("Mouse down color"), "", 0, (void *)(uintptr_t)MUI_MOUSEDOWN_COLOR); - item_append(_("Mouse down background color"), "", 0, (void *)(uintptr_t)MUI_MOUSEDOWN_BG_COLOR); + item_append(_("color-option", "Normal text"), 0, (void *)(uintptr_t)MUI_TEXT_COLOR); + item_append(_("color-option", "Selected color"), 0, (void *)(uintptr_t)MUI_SELECTED_COLOR); + item_append(_("color-option", "Normal text background"), 0, (void *)(uintptr_t)MUI_TEXT_BG_COLOR); + item_append(_("color-option", "Selected background color"), 0, (void *)(uintptr_t)MUI_SELECTED_BG_COLOR); + item_append(_("color-option", "Subitem color"), 0, (void *)(uintptr_t)MUI_SUBITEM_COLOR); + item_append(_("color-option", "Clone"), 0, (void *)(uintptr_t)MUI_CLONE_COLOR); + item_append(_("color-option", "Border"), 0, (void *)(uintptr_t)MUI_BORDER_COLOR); + item_append(_("color-option", "Background"), 0, (void *)(uintptr_t)MUI_BACKGROUND_COLOR); + item_append(_("color-option", "DIP switch"), 0, (void *)(uintptr_t)MUI_DIPSW_COLOR); + item_append(_("color-option", "Unavailable color"), 0, (void *)(uintptr_t)MUI_UNAVAILABLE_COLOR); + item_append(_("color-option", "Slider color"), 0, (void *)(uintptr_t)MUI_SLIDER_COLOR); + item_append(_("color-option", "Graphics viewer background"), 0, (void *)(uintptr_t)MUI_GFXVIEWER_BG_COLOR); + item_append(_("color-option", "Mouse over color"), 0, (void *)(uintptr_t)MUI_MOUSEOVER_COLOR); + item_append(_("color-option", "Mouse over background color"), 0, (void *)(uintptr_t)MUI_MOUSEOVER_BG_COLOR); + item_append(_("color-option", "Mouse down color"), 0, (void *)(uintptr_t)MUI_MOUSEDOWN_COLOR); + item_append(_("color-option", "Mouse down background color"), 0, (void *)(uintptr_t)MUI_MOUSEDOWN_BG_COLOR); item_append(menu_item_type::SEPARATOR); - item_append(_("Restore originals colors"), "", 0, (void *)(uintptr_t)MUI_RESTORE); - custombottom = customtop = ui().get_line_height() + 3.0f * ui().box_tb_border(); + item_append(_("Restore default colors"), 0, (void *)(uintptr_t)MUI_RESTORE); } //------------------------------------------------- -// perform our special rendering +// recompute metrics //------------------------------------------------- -void menu_colors_ui::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_colors_ui::recompute_metrics(uint32_t width, uint32_t height, float aspect) { - // top text - char const *const toptext[] = { _("UI Colors Settings") }; - draw_text_box( - std::begin(toptext), std::end(toptext), - 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); + menu::recompute_metrics(width, height, aspect); + + set_custom_space(0.0f, line_height() + 3.0f * tb_border()); +} - // bottom text +//------------------------------------------------- +// perform our special rendering +//------------------------------------------------- + +void menu_colors_ui::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +{ // get the text for 'UI Select' - std::string const bottomtext[] = { util::string_format(_("Double click or press %1$s to change the color value"), machine().input().seq_name(machine().ioport().type_seq(IPT_UI_SELECT, 0, SEQ_TYPE_STANDARD))) }; + std::string const bottomtext[] = { util::string_format(_("Double-click or press %1$s to change color"), ui().get_general_input_setting(IPT_UI_SELECT)) }; draw_text_box( std::begin(bottomtext), std::end(bottomtext), - origx1, origx2, origy2 + ui().box_tb_border(), origy2 + bottom, - ui::text_layout::CENTER, ui::text_layout::TRUNCATE, false, - ui().colors().text_color(), UI_RED_COLOR, 1.0f); + origx1, origx2, origy2 + tb_border(), origy2 + bottom, + text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, false, + ui().colors().text_color(), ui().colors().background_color()); // compute maxwidth char const *const topbuf = _("Menu Preview"); - float width; - ui().draw_text_full(container(), topbuf, 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::NEVER, - mame_ui_manager::NONE, rgb_t::white(), rgb_t::black(), &width, nullptr); - float maxwidth = width + 2.0f * ui().box_lr_border(); + float width = get_string_width(topbuf); + float maxwidth = width + 2.0f * lr_border(); std::string sampletxt[5]; - sampletxt[0] = _("Normal"); - sampletxt[1] = _("Subitem"); - sampletxt[2] = _("Selected"); - sampletxt[3] = _("Mouse Over"); - sampletxt[4] = _("Clone"); + sampletxt[0] = _("color-sample", "Normal"); + sampletxt[1] = _("color-sample", "Subitem"); + sampletxt[2] = _("color-sample", "Selected"); + sampletxt[3] = _("color-sample", "Mouse Over"); + sampletxt[4] = _("color-sample", "Clone"); for (auto & elem: sampletxt) { - ui().draw_text_full(container(), elem.c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::NEVER, - mame_ui_manager::NONE, rgb_t::white(), rgb_t::black(), &width, nullptr); - width += 2 * ui().box_lr_border(); - maxwidth = std::max(maxwidth, width); + width = get_string_width(elem); + maxwidth = std::max(maxwidth, width + 2.0f * lr_border()); } // compute our bounds for header - float x1 = origx2 + 2.0f * ui().box_lr_border(); + float x1 = origx2 + 2.0f * lr_border(); float x2 = x1 + maxwidth; float y1 = origy1; - float y2 = y1 + bottom - ui().box_tb_border(); + float y2 = y1 + bottom - tb_border(); // draw a box ui().draw_outlined_box(container(), x1, y1, x2, y2, UI_GREEN_COLOR); // take off the borders - x1 += ui().box_lr_border(); - x2 -= ui().box_lr_border(); - y1 += ui().box_tb_border(); - y2 -= ui().box_tb_border(); + x1 += lr_border(); + x2 -= lr_border(); + y1 += tb_border(); + y2 -= tb_border(); // draw the text within it - ui().draw_text_full(container(), topbuf, x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER, - mame_ui_manager::NORMAL, ui().colors().text_color(), ui().colors().text_bg_color(), nullptr, nullptr); + draw_text_normal( + topbuf, + x1, y1, x2 - x1, + text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, + ui().colors().text_color()); // compute our bounds for menu preview - float line_height = ui().get_line_height(); - x1 -= ui().box_lr_border(); - x2 += ui().box_lr_border(); - y1 = y2 + 2.0f * ui().box_tb_border(); - y2 = y1 + 5.0f * line_height + 2.0f * ui().box_tb_border(); + x1 -= lr_border(); + x2 += lr_border(); + y1 = y2 + 2.0f * tb_border(); + y2 = y1 + 5.0f * line_height() + 2.0f * tb_border(); // draw a box ui().draw_outlined_box(container(), x1, y1, x2, y2, m_color_table[MUI_BACKGROUND_COLOR].color); // take off the borders - x1 += ui().box_lr_border(); - x2 -= ui().box_lr_border(); - y1 += ui().box_tb_border(); + x1 += lr_border(); + x2 -= lr_border(); + y1 += tb_border(); // draw normal text - ui().draw_text_full(container(), sampletxt[0].c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER, - mame_ui_manager::NORMAL, m_color_table[MUI_TEXT_COLOR].color, m_color_table[MUI_TEXT_BG_COLOR].color, nullptr, nullptr); - y1 += line_height; + ui().draw_text_full( + container(), + sampletxt[0], + x1, y1, x2 - x1, + text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, + mame_ui_manager::NORMAL, m_color_table[MUI_TEXT_COLOR].color, m_color_table[MUI_TEXT_BG_COLOR].color, + nullptr, nullptr, + line_height()); + y1 += line_height(); // draw subitem text - ui().draw_text_full(container(), sampletxt[1].c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER, - mame_ui_manager::NORMAL, m_color_table[MUI_SUBITEM_COLOR].color, m_color_table[MUI_TEXT_BG_COLOR].color, nullptr, nullptr); - y1 += line_height; + ui().draw_text_full( + container(), + sampletxt[1], + x1, y1, x2 - x1, + text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, + mame_ui_manager::NORMAL, m_color_table[MUI_SUBITEM_COLOR].color, m_color_table[MUI_TEXT_BG_COLOR].color, + nullptr, nullptr, + line_height()); + y1 += line_height(); // draw selected text - highlight(x1, y1, x2, y1 + line_height, m_color_table[MUI_SELECTED_BG_COLOR].color); - ui().draw_text_full(container(), sampletxt[2].c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER, - mame_ui_manager::NORMAL, m_color_table[MUI_SELECTED_COLOR].color, m_color_table[MUI_SELECTED_BG_COLOR].color, nullptr, nullptr); - y1 += line_height; + highlight(x1, y1, x2, y1 + line_height(), m_color_table[MUI_SELECTED_BG_COLOR].color); + ui().draw_text_full( + container(), + sampletxt[2], + x1, y1, x2 - x1, + text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, + mame_ui_manager::NORMAL, m_color_table[MUI_SELECTED_COLOR].color, m_color_table[MUI_SELECTED_BG_COLOR].color, + nullptr, nullptr, + line_height()); + y1 += line_height(); // draw mouse over text - highlight(x1, y1, x2, y1 + line_height, m_color_table[MUI_MOUSEOVER_BG_COLOR].color); - ui().draw_text_full(container(), sampletxt[3].c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER, - mame_ui_manager::NORMAL, m_color_table[MUI_MOUSEOVER_COLOR].color, m_color_table[MUI_MOUSEOVER_BG_COLOR].color, nullptr, nullptr); - y1 += line_height; + highlight(x1, y1, x2, y1 + line_height(), m_color_table[MUI_MOUSEOVER_BG_COLOR].color); + ui().draw_text_full( + container(), + sampletxt[3], + x1, y1, x2 - x1, + text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, + mame_ui_manager::NORMAL, m_color_table[MUI_MOUSEOVER_COLOR].color, m_color_table[MUI_MOUSEOVER_BG_COLOR].color, + nullptr, nullptr, + line_height()); + y1 += line_height(); // draw clone text - ui().draw_text_full(container(), sampletxt[4].c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER, - mame_ui_manager::NORMAL, m_color_table[MUI_CLONE_COLOR].color, m_color_table[MUI_TEXT_BG_COLOR].color, nullptr, nullptr); - + ui().draw_text_full( + container(), + sampletxt[4], + x1, y1, x2 - x1, + text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, + mame_ui_manager::NORMAL, m_color_table[MUI_CLONE_COLOR].color, m_color_table[MUI_TEXT_BG_COLOR].color, + nullptr, nullptr, + line_height()); } //------------------------------------------------- @@ -601,260 +808,212 @@ void menu_colors_ui::restore_colors() // ctor //------------------------------------------------- -menu_rgb_ui::menu_rgb_ui(mame_ui_manager &mui, render_container &container, rgb_t *_color, std::string _title) - : menu(mui, container), - m_color(_color), - m_search(), - m_key_active(false), - m_lock_ref(0), - m_title(_title) -{ -} - -//------------------------------------------------- -// dtor -//------------------------------------------------- - -menu_rgb_ui::~menu_rgb_ui() +menu_rgb_ui::menu_rgb_ui(mame_ui_manager &mui, render_container &container, rgb_t *color, std::string &&title) + : menu(mui, container) + , m_color(color) + , m_search() + , m_key_active(false) + , m_lock_ref(0) { + set_process_flags(PROCESS_LR_REPEAT); + set_heading(std::move(title)); } //------------------------------------------------- // handle //------------------------------------------------- -void menu_rgb_ui::handle() +bool menu_rgb_ui::handle(event const *ev) { - bool changed = false; + if (!ev || !ev->itemref) + return false; - // process the menu - const event *menu_event; - - if (!m_key_active) - menu_event = process(PROCESS_LR_REPEAT); - else - menu_event = process(PROCESS_ONLYCHAR); - - if (menu_event != nullptr && menu_event->itemref != nullptr) + switch (ev->iptkey) { - switch ((uintptr_t)menu_event->itemref) + case IPT_UI_LEFT: + case IPT_UI_RIGHT: { + bool changed = false; + int updated = (IPT_UI_LEFT == ev->iptkey) ? -1 : 1; + switch (uintptr_t(ev->itemref)) + { case RGB_ALPHA: - if (menu_event->iptkey == IPT_UI_LEFT && m_color->a() > 1) - { - m_color->set_a(m_color->a() - 1); - changed = true; - } - - else if (menu_event->iptkey == IPT_UI_RIGHT && m_color->a() < 255) - { - m_color->set_a(m_color->a() + 1); - changed = true; - } - - else if (menu_event->iptkey == IPT_UI_SELECT || menu_event->iptkey == IPT_SPECIAL) + updated += m_color->a(); + if ((0 <= updated) && (255 >= updated)) { - inkey_special(menu_event); + m_color->set_a(updated); changed = true; } - break; - case RGB_RED: - if (menu_event->iptkey == IPT_UI_LEFT && m_color->r() > 1) - { - m_color->set_r(m_color->r() - 1); - changed = true; - } - - else if (menu_event->iptkey == IPT_UI_RIGHT && m_color->r() < 255) + updated += m_color->r(); + if ((0 <= updated) && (255 >= updated)) { - m_color->set_r(m_color->r() + 1); + m_color->set_r(updated); changed = true; } - - else if (menu_event->iptkey == IPT_UI_SELECT || menu_event->iptkey == IPT_SPECIAL) - { - inkey_special(menu_event); - changed = true; - } - break; - case RGB_GREEN: - if (menu_event->iptkey == IPT_UI_LEFT && m_color->g() > 1) - { - m_color->set_g(m_color->g() - 1); - changed = true; - } - - else if (menu_event->iptkey == IPT_UI_RIGHT && m_color->g() < 255) - { - m_color->set_g(m_color->g() + 1); - changed = true; - } - - else if (menu_event->iptkey == IPT_UI_SELECT || menu_event->iptkey == IPT_SPECIAL) + updated += m_color->g(); + if ((0 <= updated) && (255 >= updated)) { - inkey_special(menu_event); + m_color->set_g(updated); changed = true; } - break; - case RGB_BLUE: - if (menu_event->iptkey == IPT_UI_LEFT && m_color->b() > 1) - { - m_color->set_b(m_color->b() - 1); - changed = true; - } - - else if (menu_event->iptkey == IPT_UI_RIGHT && m_color->b() < 255) - { - m_color->set_b(m_color->b() + 1); - changed = true; - } - - else if (menu_event->iptkey == IPT_UI_SELECT || menu_event->iptkey == IPT_SPECIAL) + updated += m_color->b(); + if ((0 <= updated) && (255 >= updated)) { - inkey_special(menu_event); + m_color->set_b(updated); changed = true; } - break; + } + if (changed) + { + ev->item->set_subtext(string_format("%3u", updated)); + ev->item->set_flags(get_arrow_flags<uint8_t>(0, 255, updated)); + return true; + } + } + break; - case PALETTE_CHOOSE: - if (menu_event->iptkey == IPT_UI_SELECT) - menu::stack_push<menu_palette_sel>(ui(), container(), *m_color); - break; + case IPT_UI_SELECT: + if (uintptr_t(ev->itemref) == PALETTE_CHOOSE) + { + menu::stack_push<menu_palette_sel>(ui(), container(), *m_color); + break; + } + [[fallthrough]]; + case IPT_SPECIAL: + switch (uintptr_t(ev->itemref)) + { + case RGB_ALPHA: + case RGB_RED: + case RGB_GREEN: + case RGB_BLUE: + return inkey_special(ev); } + break; } - if (changed) - reset(reset_options::REMEMBER_REF); + return false; } //------------------------------------------------- // populate //------------------------------------------------- -void menu_rgb_ui::populate(float &customtop, float &custombottom) +void menu_rgb_ui::populate() { // set filter arrow - uint32_t arrow_flags = FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW; std::string s_text = std::string(m_search).append("_"); - item_append(_("ARGB Settings"), "", FLAG_DISABLE | FLAG_UI_HEADING, nullptr); + item_append(_("ARGB Settings"), FLAG_DISABLE | FLAG_UI_HEADING, nullptr); if (m_lock_ref != RGB_ALPHA) { - arrow_flags = get_arrow_flags<uint8_t>(0, 255, m_color->a()); - item_append(_("Alpha"), string_format("%3u", m_color->a()), arrow_flags, (void *)(uintptr_t)RGB_ALPHA); + uint32_t arrow_flags = get_arrow_flags<uint8_t>(0, 255, m_color->a()); + item_append(_("color-channel", "Alpha"), string_format("%3u", m_color->a()), arrow_flags, (void *)(uintptr_t)RGB_ALPHA); } else - item_append(_("Alpha"), s_text, 0, (void *)(uintptr_t)RGB_ALPHA); + item_append(_("color-channel", "Alpha"), s_text, 0, (void *)(uintptr_t)RGB_ALPHA); if (m_lock_ref != RGB_RED) { - arrow_flags = get_arrow_flags<uint8_t>(0, 255, m_color->r()); - item_append(_("Red"), string_format("%3u", m_color->r()), arrow_flags, (void *)(uintptr_t)RGB_RED); + uint32_t arrow_flags = get_arrow_flags<uint8_t>(0, 255, m_color->r()); + item_append(_("color-channel", "Red"), string_format("%3u", m_color->r()), arrow_flags, (void *)(uintptr_t)RGB_RED); } else - item_append(_("Red"), s_text, 0, (void *)(uintptr_t)RGB_RED); + item_append(_("color-channel", "Red"), s_text, 0, (void *)(uintptr_t)RGB_RED); if (m_lock_ref != RGB_GREEN) { - arrow_flags = get_arrow_flags<uint8_t>(0, 255, m_color->g()); - item_append(_("Green"), string_format("%3u", m_color->g()), arrow_flags, (void *)(uintptr_t)RGB_GREEN); + uint32_t arrow_flags = get_arrow_flags<uint8_t>(0, 255, m_color->g()); + item_append(_("color-channel", "Green"), string_format("%3u", m_color->g()), arrow_flags, (void *)(uintptr_t)RGB_GREEN); } else - item_append(_("Green"), s_text, 0, (void *)(uintptr_t)RGB_GREEN); + item_append(_("color-channel", "Green"), s_text, 0, (void *)(uintptr_t)RGB_GREEN); if (m_lock_ref != RGB_BLUE) { - arrow_flags = get_arrow_flags<uint8_t>(0, 255, m_color->b()); - item_append(_("Blue"), string_format("%3u", m_color->b()), arrow_flags, (void *)(uintptr_t)RGB_BLUE); + uint32_t arrow_flags = get_arrow_flags<uint8_t>(0, 255, m_color->b()); + item_append(_("color-channel", "Blue"), string_format("%3u", m_color->b()), arrow_flags, (void *)(uintptr_t)RGB_BLUE); } else - item_append(_("Blue"), s_text, 0, (void *)(uintptr_t)RGB_BLUE); + item_append(_("color-channel", "Blue"), s_text, 0, (void *)(uintptr_t)RGB_BLUE); item_append(menu_item_type::SEPARATOR); - item_append(_("Choose from palette"), "", 0, (void *)(uintptr_t)PALETTE_CHOOSE); + item_append(_("Choose from palette"), 0, (void *)(uintptr_t)PALETTE_CHOOSE); item_append(menu_item_type::SEPARATOR); - - custombottom = customtop = ui().get_line_height() + 3.0f * ui().box_tb_border(); } //------------------------------------------------- -// perform our special rendering +// recompute metrics //------------------------------------------------- -void menu_rgb_ui::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_rgb_ui::recompute_metrics(uint32_t width, uint32_t height, float aspect) { - float width, maxwidth = origx2 - origx1; - - // top text - ui().draw_text_full(container(), m_title.c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::NEVER, - mame_ui_manager::NONE, rgb_t::white(), rgb_t::black(), &width); - width += 2 * ui().box_lr_border(); - maxwidth = std::max(maxwidth, width); - - // compute our bounds - float x1 = 0.5f - 0.5f * maxwidth; - float x2 = x1 + maxwidth; - float y1 = origy1 - top; - float y2 = origy1 - ui().box_tb_border(); + menu::recompute_metrics(width, height, aspect); - // draw a box - ui().draw_outlined_box(container(), x1, y1, x2, y2, UI_GREEN_COLOR); + set_custom_space(0.0f, line_height() + 3.0f * tb_border()); +} - // take off the borders - x1 += ui().box_lr_border(); - x2 -= ui().box_lr_border(); - y1 += ui().box_tb_border(); +//------------------------------------------------- +// perform our special rendering +//------------------------------------------------- - // draw the text within it - ui().draw_text_full(container(), m_title.c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER, - mame_ui_manager::NORMAL, ui().colors().text_color(), ui().colors().text_bg_color()); +void menu_rgb_ui::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +{ + float maxwidth = origx2 - origx1; - std::string sampletxt(_("Color preview =")); - ui().draw_text_full(container(), sampletxt.c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::NEVER, - mame_ui_manager::NONE, rgb_t::white(), rgb_t::black(), &width); - width += 2 * ui().box_lr_border(); + std::string sampletxt(_("Color preview:")); + float width = get_string_width(sampletxt); + width += 2 * lr_border(); maxwidth = std::max(origx2 - origx1, width); // compute our bounds - x1 = 0.5f - 0.5f * maxwidth; - x2 = x1 + maxwidth; - y1 = origy2 + ui().box_tb_border(); - y2 = origy2 + bottom; + float x1 = 0.5f - 0.5f * maxwidth; + float x2 = x1 + maxwidth; + float y1 = origy2 + tb_border(); + float y2 = origy2 + bottom; - // draw a box - ui().draw_outlined_box(container(), x1, y1, x1 + width, y2, UI_RED_COLOR); + // draw a box - force black to ensure the text is legible + ui().draw_outlined_box(container(), x1, y1, x2, y2, rgb_t::black()); // take off the borders - x1 += ui().box_lr_border(); - y1 += ui().box_tb_border(); - - // draw the normal text - ui().draw_text_full(container(), sampletxt.c_str(), x1, y1, width - ui().box_lr_border(), ui::text_layout::CENTER, ui::text_layout::NEVER, - mame_ui_manager::NORMAL, rgb_t::white(), rgb_t::black()); - - x1 += width + ui().box_lr_border(); - y1 -= ui().box_tb_border(); - - // draw color box - ui().draw_outlined_box(container(), x1, y1, x2, y2, *m_color); + x1 += lr_border(); + y1 += tb_border(); + + // draw the text label - force white to ensure it's legible + ui().draw_text_full( + container(), + sampletxt, + x1, y1, width - lr_border(), + text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, + mame_ui_manager::NORMAL, rgb_t::white(), rgb_t::black(), + nullptr, nullptr, + line_height()); + + x1 += width + (lr_border() * 2.0f); + x2 -= lr_border(); + y2 -= tb_border(); + + // add white under half the sample swatch to make alpha effects visible + container().add_rect((x1 + x2) * 0.5f, y1, x2, y2, rgb_t::white(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + container().add_rect(x1, y1, x2, y2, *m_color, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } //------------------------------------------------- // handle special key event //------------------------------------------------- -void menu_rgb_ui::inkey_special(const event *menu_event) +bool menu_rgb_ui::inkey_special(const event *menu_event) { if (menu_event->iptkey == IPT_UI_SELECT) { m_key_active = !m_key_active; + set_process_flags(m_key_active ? PROCESS_ONLYCHAR : PROCESS_LR_REPEAT); m_lock_ref = (uintptr_t)menu_event->itemref; if (!m_key_active) @@ -883,30 +1042,39 @@ void menu_rgb_ui::inkey_special(const event *menu_event) m_search.erase(); m_lock_ref = 0; - return; + + menu_event->item->set_subtext(string_format("%3u", val)); + menu_event->item->set_flags(get_arrow_flags<uint8_t>(0, 255, val)); } + else + { + menu_event->item->set_subtext("_"); + menu_event->item->set_flags(0); + } + return true; } - - if (!m_key_active) + else if (m_key_active && input_character(m_search, 3, menu_event->unichar, uchar_is_digit)) { - m_search.erase(); - return; + menu_event->item->set_subtext(m_search + "_"); + return true; + } + else + { + return false; } - - input_character(m_search, 3, menu_event->unichar, uchar_is_digit); } std::pair<const char *, const char *> const menu_palette_sel::s_palette[] = { - { __("White"), "FFFFFFFF" }, - { __("Silver"), "FFC0C0C0" }, - { __("Gray"), "FF808080" }, - { __("Black"), "FF000000" }, - { __("Red"), "FFFF0000" }, - { __("Orange"), "FFFFA500" }, - { __("Yellow"), "FFFFFF00" }, - { __("Green"), "FF00FF00" }, - { __("Blue"), "FF0000FF" }, - { __("Violet"), "FF8F00FF" } + { N_p("color-preset", "White"), "FFFFFFFF" }, + { N_p("color-preset", "Silver"), "FFC0C0C0" }, + { N_p("color-preset", "Gray"), "FF808080" }, + { N_p("color-preset", "Black"), "FF000000" }, + { N_p("color-preset", "Red"), "FFFF0000" }, + { N_p("color-preset", "Orange"), "FFFFA500" }, + { N_p("color-preset", "Yellow"), "FFFFFF00" }, + { N_p("color-preset", "Green"), "FF00FF00" }, + { N_p("color-preset", "Blue"), "FF0000FF" }, + { N_p("color-preset", "Violet"), "FF8F00FF" } }; //------------------------------------------------- @@ -919,40 +1087,32 @@ menu_palette_sel::menu_palette_sel(mame_ui_manager &mui, render_container &conta } //------------------------------------------------- -// dtor -//------------------------------------------------- - -menu_palette_sel::~menu_palette_sel() -{ -} - -//------------------------------------------------- // handle //------------------------------------------------- -void menu_palette_sel::handle() +bool menu_palette_sel::handle(event const *ev) { - // process the menu - const event *menu_event = process(0); - if (menu_event != nullptr && menu_event->itemref != nullptr) + if (ev && ev->itemref) { - if (menu_event->iptkey == IPT_UI_SELECT) + if (ev->iptkey == IPT_UI_SELECT) { - m_original = rgb_t(uint32_t(strtoul(selected_item().subtext.c_str(), nullptr, 16))); - reset_parent(reset_options::SELECT_FIRST); + m_original = rgb_t(uint32_t(strtoul(selected_item().subtext().c_str(), nullptr, 16))); + reset_parent(reset_options::REMEMBER_REF); stack_pop(); } } + + return false; } //------------------------------------------------- // populate //------------------------------------------------- -void menu_palette_sel::populate(float &customtop, float &custombottom) +void menu_palette_sel::populate() { - for (unsigned x = 0; x < ARRAY_LENGTH(s_palette); ++x) - item_append(_(s_palette[x].first), s_palette[x].second, FLAG_COLOR_BOX, (void *)(uintptr_t)(x + 1)); + for (unsigned x = 0; x < std::size(s_palette); ++x) + item_append(_("color-preset", s_palette[x].first), s_palette[x].second, FLAG_COLOR_BOX, (void *)(uintptr_t)(x + 1)); item_append(menu_item_type::SEPARATOR); } diff --git a/src/frontend/mame/ui/custui.h b/src/frontend/mame/ui/custui.h index a419e424375..0a916b82f2a 100644 --- a/src/frontend/mame/ui/custui.h +++ b/src/frontend/mame/ui/custui.h @@ -15,7 +15,13 @@ #include "ui/menu.h" +#include <functional> +#include <string> +#include <vector> + + namespace ui { + //------------------------------------------------- // Custom UI menu //------------------------------------------------- @@ -23,28 +29,24 @@ namespace ui { class menu_custom_ui : public menu { public: - menu_custom_ui(mame_ui_manager &mui, render_container &container); - virtual ~menu_custom_ui() override; + menu_custom_ui(mame_ui_manager &mui, render_container &container, std::function<void ()> &&handler); protected: - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void menu_dismissed() override; private: - enum - { - LANGUAGE_MENU = 1, - FONT_MENU, - COLORS_MENU, - HIDE_MENU - }; - - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; - - static const char *const HIDE_STATUS[]; - - std::vector<std::string> m_lang; - std::uint16_t m_currlang; + virtual void populate() override; + virtual bool handle(event const *ev) override; + + void find_languages(); + void find_sysnames(); + + std::function<void ()> m_handler; + std::vector<std::string> m_languages; + std::vector<std::string> m_sysnames; + std::size_t m_currlang; + std::size_t m_currsysnames; + u8 m_currpanels; }; //------------------------------------------------- @@ -54,35 +56,33 @@ private: class menu_font_ui : public menu { public: - menu_font_ui(mame_ui_manager &mui, render_container &container); - virtual ~menu_font_ui() override; + menu_font_ui(mame_ui_manager &mui, render_container &container, std::function<void (bool)> &&handler); protected: - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) override; + virtual void menu_dismissed() override; private: - enum - { - INFOS_SIZE = 1, - FONT_SIZE, - MUI_FNT, - MUI_BOLD, - MUI_ITALIC - }; - - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; + virtual bool handle(event const *ev) override; void list(); - std::uint16_t m_actual; - std::vector<std::pair<std::string, std::string> > m_fonts; + std::function<void (bool)> m_handler; + std::vector<std::pair<std::string, std::string> > m_fonts; + int const m_font_min, m_font_max; + int m_font_size; + float const m_info_min, m_info_max; + float m_info_size; + bool m_face_changed; + bool m_changed; + + std::uint16_t m_actual; #ifdef UI_WINDOWS - bool m_bold, m_italic; + bool m_bold, m_italic; #endif - float m_info_min, m_info_max, m_info_size; - int m_font_min, m_font_max, m_font_size; }; //------------------------------------------------- @@ -93,10 +93,11 @@ class menu_colors_ui : public menu { public: menu_colors_ui(mame_ui_manager &mui, render_container &container); - virtual ~menu_colors_ui() override; protected: - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) override; + virtual void menu_dismissed() override; private: enum @@ -126,8 +127,8 @@ private: const char *option; }; - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; + virtual bool handle(event const *ev) override; s_color_table m_color_table[MUI_RESTORE]; void restore_colors(); @@ -140,11 +141,11 @@ private: class menu_rgb_ui : public menu { public: - menu_rgb_ui(mame_ui_manager &mui, render_container &container, rgb_t *_color, std::string _title); - virtual ~menu_rgb_ui() override; + menu_rgb_ui(mame_ui_manager &mui, render_container &container, rgb_t *color, std::string &&title); protected: - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) override; private: enum @@ -156,16 +157,15 @@ private: PALETTE_CHOOSE }; - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; + virtual bool handle(event const *ev) override; - void inkey_special(const event *menu_event); + bool inkey_special(const event *menu_event); rgb_t *m_color; std::string m_search; bool m_key_active; int m_lock_ref; - std::string m_title; }; //------------------------------------------------- @@ -176,11 +176,10 @@ class menu_palette_sel : public menu { public: menu_palette_sel(mame_ui_manager &mui, render_container &container, rgb_t &_color); - virtual ~menu_palette_sel() 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; static std::pair<const char *, const char *> const s_palette[]; rgb_t &m_original; diff --git a/src/frontend/mame/ui/datmenu.cpp b/src/frontend/mame/ui/datmenu.cpp index 7df7d2fe4b9..492bdd38e41 100644 --- a/src/frontend/mame/ui/datmenu.cpp +++ b/src/frontend/mame/ui/datmenu.cpp @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:Maurizio Petrarota +// copyright-holders:Maurizio Petrarota, Vas Crabb /********************************************************************* ui/datmenu.cpp @@ -9,52 +9,63 @@ *********************************************************************/ #include "emu.h" +#include "ui/datmenu.h" +#include "ui/systemlist.h" #include "ui/ui.h" -#include "ui/datmenu.h" -#include "ui/utils.h" +#include "luaengine.h" #include "mame.h" + +#include "drivenum.h" #include "rendfont.h" #include "softlist.h" #include "uiinput.h" -#include "luaengine.h" #include <cmath> +#include <limits> +#include <string_view> namespace ui { + //------------------------------------------------- -// ctor / dtor +// construct for currently running or specified +// system //------------------------------------------------- -menu_dats_view::menu_dats_view(mame_ui_manager &mui, render_container &container, const game_driver *driver) - : menu(mui, container) - , m_actual(0) - , m_driver((driver == nullptr) ? &mui.machine().system() : driver) +menu_dats_view::menu_dats_view(mame_ui_manager &mui, render_container &container, const ui_system_info *system) + : menu_textbox(mui, container) + , m_system(!system ? &system_list::instance().systems()[driver_list::find(mui.machine().system().name)] : system) , m_swinfo(nullptr) , m_issoft(false) - + , m_current_tab(0) + , m_tab_line(1.0F, 0.0F) + , m_clicked_tab(-1) { - for (device_image_interface& image : image_interface_iterator(mui.machine().root_device())) + set_process_flags(PROCESS_LR_ALWAYS | PROCESS_CUSTOM_NAV); + for (device_image_interface& image : image_interface_enumerator(mui.machine().root_device())) { - if (image.filename()) + if (image.loaded_through_softlist()) { - m_list = strensure(image.software_list_name()); + m_list = image.software_list_name(); m_short = image.software_entry()->shortname(); m_long = image.software_entry()->longname(); m_parent = image.software_entry()->parentname(); + break; } } + std::vector<std::string> lua_list; - if (mame_machine_manager::instance()->lua()->call_plugin("data_list", driver ? driver->name : "", lua_list)) + if (mame_machine_manager::instance()->lua()->call_plugin("data_list", system ? system->driver->name : "", lua_list)) { int count = 0; + m_items_list.reserve(lua_list.size()); for (std::string& item : lua_list) { std::string version; mame_machine_manager::instance()->lua()->call_plugin("data_version", count, version); - m_items_list.emplace_back(item.c_str(), count, std::move(version)); + m_items_list.emplace_back(std::move(item), count, std::move(version)); count++; } } @@ -64,29 +75,38 @@ menu_dats_view::menu_dats_view(mame_ui_manager &mui, render_container &container // ctor //------------------------------------------------- -menu_dats_view::menu_dats_view(mame_ui_manager &mui, render_container &container, const ui_software_info *swinfo, const game_driver *driver) - : menu(mui, container) - , m_actual(0) - , m_driver((driver == nullptr) ? &mui.machine().system() : driver) - , m_swinfo(swinfo) - , m_list(swinfo->listname) - , m_short(swinfo->shortname) - , m_long(swinfo->longname) - , m_parent(swinfo->parentname) +menu_dats_view::menu_dats_view(mame_ui_manager &mui, render_container &container, const ui_software_info &swinfo) + : menu_textbox(mui, container) + , m_system(nullptr) + , m_swinfo(&swinfo) , m_issoft(true) - + , m_current_tab(0) + , m_list(swinfo.listname) + , m_short(swinfo.shortname) + , m_long(swinfo.longname) + , m_parent(swinfo.parentname) + , m_tab_line(1.0F, 0.0F) + , m_clicked_tab(-1) { - if (swinfo != nullptr && !swinfo->usage.empty()) - m_items_list.emplace_back(_("Software Usage"), 0, ""); + set_process_flags(PROCESS_LR_ALWAYS | PROCESS_CUSTOM_NAV); + std::vector<std::string> lua_list; - if(mame_machine_manager::instance()->lua()->call_plugin("data_list", std::string(m_short).append(1, ',').append(m_list).c_str(), lua_list)) + bool const retrieved(mame_machine_manager::instance()->lua()->call_plugin("data_list", std::string(m_short).append(1, ',').append(m_list).c_str(), lua_list)); + + if (!swinfo.infotext.empty() || retrieved) + m_items_list.reserve((!swinfo.infotext.empty() ? 1 : 0) + (retrieved ? lua_list.size() : 0)); + + if (!swinfo.infotext.empty()) + m_items_list.emplace_back(_("Software List Info"), -1, ""); + + if (retrieved) { - int count = 1; - for(std::string &item : lua_list) + int count = 0; + for (std::string &item : lua_list) { std::string version; - mame_machine_manager::instance()->lua()->call_plugin("data_version", count - 1, version); - m_items_list.emplace_back(item.c_str(), count, std::move(version)); + mame_machine_manager::instance()->lua()->call_plugin("data_version", count, version); + m_items_list.emplace_back(std::move(item), count, std::move(version)); count++; } } @@ -101,327 +121,319 @@ menu_dats_view::~menu_dats_view() } //------------------------------------------------- -// handle +// add text to layout //------------------------------------------------- -void menu_dats_view::handle() +void menu_dats_view::add_info_text(text_layout &layout, std::string_view text, rgb_t color, float size) { - const event *menu_event = process(FLAG_UI_DATS); - if (menu_event != nullptr) + char justify = 'l'; // left justify by default + if ((text.length() > 3) && (text[0] == '#') && (text[1] == 'j')) { - if (menu_event->iptkey == IPT_UI_LEFT && m_actual > 0) + auto const eol = text.find('\n'); + if ((std::string_view::npos != eol) && (2 < eol)) { - m_actual--; - reset(reset_options::SELECT_FIRST); + justify = text[2]; + text.remove_prefix(eol + 1); } + } - if (menu_event->iptkey == IPT_UI_RIGHT && m_actual < m_items_list.size() - 1) + if ('2' == justify) + { + while (!text.empty()) { - m_actual++; - reset(reset_options::SELECT_FIRST); + // pop a line from the front + auto const eol = text.find('\n'); + std::string_view const line = (std::string_view::npos != eol) + ? text.substr(0, eol + 1) + : text; + text.remove_prefix(line.length()); + + // split on the first tab + auto const split = line.find('\t'); + if (std::string_view::npos != split) + { + layout.add_text(line.substr(0, split), text_layout::text_justify::LEFT, color, rgb_t::transparent(), size); + layout.add_text(" ", text_layout::text_justify::LEFT, color, rgb_t::transparent(), size); + layout.add_text(line.substr(split + 1), text_layout::text_justify::RIGHT, color, rgb_t::transparent(), size); + } + else + { + layout.add_text(line, text_layout::text_justify::LEFT, color, rgb_t::transparent(), size); + } } } -} - -//------------------------------------------------- -// populate -//------------------------------------------------- - -void menu_dats_view::populate(float &customtop, float &custombottom) -{ - bool paused = machine().paused(); - if (!paused) - machine().pause(); - - (m_issoft == true) ? get_data_sw() : get_data(); - - item_append(menu_item_type::SEPARATOR, (FLAG_UI_DATS | FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW)); - customtop = 2.0f * ui().get_line_height() + 4.0f * ui().box_tb_border(); - custombottom = ui().get_line_height() + 3.0f * ui().box_tb_border(); + else + { + // use the same alignment for all the text + auto const j = + ('c' == justify) ? text_layout::text_justify::CENTER : + ('r' == justify) ? text_layout::text_justify::RIGHT : + text_layout::text_justify::LEFT; + layout.add_text(text, j, color, rgb_t::transparent(), size); + } - if (!paused) - machine().resume(); } //------------------------------------------------- -// draw - draw dats menu +// handle //------------------------------------------------- -void menu_dats_view::draw(uint32_t flags) +bool menu_dats_view::handle(event const *ev) { - float const line_height = ui().get_line_height(); - float const ud_arrow_width = line_height * machine().render().ui_aspect(); - float const gutter_width = 0.52f * line_height * machine().render().ui_aspect(); - float const visible_width = 1.0f - (2.0f * ui().box_lr_border()); - float const visible_left = (1.0f - visible_width) * 0.5f; - float const extra_height = 2.0f * line_height; - float const visible_extra_menu_height = get_customtop() + get_custombottom() + extra_height; - int const visible_items = item_count() - 2; - - // determine effective positions taking into account the hilighting arrows - float const effective_width = visible_width - 2.0f * gutter_width; - float const effective_left = visible_left + gutter_width; - - draw_background(); - map_mouse(); - - // 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; - - // compute top/left of inner menu area by centering, if the menu is at the bottom of the extra, adjust - float const visible_top = ((1.0f - (visible_main_menu_height + visible_extra_menu_height)) * 0.5f) + get_customtop(); - - // compute left box size - float x1 = visible_left; - float y1 = visible_top - ui().box_tb_border(); - float x2 = x1 + visible_width; - float y2 = visible_top + visible_main_menu_height + ui().box_tb_border() + extra_height; - float line = visible_top + float(m_visible_lines) * line_height; - - ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color()); - - m_visible_lines = (std::min)(visible_items, m_visible_lines); - top_line = (std::max)(0, top_line); - if (top_line + m_visible_lines >= visible_items) - top_line = visible_items - m_visible_lines; - - clear_hover(); - int const n_loop = (std::min)(visible_items, m_visible_lines); - for (int linenum = 0; linenum < n_loop; linenum++) + if (ev) { - float const line_y = visible_top + (float)linenum * line_height; - int const itemnum = top_line + linenum; - menu_item const &pitem = item(itemnum); - char const *const itemtext = pitem.text.c_str(); - float const line_x0 = x1 + 0.5f * UI_LINE_WIDTH; - float const line_y0 = line_y; - float const line_x1 = x2 - 0.5f * UI_LINE_WIDTH; - float const line_y1 = line_y + line_height; - - rgb_t fgcolor = ui().colors().text_color(); - rgb_t bgcolor = ui().colors().text_bg_color(); - - if (!linenum && top_line) + // don't bother with parent event handling if we need to redraw anyway + switch (ev->iptkey) { - // if we're on the top line, display the up arrow - if (mouse_in_rect(line_x0, line_y0, line_x1, line_y1)) + case IPT_UI_LEFT: + if (m_current_tab > 0) { - fgcolor = ui().colors().mouseover_color(); - bgcolor = ui().colors().mouseover_bg_color(); - highlight(line_x0, line_y0, line_x1, line_y1, bgcolor); - set_hover(HOVER_ARROW_UP); + m_current_tab--; + m_tab_line = std::make_pair(1.0F, 0.0F); + m_clicked_tab = -1; + reset_layout(); + return true; } - 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); - } - else if ((linenum == m_visible_lines - 1) && (itemnum != visible_items - 1)) - { - // if we're on the bottom line, display the down arrow - if (mouse_in_rect(line_x0, line_y0, line_x1, line_y1)) + break; + + case IPT_UI_RIGHT: + if ((m_current_tab + 1) < m_items_list.size()) { - fgcolor = ui().colors().mouseover_color(); - bgcolor = ui().colors().mouseover_bg_color(); - highlight(line_x0, line_y0, line_x1, line_y1, bgcolor); - set_hover(HOVER_ARROW_DOWN); + m_current_tab++; + m_tab_line = std::make_pair(1.0F, 0.0F); + m_clicked_tab = -1; + reset_layout(); + return true; } - 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); - } - else if (pitem.subtext.empty()) - { - // draw dats text - ui().draw_text_full( - container(), itemtext, - effective_left, line_y, effective_width, - ui::text_layout::LEFT, ui::text_layout::NEVER, - mame_ui_manager::NORMAL, fgcolor, bgcolor, - nullptr, nullptr); + break; } } - for (size_t count = visible_items; count < item_count(); count++) - { - menu_item const &pitem = item(count); - char const *const itemtext = pitem.text.c_str(); - float const line_x0 = x1 + 0.5f * UI_LINE_WIDTH; - float const line_y0 = line; - float const line_x1 = x2 - 0.5f * UI_LINE_WIDTH; - float const line_y1 = line + line_height; - rgb_t const fgcolor = ui().colors().selected_color(); - rgb_t const bgcolor = ui().colors().selected_bg_color(); - - if (mouse_in_rect(line_x0, line_y0, line_x1, line_y1) && is_selectable(pitem)) - set_hover(count); - - 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, - UI_LINE_WIDTH, ui().colors().text_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); - } - else - { - highlight(line_x0, line_y0, line_x1, line_y1, bgcolor); - 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); - } - line += line_height; - } + return menu_textbox::handle(ev); +} - // 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); +//------------------------------------------------- +// populate +//------------------------------------------------- - // 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 != visible_items); +void menu_dats_view::populate() +{ +} + +//------------------------------------------------- +// recompute metrics +//------------------------------------------------- + +void menu_dats_view::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + menu_textbox::recompute_metrics(width, height, aspect); + + m_tab_line = std::make_pair(1.0F, 0.0F); + m_clicked_tab = -1; + + set_custom_space(2.0F * line_height() + 4.0F * tb_border(), line_height() + 3.0F * tb_border()); } //------------------------------------------------- // perform our special rendering //------------------------------------------------- -void menu_dats_view::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_dats_view::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { - float maxwidth = origx2 - origx1; - float width; - std::string driver = (m_issoft == true) ? m_swinfo->longname : m_driver->type.fullname(); + float maxwidth; + std::string_view const driver = m_issoft ? m_swinfo->longname : m_system->description; - ui().draw_text_full(container(), driver.c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::TRUNCATE, - mame_ui_manager::NONE, rgb_t::white(), rgb_t::black(), &width, nullptr); - width += 2 * ui().box_lr_border(); - maxwidth = std::max(maxwidth, width); + maxwidth = std::max(origx2 - origx1, get_string_width(driver) + (2.0F * lr_border())); // compute our bounds - float x1 = 0.5f - 0.5f * maxwidth; + float x1 = 0.5F - (0.5F * maxwidth); float x2 = x1 + maxwidth; float y1 = origy1 - top; - float y2 = origy1 - 2.0f * ui().box_tb_border() - ui().get_line_height(); + float y2 = y1 + (2.0F * tb_border()) + line_height(); // draw a box ui().draw_outlined_box(container(), x1, y1, x2, y2, UI_GREEN_COLOR); - // take off the borders - x1 += ui().box_lr_border(); - x2 -= ui().box_lr_border(); - y1 += ui().box_tb_border(); + draw_text_normal( + driver, + x1 + lr_border(), y1 + tb_border(), x2 - x1 - (2.0F * lr_border()), + text_layout::text_justify::CENTER, ui::text_layout::word_wrapping::NEVER, + ui().colors().text_color()); - ui().draw_text_full(container(), driver.c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER, - mame_ui_manager::NORMAL, ui().colors().text_color(), ui().colors().text_bg_color(), nullptr, nullptr); + // draw a box + ui().draw_outlined_box(container(), x1, origy1 - line_height() - tb_border(), x2, origy1, ui().colors().background_color()); - maxwidth = 0; - for (auto & elem : m_items_list) + // calculate geometry of tab line + if (m_tab_line.first >= m_tab_line.second) { - ui().draw_text_full(container(), elem.label.c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::NEVER, - mame_ui_manager::NONE, rgb_t::white(), rgb_t::black(), &width, nullptr); - maxwidth += width; - } - - float space = (1.0f - maxwidth) / (m_items_list.size() * 2); + m_tab_line = std::make_pair(origy1 - line_height(), origy1); - // compute our bounds - x1 -= ui().box_lr_border(); - x2 += ui().box_lr_border(); - y1 = y2 + ui().box_tb_border(); - y2 += ui().get_line_height() + 2.0f * ui().box_tb_border(); + // FIXME: deal with overflow when there are a lot of tabs + float total(0.0F); + for (auto const &elem : m_items_list) + total += get_string_width(elem.label); + float const space((1.0F - total) / (m_items_list.size() * 2.0F)); - // draw a box - ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color()); - - // take off the borders - y1 += ui().box_tb_border(); + float left(x1 + (space * 0.5F)); + for (auto &elem : m_items_list) + { + float const width(get_string_width(elem.label)); + elem.bounds = std::make_pair(left, left + width + space); + left += width + (space * 2.0F); + } + } // draw the text within it - int x = 0; - for (auto & elem : m_items_list) + for (int i = 0; m_items_list.size() > i; ++i) { - x1 += space; - rgb_t fcolor = (m_actual == x) ? rgb_t(0xff, 0xff, 0xff, 0x00) : ui().colors().text_color(); - rgb_t bcolor = (m_actual == x) ? rgb_t(0xff, 0xff, 0xff, 0xff) : ui().colors().text_bg_color(); - ui().draw_text_full(container(), elem.label.c_str(), x1, y1, 1.0f, ui::text_layout::LEFT, ui::text_layout::NEVER, mame_ui_manager::NONE, fcolor, bcolor, &width, nullptr); - - if (bcolor != ui().colors().text_bg_color()) - ui().draw_textured_box(container(), x1 - (space / 2), y1, x1 + width + (space / 2), y2, bcolor, rgb_t(255, 43, 43, 43), - hilight_main_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); - - ui().draw_text_full(container(), elem.label.c_str(), x1, y1, 1.0f, ui::text_layout::LEFT, ui::text_layout::NEVER, mame_ui_manager::NORMAL, fcolor, bcolor, &width, nullptr); - x1 += width + space; - ++x; - } - - // bottom - std::string revision; - revision.assign(_("Revision: ")).append(m_items_list[m_actual].revision); - ui().draw_text_full(container(), revision.c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::TRUNCATE, mame_ui_manager::NONE, rgb_t::white(), rgb_t::black(), &width, nullptr); - width += 2 * ui().box_lr_border(); - maxwidth = std::max(origx2 - origx1, width); + auto &elem(m_items_list[i]); - // compute our bounds - x1 = 0.5f - 0.5f * maxwidth; - x2 = x1 + maxwidth; - y1 = origy2 + ui().box_tb_border(); - y2 = origy2 + bottom; - - // draw a box - ui().draw_outlined_box(container(), x1, y1, x2, y2, UI_GREEN_COLOR); + rgb_t fgcolor; + rgb_t bgcolor; + if (i == m_current_tab) + { + fgcolor = rgb_t(0xff, 0xff, 0xff, 0x00); + bgcolor = rgb_t(0xff, 0xff, 0xff, 0xff); + ui().draw_textured_box( + container(), + elem.bounds.first, m_tab_line.first, elem.bounds.second, m_tab_line.second, + bgcolor, rgb_t(255, 43, 43, 43), + hilight_main_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1)); + } + else + { + bool const hovered(pointer_in_rect(elem.bounds.first, m_tab_line.first, elem.bounds.second, m_tab_line.second)); + if ((i == m_clicked_tab) && hovered) + { + fgcolor = ui().colors().selected_color(); + bgcolor = ui().colors().selected_bg_color(); + highlight(elem.bounds.first, m_tab_line.first, elem.bounds.second, m_tab_line.second, bgcolor); + } + else if ((i == m_clicked_tab) || (hovered && pointer_idle())) + { + fgcolor = ui().colors().mouseover_color(); + bgcolor = ui().colors().mouseover_bg_color(); + highlight(elem.bounds.first, m_tab_line.first, elem.bounds.second, m_tab_line.second, bgcolor); + } + else + { + fgcolor = ui().colors().text_color(); + bgcolor = ui().colors().text_bg_color(); + } + } - // take off the borders - x1 += ui().box_lr_border(); - x2 -= ui().box_lr_border(); - y1 += ui().box_tb_border(); + ui().draw_text_full( + container(), + elem.label, + elem.bounds.first, m_tab_line.first, elem.bounds.second - elem.bounds.first, + text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, + mame_ui_manager::NORMAL, fgcolor, bgcolor, + nullptr, nullptr, + line_height()); + } - // draw the text within it - ui().draw_text_full(container(), revision.c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::TRUNCATE, - mame_ui_manager::NORMAL, ui().colors().text_color(), ui().colors().text_bg_color(), nullptr, nullptr); + // bottom + if (!m_items_list.empty()) + { + std::string const revision(util::string_format(_("Revision: %1$s"), m_items_list[m_current_tab].revision)); + float const width(get_text_width( + revision, + 0.0F, 0.0F, 1.0F, + text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE)); + maxwidth = std::max(origx2 - origx1, width + (2.0F * lr_border())); + + // compute our bounds + x1 = 0.5F - (0.5F * maxwidth); + x2 = x1 + maxwidth; + y1 = origy2 + tb_border(); + y2 = origy2 + bottom; + + // draw a box + ui().draw_outlined_box(container(), x1, y1, x2, y2, UI_GREEN_COLOR); + + // draw the text within it + draw_text_normal( + revision, + x1 + lr_border(), y1 + tb_border(), x2 - x1 - (2.0F * lr_border()), + text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, + ui().colors().text_color()); + } } //------------------------------------------------- -// load data from DATs +// custom pointer handling //------------------------------------------------- -void menu_dats_view::get_data() +std::tuple<int, bool, bool> menu_dats_view::custom_pointer_updated(bool changed, ui_event const &uievt) { - std::vector<int> xstart, xend; - std::string buffer; - mame_machine_manager::instance()->lua()->call_plugin("data", m_items_list[m_actual].option, buffer); - - float const line_height = ui().get_line_height(); - float const gutter_width = 0.52f * line_height * machine().render().ui_aspect(); - float const visible_width = 1.0f - (2.0f * ui().box_lr_border()); - float const effective_width = visible_width - 2.0f * gutter_width; - - auto lines = ui().wrap_text(container(), buffer.c_str(), 0.0f, 0.0f, effective_width, xstart, xend); - for (int x = 0; x < lines; ++x) + if (0 <= m_clicked_tab) + { + if ((ui_event::type::POINTER_ABORT != uievt.event_type) && uievt.pointer_released & 0x01) + { + // primary button released - take action if still on the tab + bool const hit(pointer_in_rect( + m_items_list[m_clicked_tab].bounds.first, m_tab_line.first, + m_items_list[m_clicked_tab].bounds.second, m_tab_line.second)); + if (hit && (m_current_tab != m_clicked_tab)) + { + m_current_tab = m_clicked_tab; + m_tab_line = std::make_pair(1.0F, 0.0F); + reset_layout(); + } + m_clicked_tab = -1; + return std::make_tuple(hit ? IPT_CUSTOM : IPT_INVALID, false, true); + } + else if ((ui_event::type::POINTER_ABORT == uievt.event_type) || (uievt.pointer_buttons & ~u32(0x01))) + { + // treat pressing another button as cancellation + m_clicked_tab = -1; + return std::make_tuple(IPT_INVALID, false, true); + } + return std::make_tuple(IPT_INVALID, true, false); + } + else if (pointer_idle() && (uievt.pointer_pressed & 0x01) && !(uievt.pointer_buttons & ~u32(0x01))) { - std::string tempbuf(buffer.substr(xstart[x], xend[x] - xstart[x])); - if ((tempbuf[0] != '#') || x) - item_append(tempbuf, "", (FLAG_UI_DATS | FLAG_DISABLE), (void *)(uintptr_t)(x + 1)); + // primary click - see if it's over a tab + auto const [x, y] = pointer_location(); + if ((y >= m_tab_line.first) && (y < m_tab_line.second)) + { + for (int i = 0; m_items_list.size() > i; ++i) + { + if ((x >= m_items_list[i].bounds.first) && (x < m_items_list[i].bounds.second)) + { + m_clicked_tab = i; + return std::make_tuple(IPT_INVALID, true, true); + } + } + } } + + // let the base class have a look + return menu_textbox::custom_pointer_updated(changed, uievt); } -void menu_dats_view::get_data_sw() -{ - std::vector<int> xstart; - std::vector<int> xend; - std::string buffer; - if (m_items_list[m_actual].option == 0) - buffer = m_swinfo->usage; - else - mame_machine_manager::instance()->lua()->call_plugin("data", m_items_list[m_actual].option - 1, buffer); +//------------------------------------------------- +// populate selected DAT text +//------------------------------------------------- - auto lines = ui().wrap_text(container(), buffer.c_str(), 0.0f, 0.0f, 1.0f - (4.0f * ui().box_lr_border()), xstart, xend); - for (int x = 0; x < lines; ++x) +void menu_dats_view::populate_text(std::optional<text_layout> &layout, float &width, int &lines) +{ + if (!layout || (layout->width() != width)) { - std::string tempbuf(buffer.substr(xstart[x], xend[x] - xstart[x])); - item_append(tempbuf, "", (FLAG_UI_DATS | FLAG_DISABLE), (void *)(uintptr_t)(x + 1)); + m_tab_line = std::make_pair(1.0F, 0.0F); + m_clicked_tab = -1; + + std::string buffer; + if (!m_items_list.empty()) + { + if (0 > m_items_list[m_current_tab].option) + buffer = m_swinfo->infotext; + else + mame_machine_manager::instance()->lua()->call_plugin("data", m_items_list[m_current_tab].option, buffer); + } + layout.emplace(create_layout(width)); + add_info_text(*layout, buffer, ui().colors().text_color()); + lines = std::numeric_limits<int>::max(); } } diff --git a/src/frontend/mame/ui/datmenu.h b/src/frontend/mame/ui/datmenu.h index 3dc60b16c93..a55f659a84d 100644 --- a/src/frontend/mame/ui/datmenu.h +++ b/src/frontend/mame/ui/datmenu.h @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:Maurizio Petrarota +// copyright-holders:Maurizio Petrarota, Vas Crabb /*************************************************************************** ui/datmenu.h @@ -14,13 +14,18 @@ #pragma once -#include "ui/menu.h" +#include "ui/text.h" +#include "ui/textbox.h" +#include <optional> #include <string> +#include <utility> #include <vector> struct ui_software_info; +struct ui_system_info; + namespace ui { @@ -28,40 +33,48 @@ namespace ui { // class dats menu //------------------------------------------------- -class menu_dats_view : public menu +class menu_dats_view : public menu_textbox { public: - menu_dats_view(mame_ui_manager &mui, render_container &container, const ui_software_info *swinfo, const game_driver *driver = nullptr); - menu_dats_view(mame_ui_manager &mui, render_container &container, const game_driver *driver = nullptr); + menu_dats_view(mame_ui_manager &mui, render_container &container, const ui_software_info &swinfo); + menu_dats_view(mame_ui_manager &mui, render_container &container, const ui_system_info *system = nullptr); virtual ~menu_dats_view() override; -protected: - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + static void add_info_text(text_layout &layout, std::string_view text, rgb_t color, float size = 1.0f); -private: - // draw dats menu - virtual void draw(uint32_t flags) override; +protected: + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) override; + virtual std::tuple<int, bool, bool> custom_pointer_updated(bool changed, ui_event const &uievt) override; - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate_text(std::optional<text_layout> &layout, float &width, int &lines) override; - int m_actual; - const game_driver *m_driver; - const ui_software_info *m_swinfo; - std::string m_list, m_short, m_long, m_parent; - void get_data(); - void get_data_sw(); - bool m_issoft; +private: struct list_items { - list_items(std::string l, int i, std::string rev) { label = l; option = i; revision = rev; } + list_items(std::string &&l, int i, std::string &&rev) : label(std::move(l)), revision(std::move(rev)), option(i), bounds(1.0F, 0.0F) { } + std::string label; - int option; std::string revision; + int option; + + std::pair<float, float> bounds; }; + + virtual void populate() override; + virtual bool handle(event const *ev) override; + + ui_system_info const *const m_system; + ui_software_info const *const m_swinfo; + bool const m_issoft; + int m_current_tab; + std::string m_list, m_short, m_long, m_parent; std::vector<list_items> m_items_list; + + std::pair<float, float> m_tab_line; + int m_clicked_tab; }; } // namespace ui -#endif /* MAME_FRONTEND_UI_DATMENU_H */ +#endif // MAME_FRONTEND_UI_DATMENU_H diff --git a/src/frontend/mame/ui/devctrl.h b/src/frontend/mame/ui/devctrl.h index ecdbeedf736..ad89f726172 100644 --- a/src/frontend/mame/ui/devctrl.h +++ b/src/frontend/mame/ui/devctrl.h @@ -17,15 +17,15 @@ submenu listing available devices of the same kind. ***************************************************************************/ - -#pragma once - #ifndef MAME_FRONTEND_UI_DEVCTRL_H #define MAME_FRONTEND_UI_DEVCTRL_H +#pragma once + #include "ui/menu.h" namespace ui { + template<class DeviceType> class menu_device_control : public menu { @@ -37,14 +37,14 @@ protected: int count() { return m_count; } int current_index(); - void previous(); - void next(); + bool previous(); + bool next(); std::string current_display_name(); uint32_t current_display_flags(); private: - // device iterator - typedef device_type_iterator<DeviceType> iterator; + // device enumerator + typedef device_type_enumerator<DeviceType> enumerator; DeviceType * m_device; int m_count; @@ -59,7 +59,7 @@ template<class DeviceType> menu_device_control<DeviceType>::menu_device_control(mame_ui_manager &mui, render_container &container, DeviceType *device) : menu(mui, container) { - iterator iter(mui.machine().root_device()); + enumerator iter(mui.machine().root_device()); m_count = iter.count(); m_device = device ? device : iter.first(); } @@ -72,7 +72,7 @@ menu_device_control<DeviceType>::menu_device_control(mame_ui_manager &mui, rende template<class DeviceType> int menu_device_control<DeviceType>::current_index() { - iterator iter(machine().root_device()); + enumerator iter(machine().root_device()); return iter.indexof(*m_device); } @@ -82,19 +82,21 @@ int menu_device_control<DeviceType>::current_index() //------------------------------------------------- template<class DeviceType> -void menu_device_control<DeviceType>::previous() +bool menu_device_control<DeviceType>::previous() { - // left arrow - rotate left through cassette devices - if (m_device != nullptr) + // left arrow - rotate left through devices + if (m_device && (1 < m_count)) { - iterator iter(machine().root_device()); + enumerator iter(machine().root_device()); int index = iter.indexof(*m_device); if (index > 0) index--; else index = m_count - 1; m_device = iter.byindex(index); + reset(reset_options::REMEMBER_POSITION); } + return false; // triggers an item reset on changes anyway } @@ -103,19 +105,21 @@ void menu_device_control<DeviceType>::previous() //------------------------------------------------- template<class DeviceType> -void menu_device_control<DeviceType>::next() +bool menu_device_control<DeviceType>::next() { // right arrow - rotate right through cassette devices - if (m_device != nullptr) + if (m_device && (1 < m_count)) { - iterator iter(machine().root_device()); + enumerator iter(machine().root_device()); int index = iter.indexof(*m_device); if (index < m_count - 1) index++; else index = 0; m_device = iter.byindex(index); + reset(reset_options::REMEMBER_POSITION); } + return false; // triggers an item reset on changes anyway } @@ -129,7 +133,7 @@ std::string menu_device_control<DeviceType>::current_display_name() std::string display_name; display_name.assign(current_device()->name()); if (count() > 1) - display_name.append(string_format("%d", current_index() + 1)); + display_name.append(string_format(" %d", current_index() + 1)); return display_name; } @@ -154,4 +158,4 @@ uint32_t menu_device_control<DeviceType>::current_display_flags() } // namespace ui -#endif /* MAME_FRONTEND_UI_DEVCTRL_H */ +#endif // MAME_FRONTEND_UI_DEVCTRL_H diff --git a/src/frontend/mame/ui/devopt.cpp b/src/frontend/mame/ui/devopt.cpp index 8dd9b9c9a9e..fa8bb4fadce 100644 --- a/src/frontend/mame/ui/devopt.cpp +++ b/src/frontend/mame/ui/devopt.cpp @@ -9,281 +9,365 @@ *********************************************************************/ #include "emu.h" -#include "ui/ui.h" #include "ui/devopt.h" + +#include "ui/ui.h" #include "romload.h" +#include "screen.h" + +#include "util/unicode.h" + +#include <locale> +#include <sstream> namespace ui { + /*------------------------------------------------- device_config - handle the game information menu -------------------------------------------------*/ -menu_device_config::menu_device_config(mame_ui_manager &mui, render_container &container, device_slot_interface *slot, device_slot_interface::slot_option const *option) : menu(mui, container) +menu_device_config::menu_device_config( + mame_ui_manager &mui, + render_container &container, + device_slot_interface *slot, + device_slot_interface::slot_option const *option) + : menu_textbox(mui, container) + , m_option(option) + , m_mounted(machine().root_device().subdevice(slot->device().subtag(option->name())) != nullptr) +{ + set_process_flags(PROCESS_CUSTOM_NAV); +} + +menu_device_config::~menu_device_config() { - m_option = option; - m_owner = slot; - m_mounted = slot->device().subdevice(option->name()) != nullptr; } -void menu_device_config::populate(float &customtop, float &custombottom) +void menu_device_config::populate_text(std::optional<text_layout> &layout, float &width, int &lines) { - machine_config &mconfig(const_cast<machine_config &>(machine().config())); - machine_config::token const tok(mconfig.begin_configuration(mconfig.root_device())); - device_t *const dev = mconfig.device_add(m_option->name(), m_option->devtype(), 0); - for (device_t &d : device_iterator(*dev)) - if (!d.configured()) - d.config_complete(); - - std::ostringstream str; - util::stream_format( - str, - m_mounted - ? _("[This option is currently mounted in the running system]\n\nOption: %1$s\nDevice: %2$s\n\nThe selected option enables the following items:\n") - : _("[This option is NOT currently mounted in the running system]\n\nOption: %1$s\nDevice: %2$s\n\nIf you select this option, the following items will be enabled:\n"), - m_option->name(), - dev->name()); - - // loop over all CPUs - execute_interface_iterator execiter(*dev); - if (execiter.count() > 0) + if (!layout || (layout->width() != width)) { - str << _("* CPU:\n"); - std::unordered_set<std::string> exectags; - for (device_execute_interface &exec : execiter) + rgb_t const color = ui().colors().text_color(); + layout.emplace(create_layout(width)); + + machine_config &mconfig(const_cast<machine_config &>(machine().config())); + machine_config::token const tok(mconfig.begin_configuration(mconfig.root_device())); + device_t *const dev = mconfig.device_add(m_option->name(), m_option->devtype(), 0); + for (device_t &d : device_enumerator(*dev)) + if (!d.configured()) + d.config_complete(); + + // get decimal separator + std::string point; { - if (!exectags.insert(exec.device().tag()).second) - continue; + wchar_t const s(std::use_facet<std::numpunct<wchar_t> >(std::locale()).decimal_point()); + point = utf8_from_wstring(std::wstring_view(&s, 1)); + } - // get cpu specific clock that takes internal multiplier/dividers into account - int clock = exec.device().clock(); + layout->add_text( + util::string_format( + m_mounted + ? _("[This option is currently mounted in the running system]\n\nOption: %1$s\nDevice: %2$s\n\nThe selected option enables the following items:\n") + : _("[This option is NOT currently mounted in the running system]\n\nOption: %1$s\nDevice: %2$s\n\nIf you select this option, the following items will be enabled:\n"), + m_option->name(), + dev->name()), + color); - // count how many identical CPUs we have - int count = 1; - const char *name = exec.device().name(); - for (device_execute_interface &scan : execiter) + // loop over all CPUs + execute_interface_enumerator execiter(*dev); + if (execiter.count() > 0) + { + layout->add_text(_("* CPU:\n"), color); + std::unordered_set<std::string> exectags; + for (device_execute_interface &exec : execiter) { - if (exec.device().type() == scan.device().type() && strcmp(name, scan.device().name()) == 0 && exec.device().clock() == scan.device().clock()) - if (exectags.insert(scan.device().tag()).second) - count++; - } + if (!exectags.insert(exec.device().tag()).second) + continue; - // if more than one, prepend a #x in front of the CPU name and display clock in kHz or MHz - util::stream_format( - str, - (count > 1) - ? ((clock >= 1000000) ? _(" %1$d\xC3\x97%2$s %3$d.%4$06d\xC2\xA0MHz\n") : _(" %1$d\xC3\x97%2$s %5$d.%6$03d\xC2\xA0kHz\n")) - : ((clock >= 1000000) ? _(" %2$s %3$d.%4$06d\xC2\xA0MHz\n") : _(" %2$s %5$d.%6$03d\xC2\xA0kHz\n")), - count, - name, - clock / 1000000, clock % 1000000, - clock / 1000, clock % 1000); + // get cpu specific clock that takes internal multiplier/dividers into account + u32 clock = exec.device().clock(); + + // count how many identical CPUs we have + int count = 1; + const char *name = exec.device().name(); + for (device_execute_interface &scan : execiter) + { + if (exec.device().type() == scan.device().type() && strcmp(name, scan.device().name()) == 0 && exec.device().clock() == scan.device().clock()) + if (exectags.insert(scan.device().tag()).second) + count++; + } + + std::string hz(std::to_string(clock)); + int d = (clock >= 1'000'000'000) ? 9 : (clock >= 1'000'000) ? 6 : (clock >= 1000) ? 3 : 0; + if (d > 0) + { + size_t dpos = hz.length() - d; + hz.insert(dpos, point); + size_t last = hz.find_last_not_of('0'); + hz = hz.substr(0, last + (last != dpos ? 1 : 0)); + } + + // if more than one, prepend a #x in front of the CPU name and display clock + layout->add_text( + util::string_format( + (count > 1) + ? ((clock != 0) ? u8" %1$d×%2$s %3$s\u00a0%4$s\n" : u8" %1$d×%2$s\n") + : ((clock != 0) ? u8" %2$s %3$s\u00a0%4$s\n" : " %2$s\n"), + count, name, hz, + (d == 9) ? _("GHz") : (d == 6) ? _("MHz") : (d == 3) ? _("kHz") : _("Hz")), + color); + } } - } - // display screen information - screen_device_iterator scriter(*dev); - if (scriter.count() > 0) - { - str << _("* Video:\n"); - for (screen_device &screen : scriter) + // display screen information + screen_device_enumerator scriter(*dev); + if (scriter.count() > 0) { - if (screen.screen_type() == SCREEN_TYPE_VECTOR) + layout->add_text(_("* Video:\n"), color); + for (screen_device &screen : scriter) { - util::stream_format(str, _(" Screen '%1$s': Vector\n"), screen.tag()); + if (screen.screen_type() == SCREEN_TYPE_VECTOR) + { + layout->add_text(util::string_format(_(" Screen '%1$s': Vector\n"), screen.tag()), color); + } + else + { + const u32 rate = u32(screen.frame_period().as_hz() * 1'000'000 + 0.5); + const bool valid = rate >= 1'000'000; + std::string hz(valid ? std::to_string(rate) : "?"); + if (valid) + { + size_t dpos = hz.length() - 6; + hz.insert(dpos, point); + size_t last = hz.find_last_not_of('0'); + hz = hz.substr(0, last + (last != dpos ? 1 : 0)); + } + + const rectangle &visarea = screen.visible_area(); + layout->add_text( + util::string_format( + (screen.orientation() & ORIENTATION_SWAP_XY) + ? _(u8" Screen '%1$s': %2$d × %3$d (V) %4$s\u00a0Hz\n") + : _(u8" Screen '%1$s': %2$d × %3$d (H) %4$s\u00a0Hz\n"), + screen.tag(), + visarea.width(), + visarea.height(), + hz), + color); + } } - else + } + + // loop over all sound chips + sound_interface_enumerator snditer(*dev); + if (snditer.count() > 0) + { + layout->add_text(_("* Sound:\n"), color); + std::unordered_set<std::string> soundtags; + for (device_sound_interface &sound : snditer) { - const rectangle &visarea = screen.visible_area(); - - util::stream_format( - str, - (screen.orientation() & ORIENTATION_SWAP_XY) - ? _(" Screen '%1$s': %2$d \xC3\x97 %3$d (V) %4$f\xC2\xA0Hz\n") - : _(" Screen '%1$s': %2$d \xC3\x97 %3$d (H) %4$f\xC2\xA0Hz\n"), - screen.tag(), - visarea.width(), - visarea.height(), - screen.frame_period().as_hz()); + if (!soundtags.insert(sound.device().tag()).second) + continue; + + // count how many identical sound chips we have + int count = 1; + for (device_sound_interface &scan : snditer) + { + if (sound.device().type() == scan.device().type() && sound.device().clock() == scan.device().clock()) + if (soundtags.insert(scan.device().tag()).second) + count++; + } + + const u32 clock = sound.device().clock(); + std::string hz(std::to_string(clock)); + int d = (clock >= 1'000'000'000) ? 9 : (clock >= 1'000'000) ? 6 : (clock >= 1000) ? 3 : 0; + if (d > 0) + { + size_t dpos = hz.length() - d; + hz.insert(dpos, point); + size_t last = hz.find_last_not_of('0'); + hz = hz.substr(0, last + (last != dpos ? 1 : 0)); + } + + // if more than one, prepend a #x in front of the name and display clock + layout->add_text( + util::string_format( + (count > 1) + ? ((clock != 0) ? u8" %1$d×%2$s %3$s\u00a0%4$s\n" : u8" %1$d×%2$s\n") + : ((clock != 0) ? u8" %2$s %3$s\u00a0%4$s\n" : " %2$s\n"), + count, sound.device().name(), hz, + (d == 9) ? _("GHz") : (d == 6) ? _("MHz") : (d == 3) ? _("kHz") : _("Hz")), + color); } } - } - // loop over all sound chips - sound_interface_iterator snditer(*dev); - if (snditer.count() > 0) - { - str << _("* Sound:\n"); - std::unordered_set<std::string> soundtags; - for (device_sound_interface &sound : snditer) + // scan for BIOS settings + int bios = 0; + if (dev->rom_region()) { - if (!sound.issound() || !soundtags.insert(sound.device().tag()).second) - continue; + // first loop through roms in search of default bios (shortname) + char const *bios_str(nullptr); + for (const tiny_rom_entry *rom = dev->rom_region(); !ROMENTRY_ISEND(rom); ++rom) + { + if (ROMENTRY_ISDEFAULT_BIOS(rom)) + bios_str = rom->name; + } - // count how many identical sound chips we have - int count = 1; - for (device_sound_interface &scan : snditer) + // then loop again to count bios options and to get the default bios complete name + char const *bios_desc(nullptr); + for (romload::system_bios const &rom : romload::entries(dev->rom_region()).get_system_bioses()) { - if (sound.device().type() == scan.device().type() && sound.device().clock() == scan.device().clock()) - if (soundtags.insert(scan.device().tag()).second) - count++; + bios++; + if (bios_str && !std::strcmp(bios_str, rom.get_name())) + bios_desc = rom.get_description(); } - // if more than one, prepend a #x in front of the name and display clock in kHz or MHz - int const clock = sound.device().clock(); - util::stream_format( - str, - (count > 1) - ? ((clock >= 1000000) ? _(" %1$d\xC3\x97%2$s %3$d.%4$06d\xC2\xA0MHz\n") : clock ? _(" %1$d\xC3\x97%2$s %5$d.%6$03d\xC2\xA0kHz\n") : _(" %1$d\xC3\x97%2$s\n")) - : ((clock >= 1000000) ? _(" %2$s %3$d.%4$06d\xC2\xA0MHz\n") : clock ? _(" %2$s %5$d.%6$03d\xC2\xA0kHz\n") : _(" %2$s\n")), - count, - sound.device().name(), - clock / 1000000, clock % 1000000, - clock / 1000, clock % 1000); - } - } - // scan for BIOS settings - int bios = 0; - if (dev->rom_region()) - { - // first loop through roms in search of default bios (shortname) - char const *bios_str(nullptr); - for (const tiny_rom_entry *rom = dev->rom_region(); !ROMENTRY_ISEND(rom); ++rom) - { - if (ROMENTRY_ISDEFAULT_BIOS(rom)) - bios_str = rom->name; + if (bios) + { + layout->add_text( + util::string_format( + _("* BIOS settings:\n %1$d options [default: %2$s]\n"), + bios, + bios_desc ? bios_desc : bios_str ? bios_str : ""), + color); + } } - // then loop again to count bios options and to get the default bios complete name - char const *bios_desc(nullptr); - for (romload::system_bios const &rom : romload::entries(dev->rom_region()).get_system_bioses()) + int input = 0, input_mj = 0, input_hana = 0, input_gamble = 0, input_analog = 0, input_adjust = 0; + int input_keypad = 0, input_keyboard = 0, dips = 0, confs = 0; + std::ostringstream dips_opt, confs_opt; + ioport_list portlist; { - bios++; - if (bios_str && !std::strcmp(bios_str, rom.get_name())) - bios_desc = rom.get_description(); + std::ostringstream errors; + for (device_t &iptdev : device_enumerator(*dev)) + portlist.append(iptdev, errors); } - if (bios) - util::stream_format(str, _("* BIOS settings:\n %1$d options [default: %2$s]\n"), bios, bios_desc ? bios_desc : bios_str ? bios_str : ""); - } - - int input = 0, input_mj = 0, input_hana = 0, input_gamble = 0, input_analog = 0, input_adjust = 0; - int input_keypad = 0, input_keyboard = 0, dips = 0, confs = 0; - std::string errors; - std::ostringstream dips_opt, confs_opt; - ioport_list portlist; - for (device_t &iptdev : device_iterator(*dev)) - portlist.append(iptdev, errors); - - // check if the device adds inputs to the system - for (auto &port : portlist) - for (ioport_field &field : port.second->fields()) - { - if (field.type() >= IPT_MAHJONG_FIRST && field.type() < IPT_MAHJONG_LAST) - input_mj++; - else if (field.type() >= IPT_HANAFUDA_FIRST && field.type() < IPT_HANAFUDA_LAST) - input_hana++; - else if (field.type() >= IPT_GAMBLING_FIRST && field.type() < IPT_GAMBLING_LAST) - input_gamble++; - else if (field.type() >= IPT_ANALOG_FIRST && field.type() < IPT_ANALOG_LAST) - input_analog++; - else if (field.type() == IPT_ADJUSTER) - input_adjust++; - else if (field.type() == IPT_KEYPAD) - input_keypad++; - else if (field.type() == IPT_KEYBOARD) - input_keyboard++; - else if (field.type() >= IPT_START1 && field.type() < IPT_UI_FIRST) - input++; - else if (field.type() == IPT_DIPSWITCH) + // check if the device adds inputs to the system + for (auto &port : portlist) + for (ioport_field &field : port.second->fields()) { - dips++; - bool def(false); - for (ioport_setting &setting : field.settings()) + if (field.type() >= IPT_MAHJONG_FIRST && field.type() < IPT_MAHJONG_LAST) + input_mj++; + else if (field.type() >= IPT_HANAFUDA_FIRST && field.type() < IPT_HANAFUDA_LAST) + input_hana++; + else if (field.type() >= IPT_GAMBLING_FIRST && field.type() < IPT_GAMBLING_LAST) + input_gamble++; + else if (field.type() >= IPT_ANALOG_FIRST && field.type() < IPT_ANALOG_LAST) + input_analog++; + else if (field.type() == IPT_ADJUSTER) + input_adjust++; + else if (field.type() == IPT_KEYPAD) + input_keypad++; + else if (field.type() == IPT_KEYBOARD) + input_keyboard++; + else if (field.type() >= IPT_START1 && field.type() < IPT_UI_FIRST) + input++; + else if (field.type() == IPT_DIPSWITCH) { - if (setting.value() == field.defvalue()) + dips++; + bool def(false); + for (ioport_setting const &setting : field.settings()) { - def = true; - util::stream_format(dips_opt, _(" %1$s [default: %2$s]\n"), field.name(), setting.name()); - break; + if (setting.value() == field.defvalue()) + { + def = true; + util::stream_format(dips_opt, _(" %1$s [default: %2$s]\n"), field.specific_name(), setting.name()); + break; + } } + if (!def) + util::stream_format(dips_opt, _(" %1$s\n"), field.specific_name()); } - if (!def) - util::stream_format(dips_opt, _(" %1$s\n"), field.name()); - } - else if (field.type() == IPT_CONFIG) - { - confs++; - bool def(false); - for (ioport_setting &setting : field.settings()) + else if (field.type() == IPT_CONFIG) { - if (setting.value() == field.defvalue()) + confs++; + bool def(false); + for (ioport_setting const &setting : field.settings()) { - def = true; - util::stream_format(confs_opt, _(" %1$s [default: %2$s]\n"), field.name(), setting.name()); - break; + if (setting.value() == field.defvalue()) + { + def = true; + util::stream_format(confs_opt, _(" %1$s [default: %2$s]\n"), field.specific_name(), setting.name()); + break; + } } + if (!def) + util::stream_format(confs_opt, _(" %1$s\n"), field.specific_name()); } - if (!def) - util::stream_format(confs_opt, _(" %1$s\n"), field.name()); } - } - if (dips) - str << _("* DIP switch settings:\n") << dips_opt.str(); - if (confs) - str << _("* Configuration settings:\n") << confs_opt.str(); - if (input || input_mj || input_hana || input_gamble || input_analog || input_adjust || input_keypad || input_keyboard) - str << _("* Input device(s):\n"); - if (input) - util::stream_format(str, _(" User inputs [%1$d inputs]\n"), input); - if (input_mj) - util::stream_format(str, _(" Mahjong inputs [%1$d inputs]\n"), input_mj); - if (input_hana) - util::stream_format(str, _(" Hanafuda inputs [%1$d inputs]\n"), input_hana); - if (input_gamble) - util::stream_format(str, _(" Gambling inputs [%1$d inputs]\n"), input_gamble); - if (input_analog) - util::stream_format(str, _(" Analog inputs [%1$d inputs]\n"), input_analog); - if (input_adjust) - util::stream_format(str, _(" Adjuster inputs [%1$d inputs]\n"), input_adjust); - if (input_keypad) - util::stream_format(str, _(" Keypad inputs [%1$d inputs]\n"), input_keypad); - if (input_keyboard) - util::stream_format(str, _(" Keyboard inputs [%1$d inputs]\n"), input_keyboard); - - image_interface_iterator imgiter(*dev); - if (imgiter.count() > 0) - { - str << _("* Media Options:\n"); - for (const device_image_interface &imagedev : imgiter) - util::stream_format(str, _(" %1$s [tag: %2$s]\n"), imagedev.image_type_name(), imagedev.device().tag()); - } + if (dips) + { + layout->add_text(_("* DIP switch settings:\n"), color); + layout->add_text(std::move(dips_opt).str(), color); + } + if (confs) + { + layout->add_text(_("* Configuration settings:\n"), color); + layout->add_text(std::move(confs_opt).str(), color); + } + if (input || input_mj || input_hana || input_gamble || input_analog || input_adjust || input_keypad || input_keyboard) + layout->add_text(_("* Input device(s):\n"), color); + if (input) + layout->add_text(util::string_format(_(" User inputs [%1$d inputs]\n"), input), color); + if (input_mj) + layout->add_text(util::string_format(_(" Mahjong inputs [%1$d inputs]\n"), input_mj), color); + if (input_hana) + layout->add_text(util::string_format(_(" Hanafuda inputs [%1$d inputs]\n"), input_hana), color); + if (input_gamble) + layout->add_text(util::string_format(_(" Gambling inputs [%1$d inputs]\n"), input_gamble), color); + if (input_analog) + layout->add_text(util::string_format(_(" Analog inputs [%1$d inputs]\n"), input_analog), color); + if (input_adjust) + layout->add_text(util::string_format(_(" Adjuster inputs [%1$d inputs]\n"), input_adjust), color); + if (input_keypad) + layout->add_text(util::string_format(_(" Keypad inputs [%1$d inputs]\n"), input_keypad), color); + if (input_keyboard) + layout->add_text(util::string_format(_(" Keyboard inputs [%1$d inputs]\n"), input_keyboard), color); - slot_interface_iterator slotiter(*dev); - if (slotiter.count() > 0) - { - str << _("* Slot Options:\n"); - for (const device_slot_interface &slot : slotiter) - util::stream_format(str, _(" %1$s [default: %2$s]\n"), slot.device().tag(), slot.default_option() ? slot.default_option() : "----"); - } + image_interface_enumerator imgiter(*dev); + if (imgiter.count() > 0) + { + layout->add_text(_("* Media Options:\n"), color); + for (const device_image_interface &imagedev : imgiter) + { + layout->add_text( + util::string_format( + _(" %1$s [tag: %2$s]\n"), + imagedev.image_type_name(), + imagedev.device().tag()), + color); + } + } - if ((execiter.count() + scriter.count() + snditer.count() + imgiter.count() + slotiter.count() + bios + dips + confs - + input + input_mj + input_hana + input_gamble + input_analog + input_adjust + input_keypad + input_keyboard) == 0) - str << _("[None]\n"); + slot_interface_enumerator slotiter(*dev); + if (slotiter.count() > 0) + { + layout->add_text(_("* Slot Options:\n"), color); + for (const device_slot_interface &slot : slotiter) + { + layout->add_text( + util::string_format( + _(" %1$s [default: %2$s]\n"), + slot.device().tag(), + slot.default_option() ? slot.default_option() : "----"), + color); + } + } - mconfig.device_remove(m_option->name()); - item_append(str.str(), "", FLAG_MULTILINE, nullptr); -} + if ((execiter.count() + scriter.count() + snditer.count() + imgiter.count() + slotiter.count() + bios + dips + confs + + input + input_mj + input_hana + input_gamble + input_analog + input_adjust + input_keypad + input_keyboard) == 0) + layout->add_text(_("[None]\n"), color); -void menu_device_config::handle() -{ - /* process the menu */ - process(0); + mconfig.device_remove(m_option->name()); + lines = layout->lines(); + } + width = layout->actual_width(); } -menu_device_config::~menu_device_config() +void menu_device_config::populate() { } diff --git a/src/frontend/mame/ui/devopt.h b/src/frontend/mame/ui/devopt.h index 2bb4e24c6c0..2e67cd4524f 100644 --- a/src/frontend/mame/ui/devopt.h +++ b/src/frontend/mame/ui/devopt.h @@ -8,29 +8,32 @@ ***************************************************************************/ -#pragma once - #ifndef MAME_FRONTEND_UI_DEVOPT_H #define MAME_FRONTEND_UI_DEVOPT_H -#include "ui/menu.h" +#pragma once + +#include "ui/textbox.h" + namespace ui { -class menu_device_config : public menu + +class menu_device_config : public menu_textbox { public: menu_device_config(mame_ui_manager &mui, render_container &container, device_slot_interface *slot, device_slot_interface::slot_option const *option); virtual ~menu_device_config() override; +protected: + virtual void populate_text(std::optional<text_layout> &layout, float &width, int &lines) override; + private: - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; - device_slot_interface *m_owner; - device_slot_interface::slot_option const *m_option; - bool m_mounted; + device_slot_interface::slot_option const *const m_option; + bool const m_mounted; }; } // namespace ui -#endif /* MAME_FRONTEND_UI_DEVOPT_H */ +#endif // MAME_FRONTEND_UI_DEVOPT_H diff --git a/src/frontend/mame/ui/dirmenu.cpp b/src/frontend/mame/ui/dirmenu.cpp index 1d8166718f0..fba8f7fcb0c 100644 --- a/src/frontend/mame/ui/dirmenu.cpp +++ b/src/frontend/mame/ui/dirmenu.cpp @@ -9,513 +9,611 @@ *********************************************************************/ #include "emu.h" +#include "ui/dirmenu.h" #include "ui/ui.h" -#include "ui/dirmenu.h" #include "ui/utils.h" -#include "ui/optsmenu.h" #include "emuopts.h" +#include "fileio.h" #include "mame.h" +#include "util/corestr.h" +#include "util/path.h" + +#include <locale> + namespace ui { -static int ADDING = 1; -static int CHANGE = 2; + +namespace { struct folders_entry { const char *name; const char *option; - const int action; }; -static const folders_entry s_folders[] = +const folders_entry f_folders[] = { - { __("ROMs"), OPTION_MEDIAPATH, ADDING }, - { __("Software Media"), OPTION_SWPATH, CHANGE }, - { __("UI"), OPTION_UI_PATH, CHANGE }, - { __("Language"), OPTION_LANGUAGEPATH, CHANGE }, - { __("Samples"), OPTION_SAMPLEPATH, ADDING }, - { __("DATs"), OPTION_HISTORY_PATH, ADDING }, - { __("INIs"), OPTION_INIPATH, ADDING }, - { __("Category INIs"), OPTION_CATEGORYINI_PATH, CHANGE }, - { __("Icons"), OPTION_ICONS_PATH, ADDING }, - { __("Cheats"), OPTION_CHEATPATH, ADDING }, - { __("Snapshots"), OPTION_SNAPSHOT_DIRECTORY, ADDING }, - { __("Cabinets"), OPTION_CABINETS_PATH, ADDING }, - { __("Flyers"), OPTION_FLYERS_PATH, ADDING }, - { __("Titles"), OPTION_TITLES_PATH, ADDING }, - { __("Ends"), OPTION_ENDS_PATH, ADDING }, - { __("PCBs"), OPTION_PCBS_PATH, ADDING }, - { __("Marquees"), OPTION_MARQUEES_PATH, ADDING }, - { __("Controls Panels"), OPTION_CPANELS_PATH, ADDING }, - { __("Crosshairs"), OPTION_CROSSHAIRPATH, ADDING }, - { __("Artworks"), OPTION_ARTPATH, ADDING }, - { __("Bosses"), OPTION_BOSSES_PATH, ADDING }, - { __("Artworks Preview"), OPTION_ARTPREV_PATH, ADDING }, - { __("Select"), OPTION_SELECT_PATH, ADDING }, - { __("GameOver"), OPTION_GAMEOVER_PATH, ADDING }, - { __("HowTo"), OPTION_HOWTO_PATH, ADDING }, - { __("Logos"), OPTION_LOGOS_PATH, ADDING }, - { __("Scores"), OPTION_SCORES_PATH, ADDING }, - { __("Versus"), OPTION_VERSUS_PATH, ADDING }, - { __("Covers"), OPTION_COVER_PATH, ADDING } + { N_p("path-option", "ROMs"), OPTION_MEDIAPATH }, + { N_p("path-option", "Software Media"), OPTION_SWPATH }, + { N_p("path-option", "Sound Samples"), OPTION_SAMPLEPATH }, + { N_p("path-option", "Artwork"), OPTION_ARTPATH }, + { N_p("path-option", "Crosshairs"), OPTION_CROSSHAIRPATH }, + { N_p("path-option", "Cheat Files"), OPTION_CHEATPATH }, + { N_p("path-option", "Plugins"), OPTION_PLUGINSPATH }, + { N_p("path-option", "UI Translations"), OPTION_LANGUAGEPATH }, + { N_p("path-option", "Software Lists"), OPTION_HASHPATH }, + { N_p("path-option", "INIs"), OPTION_INIPATH }, + { N_p("path-option", "UI Settings"), OPTION_UI_PATH }, + { N_p("path-option", "Plugin Data"), OPTION_PLUGINDATAPATH }, + { N_p("path-option", "DATs"), OPTION_HISTORY_PATH }, + { N_p("path-option", "Category INIs"), OPTION_CATEGORYINI_PATH }, + { N_p("path-option", "Snapshots"), OPTION_SNAPSHOT_DIRECTORY }, + { N_p("path-option", "Icons"), OPTION_ICONS_PATH }, + { N_p("path-option", "Control Panels"), OPTION_CPANELS_PATH }, + { N_p("path-option", "Cabinets"), OPTION_CABINETS_PATH }, + { N_p("path-option", "Marquees"), OPTION_MARQUEES_PATH }, + { N_p("path-option", "PCBs"), OPTION_PCBS_PATH }, + { N_p("path-option", "Flyers"), OPTION_FLYERS_PATH }, + { N_p("path-option", "Title Screens"), OPTION_TITLES_PATH }, + { N_p("path-option", "Game Endings"), OPTION_ENDS_PATH }, + { N_p("path-option", "Bosses"), OPTION_BOSSES_PATH }, + { N_p("path-option", "Artwork Previews"), OPTION_ARTPREV_PATH }, + { N_p("path-option", "Select"), OPTION_SELECT_PATH }, + { N_p("path-option", "Game Over Screens"), OPTION_GAMEOVER_PATH }, + { N_p("path-option", "HowTo"), OPTION_HOWTO_PATH }, + { N_p("path-option", "Logos"), OPTION_LOGOS_PATH }, + { N_p("path-option", "Scores"), OPTION_SCORES_PATH }, + { N_p("path-option", "Versus"), OPTION_VERSUS_PATH }, + { N_p("path-option", "Covers"), OPTION_COVER_PATH } }; /************************************************** - MENU DIRECTORY + MENU REMOVE FOLDER **************************************************/ -//------------------------------------------------- -// ctor / dtor -//------------------------------------------------- - -menu_directory::menu_directory(mame_ui_manager &mui, render_container &container) : menu(mui, container) -{ -} -menu_directory::~menu_directory() +class menu_remove_folder : public menu { - ui().save_ui_options(); - ui_globals::reset = true; -} +public: + menu_remove_folder(mame_ui_manager &mui, render_container &container, int ref); -//------------------------------------------------- -// handle -//------------------------------------------------- +private: + virtual void populate() override; + virtual bool handle(event const *ev) override; -void menu_directory::handle() -{ - // process the menu - const event *menu_event = process(0); - - if (menu_event != nullptr && menu_event->itemref != nullptr && menu_event->iptkey == IPT_UI_SELECT) - menu::stack_push<menu_display_actual>(ui(), container(), selected_index()); -} + std::string m_searchpath; + int const m_ref; + std::vector<std::string> m_folders; +}; //------------------------------------------------- -// populate +// ctor / dtor //------------------------------------------------- -void menu_directory::populate(float &customtop, float &custombottom) +menu_remove_folder::menu_remove_folder(mame_ui_manager &mui, render_container &container, int ref) + : menu(mui, container) + , m_ref(ref) { - for (auto & elem : s_folders) - item_append(_(elem.name), "", 0, (void *)(uintptr_t)elem.action); + set_heading(util::string_format(_("Remove %1$s Folder"), _("path-option", f_folders[m_ref].name))); - item_append(menu_item_type::SEPARATOR); - customtop = ui().get_line_height() + 3.0f * ui().box_tb_border(); + if (mui.options().exists(f_folders[m_ref].option)) + m_searchpath.assign(mui.options().value(f_folders[m_ref].option)); + else + m_searchpath.assign(mui.machine().options().value(f_folders[m_ref].option)); + + path_iterator path(m_searchpath); + std::string curpath; + while (path.next(curpath)) + m_folders.push_back(curpath); } //------------------------------------------------- -// perform our special rendering +// handle //------------------------------------------------- -void menu_directory::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +bool menu_remove_folder::handle(event const *ev) { - char const *const toptext[] = { _("Folders Setup") }; - draw_text_box( - std::begin(toptext), std::end(toptext), - 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); -} + // process the menu + if (ev && ev->itemref && ev->iptkey == IPT_UI_SELECT) + { + std::string tmppath, error_string; + m_folders.erase(m_folders.begin() + selected_index()); + for (int x = 0; x < m_folders.size(); ++x) + { + tmppath.append(m_folders[x]); + if (x < m_folders.size() - 1) + tmppath.append(";"); + } -/************************************************** - MENU DISPLAY PATH -**************************************************/ -//------------------------------------------------- -// ctor / dtor -//------------------------------------------------- + if (ui().options().exists(f_folders[m_ref].option)) + ui().options().set_value(f_folders[m_ref].option, tmppath, OPTION_PRIORITY_CMDLINE); + else if (machine().options().value(f_folders[m_ref].option) != tmppath) + machine().options().set_value(f_folders[m_ref].option, tmppath, OPTION_PRIORITY_CMDLINE); -menu_display_actual::menu_display_actual(mame_ui_manager &mui, render_container &container, int ref) - : menu(mui, container), m_ref(ref) -{ -} + reset_parent(reset_options::REMEMBER_REF); + stack_pop(); + } -menu_display_actual::~menu_display_actual() -{ + return false; } //------------------------------------------------- -// handle +// populate menu //------------------------------------------------- -void menu_display_actual::handle() +void menu_remove_folder::populate() { - // process the menu - const event *menu_event = process(0); - if (menu_event != nullptr && menu_event->itemref != nullptr && menu_event->iptkey == IPT_UI_SELECT) - switch ((uintptr_t)menu_event->itemref) - { - case REMOVE: - menu::stack_push<menu_remove_folder>(ui(), container(), m_ref); - break; + int folders_count = 0; + for (auto & elem : m_folders) + item_append(elem, 0, (void *)(uintptr_t)++folders_count); - case ADD_CHANGE: - menu::stack_push<menu_add_change_folder>(ui(), container(), m_ref); - break; - } + item_append(menu_item_type::SEPARATOR); } -//------------------------------------------------- -// populate -//------------------------------------------------- -void menu_display_actual::populate(float &customtop, float &custombottom) -{ - m_heading[0] = string_format(_("Current %1$s Folders"), _(s_folders[m_ref].name)); - if (ui().options().exists(s_folders[m_ref].option)) - m_searchpath.assign(ui().options().value(s_folders[m_ref].option)); - else - m_searchpath.assign(machine().options().value(s_folders[m_ref].option)); +/************************************************** + MENU ADD FOLDER +**************************************************/ - path_iterator path(m_searchpath.c_str()); - std::string curpath; - m_folders.clear(); - while (path.next(curpath, nullptr)) - m_folders.push_back(curpath); +class menu_add_change_folder : public menu +{ +public: + menu_add_change_folder(mame_ui_manager &mui, render_container &container, int ref, bool multipath); - item_append((s_folders[m_ref].action == CHANGE) ? _("Change Folder") : _("Add Folder"), "", 0, (void *)ADD_CHANGE); +protected: + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) override; - if (m_folders.size() > 1) - item_append(_("Remove Folder"), "", 0, (void *)REMOVE); + virtual bool custom_ui_back() override { return !m_search.empty(); } - item_append(menu_item_type::SEPARATOR); - customtop = (m_folders.size() + 1) * ui().get_line_height() + 6.0f * ui().box_tb_border(); -} +private: + virtual void populate() override; + virtual bool handle(event const *ev) override; -//------------------------------------------------- -// perform our special rendering -//------------------------------------------------- + void update_search(); -void menu_display_actual::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) -{ - float const lineheight(ui().get_line_height()); - float const maxwidth(draw_text_box( - std::begin(m_folders), std::end(m_folders), - origx1, origx2, origy1 - (3.0f * ui().box_tb_border()) - (m_folders.size() * lineheight), origy1 - ui().box_tb_border(), - ui::text_layout::CENTER, ui::text_layout::TRUNCATE, false, - ui().colors().text_color(), ui().colors().background_color(), 1.0f)); - draw_text_box( - std::begin(m_heading), std::end(m_heading), - 0.5f * (1.0f - maxwidth), 0.5f * (1.0f + maxwidth), origy1 - top, origy1 - top + lineheight + (2.0f * ui().box_tb_border()), - ui::text_layout::CENTER, ui::text_layout::TRUNCATE, false, - ui().colors().text_color(), UI_GREEN_COLOR, 1.0f); -} + int const m_ref; + bool const m_multipath; + std::string m_current_path; + std::string m_search; + std::vector<std::string> m_folders; +}; -/************************************************** -MENU ADD FOLDER -**************************************************/ //------------------------------------------------- -// ctor / dtor +// ctor //------------------------------------------------- -menu_add_change_folder::menu_add_change_folder(mame_ui_manager &mui, render_container &container, int ref) : menu(mui, container) +menu_add_change_folder::menu_add_change_folder(mame_ui_manager &mui, render_container &container, int ref, bool multipath) + : menu(mui, container) + , m_ref(ref) + , m_multipath(multipath) { - m_ref = ref; - m_change = (s_folders[ref].action == CHANGE); - m_search.clear(); - // configure the starting path osd_get_full_path(m_current_path, "."); std::string searchpath; - if (mui.options().exists(s_folders[m_ref].option)) - searchpath = mui.options().value(s_folders[m_ref].option); + if (mui.options().exists(f_folders[m_ref].option)) + searchpath = mui.options().value(f_folders[m_ref].option); else - searchpath = mui.machine().options().value(s_folders[m_ref].option); + searchpath = mui.machine().options().value(f_folders[m_ref].option); - path_iterator path(searchpath.c_str()); + path_iterator path(searchpath); std::string curpath; - while (path.next(curpath, nullptr)) + while (path.next(curpath)) m_folders.push_back(curpath); } -menu_add_change_folder::~menu_add_change_folder() -{ -} - //------------------------------------------------- // handle //------------------------------------------------- -void menu_add_change_folder::handle() +bool menu_add_change_folder::handle(event const *ev) { - // process the menu - const event *menu_event = process(0); + if (!ev || !ev->itemref) + return false; - if (menu_event != nullptr && menu_event->itemref != nullptr) + if (ev->iptkey == IPT_UI_SELECT) { - if (menu_event->iptkey == IPT_UI_SELECT) - { - int index = (uintptr_t)menu_event->itemref - 1; - const menu_item &pitem = item(index); + assert(ev->item); + menu_item const &pitem = *ev->item; - // go up to the parent path - if (!strcmp(pitem.text.c_str(), "..")) - { - size_t first_sep = m_current_path.find_first_of(PATH_SEPARATOR[0]); - size_t last_sep = m_current_path.find_last_of(PATH_SEPARATOR[0]); - if (first_sep != last_sep) - m_current_path.erase(++last_sep); - } + // go up to the parent path + if (pitem.text() == "..") + { + size_t const first_sep = m_current_path.find_first_of(PATH_SEPARATOR[0]); + size_t const last_sep = m_current_path.find_last_of(PATH_SEPARATOR[0]); + m_current_path.erase(last_sep + ((first_sep == last_sep) ? 1 : 0)); + } + else + { + // if isn't a drive, appends the directory + if (pitem.subtext() != "[DRIVE]") + util::path_append(m_current_path, pitem.text()); else - { - // if isn't a drive, appends the directory - if (strcmp(pitem.subtext.c_str(), "[DRIVE]") != 0) - { - if (m_current_path[m_current_path.length() - 1] == PATH_SEPARATOR[0]) - m_current_path.append(pitem.text); - else - m_current_path.append(PATH_SEPARATOR).append(pitem.text); - } - else - m_current_path = pitem.text; - } + m_current_path = pitem.text(); + } - // reset the char buffer also in this case - m_search.clear(); - reset(reset_options::SELECT_FIRST); + // reset the char buffer also in this case + m_search.clear(); + reset(reset_options::SELECT_FIRST); + } + else if (ev->iptkey == IPT_UI_PASTE) + { + if (paste_text(m_search, uchar_is_printable)) + { + update_search(); + return true; } - else if (menu_event->iptkey == IPT_SPECIAL) + } + else if (ev->iptkey == IPT_SPECIAL) + { + if (ev->unichar == 0x09) { - bool update_selected = false; - - if (menu_event->unichar == 0x09) + // Tab key, save current path + std::string error_string; + if (!m_multipath) { - // Tab key, save current path - std::string error_string; - if (m_change) - { - if (ui().options().exists(s_folders[m_ref].option)) - ui().options().set_value(s_folders[m_ref].option, m_current_path.c_str(), OPTION_PRIORITY_CMDLINE); - else if (strcmp(machine().options().value(s_folders[m_ref].option), m_current_path.c_str()) != 0) - { - machine().options().set_value(s_folders[m_ref].option, m_current_path.c_str(), OPTION_PRIORITY_CMDLINE); - } - } - else - { - m_folders.push_back(m_current_path); - std::string tmppath; - for (int x = 0; x < m_folders.size(); ++x) - { - tmppath.append(m_folders[x]); - if (x != m_folders.size() - 1) - tmppath.append(";"); - } - - if (ui().options().exists(s_folders[m_ref].option)) - ui().options().set_value(s_folders[m_ref].option, tmppath.c_str(), OPTION_PRIORITY_CMDLINE); - else if (strcmp(machine().options().value(s_folders[m_ref].option), tmppath.c_str()) != 0) - { - machine().options().set_value(s_folders[m_ref].option, tmppath.c_str(), OPTION_PRIORITY_CMDLINE); - } - } - - reset_parent(reset_options::SELECT_FIRST); - stack_pop(); + if (ui().options().exists(f_folders[m_ref].option)) + ui().options().set_value(f_folders[m_ref].option, m_current_path, OPTION_PRIORITY_CMDLINE); + else if (machine().options().value(f_folders[m_ref].option) != m_current_path) + machine().options().set_value(f_folders[m_ref].option, m_current_path, OPTION_PRIORITY_CMDLINE); } else { - // if it's any other key and we're not maxed out, update - update_selected = input_character(m_search, menu_event->unichar, uchar_is_printable); - } - - // check for entries which matches our search buffer - if (update_selected) - { - const int cur_selected = selected_index(); - int entry, bestmatch = 0; - - // from current item to the end - for (entry = cur_selected; entry < item_count(); entry++) - if (item(entry).ref != nullptr && !m_search.empty()) - { - int match = 0; - for (int i = 0; i < m_search.size() + 1; i++) - { - if (core_strnicmp(item(entry).text.c_str(), m_search.data(), i) == 0) - match = i; - } - - if (match > bestmatch) - { - bestmatch = match; - set_selected_index(entry); - } - } - - // and from the first item to current one - for (entry = 0; entry < cur_selected; entry++) + m_folders.push_back(m_current_path); + std::string tmppath; + for (int x = 0; x < m_folders.size(); ++x) { - if (item(entry).ref != nullptr && !m_search.empty()) - { - int match = 0; - for (int i = 0; i < m_search.size() + 1; i++) - { - if (core_strnicmp(item(entry).text.c_str(), m_search.data(), i) == 0) - match = i; - } - - if (match > bestmatch) - { - bestmatch = match; - set_selected_index(entry); - } - } + tmppath.append(m_folders[x]); + if (x != m_folders.size() - 1) + tmppath.append(";"); } - centre_selection(); + + if (ui().options().exists(f_folders[m_ref].option)) + ui().options().set_value(f_folders[m_ref].option, tmppath, OPTION_PRIORITY_CMDLINE); + else if (machine().options().value(f_folders[m_ref].option) != tmppath) + machine().options().set_value(f_folders[m_ref].option, tmppath, OPTION_PRIORITY_CMDLINE); } + + reset_parent(reset_options::SELECT_FIRST); + stack_pop(); } - else if (menu_event->iptkey == IPT_UI_CANCEL) + else if (input_character(m_search, ev->unichar, uchar_is_printable)) { - // reset the char buffer also in this case - m_search.clear(); + // if it's any other key and we're not maxed out, update + update_search(); + return true; } } + else if (ev->iptkey == IPT_UI_CANCEL) + { + // reset the char buffer also in this case + m_search.clear(); + return true; + } + + return false; } //------------------------------------------------- // populate //------------------------------------------------- -void menu_add_change_folder::populate(float &customtop, float &custombottom) +void menu_add_change_folder::populate() { - // open a path - const char *volume_name = nullptr; - file_enumerator path(m_current_path.c_str()); - const osd::directory::entry *dirent; int folders_count = 0; // add the drives - for (int i = 0; (volume_name = osd_get_volume_name(i)) != nullptr; ++i) + for (std::string const &volume_name : osd_get_volume_names()) item_append(volume_name, "[DRIVE]", 0, (void *)(uintptr_t)++folders_count); - // add the directories + // get subdirectories + std::vector<std::string> dirnames; + file_enumerator path(m_current_path); + const osd::directory::entry *dirent; while ((dirent = path.next()) != nullptr) { - if (dirent->type == osd::directory::entry::entry_type::DIR && strcmp(dirent->name, ".") != 0) - item_append(dirent->name, "[DIR]", 0, (void *)(uintptr_t)++folders_count); + if ((osd::directory::entry::entry_type::DIR == dirent->type) && strcmp(dirent->name, ".")) + dirnames.emplace_back(dirent->name); } + // sort + std::locale const lcl; + std::collate<wchar_t> const &coll = std::use_facet<std::collate<wchar_t> >(lcl); + std::sort( + dirnames.begin(), + dirnames.end(), + [&coll] (std::string const &x, std::string const &y) + { + std::wstring const xw = wstring_from_utf8(x); + std::wstring const yw = wstring_from_utf8(y); + return coll.compare(xw.data(), xw.data() + xw.size(), yw.data(), yw.data() + yw.size()) < 0; + }); + + // add to menu + for (std::string const &name : dirnames) + item_append(name, "[DIR]", 0, (void *)(uintptr_t)++folders_count); + item_append(menu_item_type::SEPARATOR); +} + +//------------------------------------------------- +// recompute metrics +//------------------------------------------------- + +void menu_add_change_folder::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + menu::recompute_metrics(width, height, aspect); // configure the custom rendering - customtop = 2.0f * ui().get_line_height() + 3.0f * ui().box_tb_border(); - custombottom = 1.0f * ui().get_line_height() + 3.0f * ui().box_tb_border(); + set_custom_space(2.0f * line_height() + 3.0f * tb_border(), 1.0f * line_height() + 3.0f * tb_border()); } //------------------------------------------------- // perform our special rendering //------------------------------------------------- -void menu_add_change_folder::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_add_change_folder::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { std::string const toptext[] = { util::string_format( - m_change ? _("Change %1$s Folder - Search: %2$s_") : _("Add %1$s Folder - Search: %2$s_"), - _(s_folders[m_ref].name), + m_multipath ? _("Add %1$s Folder - Search: %2$s_") : _("Change %1$s Folder - Search: %2$s_"), + _("path-option", f_folders[m_ref].name), m_search), m_current_path }; draw_text_box( std::begin(toptext), std::end(toptext), - origx1, origx2, origy1 - top, origy1 - ui().box_tb_border(), - ui::text_layout::CENTER, ui::text_layout::NEVER, false, - ui().colors().text_color(), UI_GREEN_COLOR, 1.0f); + origx1, origx2, origy1 - top, origy1 - tb_border(), + text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, false, + ui().colors().text_color(), UI_GREEN_COLOR); // bottom text char const *const bottomtext[] = { _("Press TAB to set") }; draw_text_box( std::begin(bottomtext), std::end(bottomtext), - origx1, origx2, origy2 + ui().box_tb_border(), origy2 + bottom, - ui::text_layout::CENTER, ui::text_layout::TRUNCATE, false, - ui().colors().text_color(), UI_RED_COLOR, 1.0f); + origx1, origx2, origy2 + tb_border(), origy2 + bottom, + text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, false, + ui().colors().text_color(), ui().colors().background_color()); } -/************************************************** - MENU REMOVE FOLDER -**************************************************/ //------------------------------------------------- -// ctor / dtor +// update search //------------------------------------------------- -menu_remove_folder::menu_remove_folder(mame_ui_manager &mui, render_container &container, int ref) : menu(mui, container) +void menu_add_change_folder::update_search() { - m_ref = ref; - if (mui.options().exists(s_folders[m_ref].option)) - m_searchpath.assign(mui.options().value(s_folders[m_ref].option)); - else - m_searchpath.assign(mui.machine().options().value(s_folders[m_ref].option)); + // check for entries which matches our search buffer + const int cur_selected = selected_index(); + int entry, bestmatch = 0; - path_iterator path(m_searchpath.c_str()); - std::string curpath; - while (path.next(curpath, nullptr)) - m_folders.push_back(curpath); + // from current item to the end + for (entry = cur_selected; entry < item_count(); entry++) + if (item(entry).ref() && !m_search.empty()) + { + int match = 0; + for (int i = 0; i < m_search.size() + 1; i++) + { + if (core_strnicmp(item(entry).text().c_str(), m_search.data(), i) == 0) + match = i; + } + + if (match > bestmatch) + { + bestmatch = match; + set_selected_index(entry); + } + } + + // and from the first item to current one + for (entry = 0; entry < cur_selected; entry++) + { + if (item(entry).ref() && !m_search.empty()) + { + int match = 0; + for (int i = 0; i < m_search.size() + 1; i++) + { + if (core_strnicmp(item(entry).text().c_str(), m_search.data(), i) == 0) + match = i; + } + + if (match > bestmatch) + { + bestmatch = match; + set_selected_index(entry); + } + } + } + centre_selection(); } -menu_remove_folder::~menu_remove_folder() + +/************************************************** + MENU DISPLAY PATH +**************************************************/ + +class menu_display_actual : public menu { +public: + menu_display_actual(mame_ui_manager &mui, render_container &container, int selectedref) + : menu(mui, container) + , m_ref(selectedref) + , m_multipath(is_multipath(f_folders[selectedref].option)) + , m_heading{ util::string_format(m_multipath ? _("%1$s Folders") : _("%1$s Folder"), _("path-option", f_folders[selectedref].name)) } + { + } + +protected: + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) override; + +private: + enum + { + ADD_CHANGE = 1, + REMOVE, + }; + + virtual void populate() override; + virtual bool handle(event const *ev) override; + + bool is_multipath(std::string_view folder) const; + + int const m_ref; + bool const m_multipath; + std::string const m_heading[1]; + std::string m_searchpath; + std::vector<std::string> m_folders; +}; + +//------------------------------------------------- +// is_multipath +//------------------------------------------------- + +bool menu_display_actual::is_multipath(std::string_view folder) const +{ + auto option = ui().options().get_entry(folder); + if (!option) + option = machine().options().get_entry(folder); + assert(option); + + return option->type() == core_options::option_type::MULTIPATH; } //------------------------------------------------- // handle //------------------------------------------------- -void menu_remove_folder::handle() +bool menu_display_actual::handle(event const *ev) { - // process the menu - const event *menu_event = process(0); - if (menu_event != nullptr && menu_event->itemref != nullptr && menu_event->iptkey == IPT_UI_SELECT) + if (ev && ev->itemref && ev->iptkey == IPT_UI_SELECT) { - std::string tmppath, error_string; - m_folders.erase(m_folders.begin() + selected_index()); - for (int x = 0; x < m_folders.size(); ++x) + switch ((uintptr_t)ev->itemref) { - tmppath.append(m_folders[x]); - if (x < m_folders.size() - 1) - tmppath.append(";"); - } + case REMOVE: + menu::stack_push<menu_remove_folder>(ui(), container(), m_ref); + break; - if (ui().options().exists(s_folders[m_ref].option)) - ui().options().set_value(s_folders[m_ref].option, tmppath.c_str(), OPTION_PRIORITY_CMDLINE); - else if (strcmp(machine().options().value(s_folders[m_ref].option),tmppath.c_str())!=0) - { - machine().options().set_value(s_folders[m_ref].option, tmppath.c_str(), OPTION_PRIORITY_CMDLINE); + case ADD_CHANGE: + menu::stack_push<menu_add_change_folder>(ui(), container(), m_ref, m_multipath); + break; } - - reset_parent(reset_options::REMEMBER_REF); - stack_pop(); } + + return false; } //------------------------------------------------- -// populate menu +// populate //------------------------------------------------- -void menu_remove_folder::populate(float &customtop, float &custombottom) +void menu_display_actual::populate() { - int folders_count = 0; - for (auto & elem : m_folders) - item_append(elem, "", 0, (void *)(uintptr_t)++folders_count); + auto const &folder = f_folders[m_ref]; + auto option = ui().options().get_entry(folder.option); + if (!option) + option = machine().options().get_entry(folder.option); + assert(option); + + m_searchpath.assign(option->value()); + + m_folders.clear(); + if (m_multipath) + { + path_iterator path(m_searchpath); + std::string curpath; + while (path.next(curpath)) + m_folders.push_back(curpath); + + item_append(_("Add Folder"), 0, (void *)ADD_CHANGE); + if (m_folders.size() > 1) + item_append(_("Remove Folder"), 0, (void *)REMOVE); + } + else + { + m_folders.push_back(m_searchpath); + item_append(_("Change Folder"), 0, (void *)ADD_CHANGE); + } item_append(menu_item_type::SEPARATOR); - customtop = ui().get_line_height() + 3.0f * ui().box_tb_border(); + + set_custom_space((m_folders.size() + 1) * line_height() + 6.0f * tb_border(), 0.0f); +} + +//------------------------------------------------- +// recompute metrics +//------------------------------------------------- + +void menu_display_actual::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + menu::recompute_metrics(width, height, aspect); + + set_custom_space((m_folders.size() + 1) * line_height() + 6.0f * tb_border(), 0.0f); } //------------------------------------------------- // perform our special rendering //------------------------------------------------- -void menu_remove_folder::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_display_actual::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { - std::string const toptext[] = {string_format(_("Remove %1$s Folder"), _(s_folders[m_ref].name)) }; + float const maxwidth(draw_text_box( + std::begin(m_folders), std::end(m_folders), + origx1, origx2, origy1 - (3.0f * tb_border()) - (m_folders.size() * line_height()), origy1 - tb_border(), + text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, false, + ui().colors().text_color(), ui().colors().background_color())); draw_text_box( - std::begin(toptext), std::end(toptext), - 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); + std::begin(m_heading), std::end(m_heading), + 0.5f * (1.0f - maxwidth), 0.5f * (1.0f + maxwidth), origy1 - top, origy1 - top + line_height() + (2.0f * tb_border()), + text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, false, + ui().colors().text_color(), UI_GREEN_COLOR); +} + +} // anonymous namespace + + +/************************************************** + MENU DIRECTORY +**************************************************/ +//------------------------------------------------- +// ctor / dtor +//------------------------------------------------- + +menu_directory::menu_directory(mame_ui_manager &mui, render_container &container) : menu(mui, container) +{ + set_heading(_("Configure Folders")); +} + +menu_directory::~menu_directory() +{ + ui().save_ui_options(); + ui_globals::reset = true; +} + +//------------------------------------------------- +// handle +//------------------------------------------------- + +bool menu_directory::handle(event const *ev) +{ + if (ev && ev->itemref && ev->iptkey == IPT_UI_SELECT) + menu::stack_push<menu_display_actual>(ui(), container(), selected_index()); + + return false; +} + +//------------------------------------------------- +// populate +//------------------------------------------------- + +void menu_directory::populate() +{ + for (auto & elem : f_folders) + item_append(_("path-option", elem.name), 0, this); // need a non-null reference pointer - value is immaterial + + item_append(menu_item_type::SEPARATOR); } } // namespace ui diff --git a/src/frontend/mame/ui/dirmenu.h b/src/frontend/mame/ui/dirmenu.h index 7280d4ef571..54eb155267e 100644 --- a/src/frontend/mame/ui/dirmenu.h +++ b/src/frontend/mame/ui/dirmenu.h @@ -8,17 +8,18 @@ ***************************************************************************/ -#pragma once - #ifndef MAME_FRONTEND_UI_DIRMENU_H #define MAME_FRONTEND_UI_DIRMENU_H +#pragma once + #include "ui/menu.h" #include <string> #include <vector> namespace ui { + //------------------------------------------------- // class directory menu //------------------------------------------------- @@ -29,90 +30,11 @@ public: menu_directory(mame_ui_manager &mui, render_container &container); virtual ~menu_directory() 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; -}; - -//------------------------------------------------- -// class directory specific menu -//------------------------------------------------- - -class menu_display_actual : public menu -{ -public: - menu_display_actual(mame_ui_manager &mui, render_container &container, int selectedref); - virtual ~menu_display_actual() override; - -protected: - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; - -private: - enum - { - ADD_CHANGE = 1, - REMOVE, - }; - - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; - - std::string m_heading[1], m_searchpath; - std::vector<std::string> m_folders; - int m_ref; -}; - -//------------------------------------------------- -// class remove folder menu -//------------------------------------------------- - -class menu_remove_folder : public menu -{ -public: - menu_remove_folder(mame_ui_manager &mui, render_container &container, int ref); - virtual ~menu_remove_folder() 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; - - std::string m_searchpath; - int m_ref; - std::vector<std::string> m_folders; -}; - -//------------------------------------------------- -// class add / change folder menu -//------------------------------------------------- - -class menu_add_change_folder : public menu -{ -public: - menu_add_change_folder(mame_ui_manager &mui, render_container &container, int ref); - virtual ~menu_add_change_folder() override; - -protected: - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; - - virtual bool menu_has_search_active() override { return !m_search.empty(); } - -private: - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; - - int m_ref; - std::string m_current_path; - std::string m_search; - bool m_change; - std::vector<std::string> m_folders; + virtual void populate() override; + virtual bool handle(event const *ev) override; }; } // namespace ui -#endif /* MAME_FRONTEND_UI_DIRMENU_H */ +#endif // MAME_FRONTEND_UI_DIRMENU_H diff --git a/src/frontend/mame/ui/filecreate.cpp b/src/frontend/mame/ui/filecreate.cpp index 9928829ecf5..828e59cbb74 100644 --- a/src/frontend/mame/ui/filecreate.cpp +++ b/src/frontend/mame/ui/filecreate.cpp @@ -12,19 +12,21 @@ ***************************************************************************/ #include "emu.h" - #include "ui/filecreate.h" + #include "ui/ui.h" #include "ui/utils.h" -#include "imagedev/floppy.h" +#include "formats/flopimg.h" +#include "path.h" #include "zippath.h" #include <cstring> namespace ui { + /*************************************************************************** CONSTANTS ***************************************************************************/ @@ -53,11 +55,11 @@ CONFIRM SAVE AS MENU // ctor //------------------------------------------------- -menu_confirm_save_as::menu_confirm_save_as(mame_ui_manager &mui, render_container &container, bool *yes) +menu_confirm_save_as::menu_confirm_save_as(mame_ui_manager &mui, render_container &container, bool &yes) : menu(mui, container) + , m_yes(yes) { - m_yes = yes; - *m_yes = false; + m_yes = false; } @@ -74,32 +76,31 @@ menu_confirm_save_as::~menu_confirm_save_as() // populate //------------------------------------------------- -void menu_confirm_save_as::populate(float &customtop, float &custombottom) +void menu_confirm_save_as::populate() { - item_append(_("File Already Exists - Override?"), "", FLAG_DISABLE, nullptr); + item_append(_("File Already Exists - Override?"), FLAG_DISABLE, nullptr); item_append(menu_item_type::SEPARATOR); - item_append(_("No"), "", 0, ITEMREF_NO); - item_append(_("Yes"), "", 0, ITEMREF_YES); + item_append(_("No"), 0, ITEMREF_NO); + item_append(_("Yes"), 0, ITEMREF_YES); } //------------------------------------------------- // handle - confirm save as menu //------------------------------------------------- -void menu_confirm_save_as::handle() +bool menu_confirm_save_as::handle(event const *ev) { - // process the menu - const event *event = process(0); - // process the event - if ((event != nullptr) && (event->iptkey == IPT_UI_SELECT)) + if (ev && (ev->iptkey == IPT_UI_SELECT)) { - if (event->itemref == ITEMREF_YES) - *m_yes = true; + if (ev->itemref == ITEMREF_YES) + m_yes = true; // no matter what, pop out stack_pop(); } + + return false; } @@ -120,7 +121,7 @@ menu_file_create::menu_file_create(mame_ui_manager &mui, render_container &conta , m_current_format(nullptr) { m_image = image; - m_ok = true; + m_ok = false; m_filename.reserve(1024); m_filename = core_filename_extract_base(current_file); @@ -137,14 +138,36 @@ menu_file_create::~menu_file_create() //------------------------------------------------- +// recompute_metrics - recompute metrics +//------------------------------------------------- + +void menu_file_create::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + menu::recompute_metrics(width, height, aspect); + + set_custom_space(line_height() + 3.0F * tb_border(), 0.0F); +} + + +//------------------------------------------------- // custom_render - perform our special rendering //------------------------------------------------- -void menu_file_create::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_file_create::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { extra_text_render(top, bottom, origx1, origy1, origx2, origy2, - m_current_directory.c_str(), - nullptr); + m_current_directory, + std::string_view()); +} + + +//------------------------------------------------- +// custom_ui_back - override back handling +//------------------------------------------------- + +bool menu_file_create::custom_ui_back() +{ + return (get_selection_ref() == ITEMREF_NEW_IMAGE_NAME) && !m_filename.empty(); } @@ -152,10 +175,9 @@ void menu_file_create::custom_render(void *selectedref, float top, float bottom, // populate - populates the file creator menu //------------------------------------------------- -void menu_file_create::populate(float &customtop, float &custombottom) +void menu_file_create::populate() { std::string buffer; - const image_device_format *format; const std::string *new_image_name; // append the "New Image Name" item @@ -171,18 +193,17 @@ void menu_file_create::populate(float &customtop, float &custombottom) item_append(_("New Image Name:"), *new_image_name, 0, ITEMREF_NEW_IMAGE_NAME); // do we support multiple formats? - if (ENABLE_FORMATS) format = m_image->formatlist().front().get(); - if (ENABLE_FORMATS && (format != nullptr)) + image_device_format const *const format = ENABLE_FORMATS ? m_image->formatlist().front().get() : nullptr; + if (format) { + // FIXME: is this in the right order? It reassigns m_current_format after reading it. item_append(_("Image Format:"), m_current_format->description(), 0, ITEMREF_FORMAT); m_current_format = format; } // finish up the menu item_append(menu_item_type::SEPARATOR); - item_append(_("Create"), "", 0, ITEMREF_CREATE); - - customtop = ui().get_line_height() + 3.0f * ui().box_tb_border(); + item_append(_("Create"), 0, ITEMREF_CREATE); } @@ -190,43 +211,64 @@ void menu_file_create::populate(float &customtop, float &custombottom) // handle - file creator menu //------------------------------------------------- -void menu_file_create::handle() +bool menu_file_create::handle(event const *ev) { - // process the menu - const event *event = process(0); + if (!ev) + return false; - // process the event - if (event) + // handle selections + switch (ev->iptkey) { - // handle selections - switch (event->iptkey) + case IPT_UI_SELECT: + if ((ev->itemref == ITEMREF_CREATE) || (ev->itemref == ITEMREF_NEW_IMAGE_NAME)) + { + std::string tmp_file(m_filename); + if (tmp_file.find('.') != -1 && tmp_file.find('.') < tmp_file.length() - 1) + { + m_current_file = m_filename; + m_ok = true; + stack_pop(); + } + else + { + ui().popup_time(1, "%s", _("Please enter a file extension too")); + } + } + break; + + case IPT_UI_PASTE: + if (ev->itemref == ITEMREF_NEW_IMAGE_NAME) { - case IPT_UI_SELECT: - if ((event->itemref == ITEMREF_CREATE) || (event->itemref == ITEMREF_NEW_IMAGE_NAME)) + if (paste_text(m_filename, &osd_is_valid_filename_char)) { - std::string tmp_file(m_filename); - if (tmp_file.find(".") != -1 && tmp_file.find(".") < tmp_file.length() - 1) - { - m_current_file = m_filename; - stack_pop(); - } - else - ui().popup_time(1, "%s", _("Please enter a file extension too")); + ev->item->set_subtext(m_filename + "_"); + return true; } - break; + } + break; - case IPT_SPECIAL: - if (get_selection_ref() == ITEMREF_NEW_IMAGE_NAME) + case IPT_SPECIAL: + if (ev->itemref == ITEMREF_NEW_IMAGE_NAME) + { + if (input_character(m_filename, ev->unichar, &osd_is_valid_filename_char)) { - input_character(m_filename, event->unichar, &osd_is_valid_filename_char); - reset(reset_options::REMEMBER_POSITION); + ev->item->set_subtext(m_filename + "_"); + return true; } - break; - case IPT_UI_CANCEL: - m_ok = false; - break; } + break; + + case IPT_UI_CANCEL: + if ((ev->itemref == ITEMREF_NEW_IMAGE_NAME) && !m_filename.empty()) + { + m_filename.clear(); + ev->item->set_subtext("_"); + return true; + } + break; } + + return false; } @@ -238,12 +280,11 @@ SELECT FORMAT MENU // ctor //------------------------------------------------- -menu_select_format::menu_select_format(mame_ui_manager &mui, render_container &container, floppy_image_format_t **formats, int ext_match, int total_usable, int *result) +menu_select_format::menu_select_format(mame_ui_manager &mui, render_container &container, const std::vector<const floppy_image_format_t *> &formats, int ext_match, const floppy_image_format_t **result) : menu(mui, container) { m_formats = formats; m_ext_match = ext_match; - m_total_usable = total_usable; m_result = result; } @@ -261,17 +302,73 @@ menu_select_format::~menu_select_format() // populate //------------------------------------------------- -void menu_select_format::populate(float &customtop, float &custombottom) +void menu_select_format::populate() { - item_append(_("Select image format"), "", FLAG_DISABLE, nullptr); - for (int i = 0; i < m_total_usable; i++) + item_append(_("Select image format"), FLAG_DISABLE, nullptr); + for (unsigned int i = 0; i != m_formats.size(); i++) { const floppy_image_format_t *fmt = m_formats[i]; if (i && i == m_ext_match) item_append(menu_item_type::SEPARATOR); - item_append(fmt->description(), fmt->name(), 0, (void *)(uintptr_t)i); + item_append(fmt->description(), fmt->name(), 0, const_cast<floppy_image_format_t *>(fmt)); + } +} + + +//------------------------------------------------- +// handle +//------------------------------------------------- + +bool menu_select_format::handle(event const *ev) +{ + // process the menu + if (ev && ev->iptkey == IPT_UI_SELECT) + { + *m_result = (floppy_image_format_t *)ev->itemref; + stack_pop(); } + + return false; +} + + +/*************************************************************************** +SELECT FORMAT MENU +***************************************************************************/ + +//------------------------------------------------- +// ctor +//------------------------------------------------- + +menu_select_floppy_init::menu_select_floppy_init(mame_ui_manager &mui, render_container &container, std::vector<std::reference_wrapper<const floppy_image_device::fs_info>> &&fs, int *result) + : menu(mui, container), + m_fs(std::move(fs)), + m_result(result) + +{ +} + + +//------------------------------------------------- +// dtor +//------------------------------------------------- + +menu_select_floppy_init::~menu_select_floppy_init() +{ +} + + +//------------------------------------------------- +// populate +//------------------------------------------------- + +void menu_select_floppy_init::populate() +{ + item_append(_("Select initial contents"), FLAG_DISABLE, nullptr); + int id = 0; + for (const floppy_image_device::fs_info &fmt : m_fs) + item_append(fmt.m_description, fmt.m_name, 0, (void *)(uintptr_t)(id++)); } @@ -279,15 +376,16 @@ void menu_select_format::populate(float &customtop, float &custombottom) // handle //------------------------------------------------- -void menu_select_format::handle() +bool menu_select_floppy_init::handle(event const *ev) { // process the menu - const event *event = process(0); - if (event != nullptr && event->iptkey == IPT_UI_SELECT) + if (ev && ev->iptkey == IPT_UI_SELECT) { - *m_result = int(uintptr_t(event->itemref)); + *m_result = int(uintptr_t(ev->itemref)); stack_pop(); } + + return false; } diff --git a/src/frontend/mame/ui/filecreate.h b/src/frontend/mame/ui/filecreate.h index a97b4f53ef5..aeb6bcfb159 100644 --- a/src/frontend/mame/ui/filecreate.h +++ b/src/frontend/mame/ui/filecreate.h @@ -15,22 +15,26 @@ #include "ui/menu.h" +#include "imagedev/floppy.h" + + class floppy_image_format_t; namespace ui { + // ======================> menu_confirm_save_as class menu_confirm_save_as : public menu { public: - menu_confirm_save_as(mame_ui_manager &mui, render_container &container, bool *yes); + menu_confirm_save_as(mame_ui_manager &mui, render_container &container, bool &yes); virtual ~menu_confirm_save_as() 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; - bool *m_yes; + bool &m_yes; }; @@ -43,11 +47,13 @@ public: virtual ~menu_file_create() override; protected: - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) override; + virtual bool custom_ui_back() 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; bool & m_ok; device_image_interface * m_image; @@ -63,18 +69,35 @@ class menu_select_format : public menu { public: menu_select_format(mame_ui_manager &mui, render_container &container, - floppy_image_format_t **formats, int ext_match, int total_usable, int *result); + const std::vector<const floppy_image_format_t *> &formats, int ext_match, const floppy_image_format_t **result); virtual ~menu_select_format() 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; + + // internal state + std::vector<const floppy_image_format_t *> m_formats; + int m_ext_match; + const floppy_image_format_t * *m_result; +}; + +// ======================> menu_select_floppy_init + +class menu_select_floppy_init : public menu +{ +public: + menu_select_floppy_init(mame_ui_manager &mui, render_container &container, + std::vector<std::reference_wrapper<const floppy_image_device::fs_info>> &&fs, int *result); + virtual ~menu_select_floppy_init() override; + +private: + virtual void populate() override; + virtual bool handle(event const *ev) override; // internal state - floppy_image_format_t ** m_formats; - int m_ext_match; - int m_total_usable; - int * m_result; + std::vector<std::reference_wrapper<const floppy_image_device::fs_info>> m_fs; + int * m_result; }; diff --git a/src/frontend/mame/ui/filemngr.cpp b/src/frontend/mame/ui/filemngr.cpp index cb3243e0544..abbe2e29a8b 100644 --- a/src/frontend/mame/ui/filemngr.cpp +++ b/src/frontend/mame/ui/filemngr.cpp @@ -12,17 +12,24 @@ *********************************************************************/ #include "emu.h" -#include "ui/ui.h" -#include "ui/menu.h" #include "ui/filemngr.h" + #include "ui/filesel.h" -#include "ui/miscmenu.h" -#include "ui/imgcntrl.h" #include "ui/floppycntrl.h" +#include "ui/imgcntrl.h" +#include "ui/prscntrl.h" +#include "ui/miscmenu.h" +#include "ui/ui.h" + #include "softlist.h" +#include <string_view> +#include <unordered_set> +#include <utility> + namespace ui { + /*************************************************************************** FILE MANAGER ***************************************************************************/ @@ -31,16 +38,14 @@ namespace ui { // ctor //------------------------------------------------- -menu_file_manager::menu_file_manager(mame_ui_manager &mui, render_container &container, const char *warnings) : menu(mui, container), selected_device(nullptr) +menu_file_manager::menu_file_manager(mame_ui_manager &mui, render_container &container, std::string &&warnings) + : menu(mui, container) + , m_warnings(std::move(warnings)) + , m_selected_device(nullptr) { - // This warning string is used when accessing from the force_file_manager call, i.e. + // The warning string is used when accessing from the force_file_manager call, i.e. // when the file manager is loaded top front in the case of mandatory image devices - if (warnings) - m_warnings.assign(warnings); - else - m_warnings.clear(); - - m_curr_selected = false; + set_heading(_("File Manager")); } @@ -54,111 +59,166 @@ menu_file_manager::~menu_file_manager() //------------------------------------------------- -// custom_render - perform our special rendering +// recompute_metrics - recompute metrics //------------------------------------------------- -void menu_file_manager::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_file_manager::recompute_metrics(uint32_t width, uint32_t height, float aspect) { - const char *path; + menu::recompute_metrics(width, height, aspect); + + if (!m_warnings.empty()) + { + m_warnings_layout.reset(); + + float const max_width(1.0F - (4.0F * lr_border())); + m_warnings_layout.emplace(create_layout(max_width, text_layout::text_justify::LEFT)); + m_warnings_layout->add_text(m_warnings, ui().colors().text_color()); + + set_custom_space(0.0F, (float(m_warnings_layout->lines() + 1) * line_height()) + (6.0F * tb_border())); + } + else + { + set_custom_space(0.0F, line_height() + (3.0F * tb_border())); + } +} + +//------------------------------------------------- +// custom_render - perform our special rendering +//------------------------------------------------- + +void menu_file_manager::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +{ // access the path - path = selected_device ? selected_device->filename() : nullptr; - extra_text_render(top, bottom, origx1, origy1, origx2, origy2, nullptr, path); + if (m_selected_device && m_selected_device->exists()) + extra_text_render(top, (3.0F * tb_border()) + line_height(), origx1, origy1, origx2, origy2, std::string_view(), m_selected_device->filename()); + + // show the warnings if any + if (m_warnings_layout) + { + ui().draw_outlined_box( + container(), + ((1.0F + m_warnings_layout->actual_width()) * 0.5F) + lr_border(), origy2 + (4.0F * tb_border()) + line_height(), + ((1.0F - m_warnings_layout->actual_width()) * 0.5F) - lr_border(), origy2 + bottom, + ui().colors().background_color()); + m_warnings_layout->emit( + container(), + (1.0F - m_warnings_layout->actual_width()) * 0.5F, + origy2 + (5.0F * tb_border()) + line_height()); + } + } -void menu_file_manager::fill_image_line(device_image_interface *img, std::string &instance, std::string &filename) +void menu_file_manager::fill_image_line(device_image_interface &img, std::string &instance, std::string &filename) { // get the image type/id - instance = string_format("%s (%s)", img->instance_name(), img->brief_instance_name()); + instance = string_format("%s (%s)", img.instance_name(), img.brief_instance_name()); // get the base name - if (img->basename() != nullptr) + if (img.basename()) { - filename.assign(img->basename()); + filename.assign(img.basename()); // if the image has been loaded through softlist, also show the loaded part - if (img->loaded_through_softlist()) + if (img.loaded_through_softlist()) { - const software_part *tmp = img->part_entry(); + const software_part *tmp = img.part_entry(); if (!tmp->name().empty()) { filename.append(" ("); filename.append(tmp->name()); // also check if this part has a specific part_id (e.g. "Map Disc", "Bonus Disc", etc.), and in case display it - if (img->get_feature("part_id") != nullptr) + if (img.get_feature("part_id") != nullptr) { filename.append(": "); - filename.append(img->get_feature("part_id")); + filename.append(img.get_feature("part_id")); } filename.append(")"); } } } else + { filename.assign("---"); + } } //------------------------------------------------- // populate //------------------------------------------------- -void menu_file_manager::populate(float &customtop, float &custombottom) +void menu_file_manager::populate() { - std::string tmp_inst, tmp_name; - bool first_entry = true; - - if (!m_warnings.empty()) - { - item_append(m_warnings, "", FLAG_DISABLE, nullptr); - item_append("", "", FLAG_DISABLE, nullptr); - } + m_notifiers.clear(); // cycle through all devices for this system + bool missing_mandatory = false; std::unordered_set<std::string> devtags; - for (device_t &dev : device_iterator(machine().root_device())) + std::string tmp_inst, tmp_name; + for (device_t &dev : device_enumerator(machine().root_device())) { bool tag_appended = false; if (!devtags.insert(dev.tag()).second) continue; // check whether it owns an image interface - image_interface_iterator subiter(dev); + image_interface_enumerator subiter(dev); if (subiter.first() != nullptr) { // if so, cycle through all its image interfaces for (device_image_interface &scan : subiter) { - if (!scan.user_loadable()) - continue; - - // if it is a children device, and not something further down the device tree, we want it in the menu! - if (strcmp(scan.device().owner()->tag(), dev.tag()) == 0) + if (scan.has_preset_images_selection()) + { if (devtags.insert(scan.device().tag()).second) { - // check whether we already had some devices with the same owner: if not, output the owner tag! - if (!tag_appended) + item_append(string_format(_("[root%1$s]"), scan.device().owner()->tag()), FLAG_UI_HEADING | FLAG_DISABLE, nullptr); + int const index = item_append(scan.image_type_name(), scan.preset_images_list()[scan.current_preset_image_id()], 0, (void *)&scan); + m_notifiers.emplace_back(scan.add_media_change_notifier( + [this, index, &scan] (device_image_interface::media_change_event ev) + { + item(index).set_subtext(scan.preset_images_list()[scan.current_preset_image_id()]); + })); + } + } + else if (scan.user_loadable()) + { + // if it is a child device, and not something further down the device tree, we want it in the menu! + if (!strcmp(scan.device().owner()->tag(), dev.tag())) + { + if (devtags.insert(scan.device().tag()).second) { - if (first_entry) - first_entry = false; - else - item_append(menu_item_type::SEPARATOR); - item_append(string_format("[root%s]", dev.tag()), "", 0, nullptr); - tag_appended = true; + if (!scan.basename() && scan.must_be_loaded()) + missing_mandatory = true; + + // check whether we already had some devices with the same owner: if not, output the owner tag! + if (!tag_appended) + { + item_append(string_format(_("[root%1$s]"), dev.tag()), FLAG_UI_HEADING | FLAG_DISABLE, nullptr); + tag_appended = true; + } + + // finally, append the image interface to the menu + fill_image_line(scan, tmp_inst, tmp_name); + int const index = item_append(std::move(tmp_inst), std::move(tmp_name), 0, (void *)&scan); + m_notifiers.emplace_back(scan.add_media_change_notifier( + [this, index, &scan] (device_image_interface::media_change_event ev) + { + std::string text, subtext; + fill_image_line(scan, text, subtext); + item(index).set_subtext(std::move(subtext)); + })); } - // finally, append the image interface to the menu - fill_image_line(&scan, tmp_inst, tmp_name); - item_append(tmp_inst, tmp_name, 0, (void *)&scan); } + } } } } item_append(menu_item_type::SEPARATOR); - if (m_warnings.empty() || m_curr_selected) - item_append("Reset", "", 0, (void *)1); - - custombottom = ui().get_line_height() + 3.0f * ui().box_tb_border(); + if (m_warnings.empty() || !missing_mandatory) + item_append(m_warnings.empty() ? _("Reset System") : _("Start System"), 0, (void *)1); } @@ -166,49 +226,58 @@ void menu_file_manager::populate(float &customtop, float &custombottom) // handle //------------------------------------------------- -void menu_file_manager::handle() +bool menu_file_manager::handle(event const *ev) { - // process the menu - const event *event = process(0); - if (event != nullptr && event->itemref != nullptr && event->iptkey == IPT_UI_SELECT) + bool result = false; + + if (ev) { - if ((uintptr_t)event->itemref == 1) + if ((uintptr_t)ev->itemref == 1) { - machine().schedule_hard_reset(); + if (m_selected_device) + { + m_selected_device = nullptr; + result = true; + } + + if (IPT_UI_SELECT == ev->iptkey) + machine().schedule_hard_reset(); } else { - selected_device = (device_image_interface *) event->itemref; - if (selected_device != nullptr) + if (ev->itemref != m_selected_device) + { + m_selected_device = (device_image_interface *)ev->itemref; + result = true; + } + + if (m_selected_device && (IPT_UI_SELECT == ev->iptkey)) { - m_curr_selected = true; - floppy_image_device *floppy_device = dynamic_cast<floppy_image_device *>(selected_device); - if (floppy_device != nullptr) + if (m_selected_device->has_preset_images_selection()) { - menu::stack_push<menu_control_floppy_image>(ui(), container(), *floppy_device); + menu::stack_push<menu_control_device_preset>(ui(), container(), *m_selected_device); } else { - menu::stack_push<menu_control_device_image>(ui(), container(), *selected_device); + floppy_image_device *floppy_device = dynamic_cast<floppy_image_device *>(m_selected_device); + if (floppy_device) + menu::stack_push<menu_control_floppy_image>(ui(), container(), *floppy_device); + else + menu::stack_push<menu_control_device_image>(ui(), container(), *m_selected_device); } - // reset the existing menu - reset(reset_options::REMEMBER_POSITION); } } } + + return result; } // force file manager menu -void menu_file_manager::force_file_manager(mame_ui_manager &mui, render_container &container, const char *warnings) +void menu_file_manager::force_file_manager(mame_ui_manager &mui, render_container &container, std::string &&warnings) { - // reset the menu stack - menu::stack_reset(mui.machine()); - - // add the quit entry followed by the game select entry - menu::stack_push_special_main<menu_quit_game>(mui, container); - menu::stack_push<menu_file_manager>(mui, container, warnings); - - // force the menus on + // drop any existing menus and start the file manager + menu::stack_reset(mui); + menu::stack_push_special_main<menu_file_manager>(mui, container, std::move(warnings)); mui.show_menu(); // make sure MAME is paused diff --git a/src/frontend/mame/ui/filemngr.h b/src/frontend/mame/ui/filemngr.h index 68da54f1d05..2c303d2cc97 100644 --- a/src/frontend/mame/ui/filemngr.h +++ b/src/frontend/mame/ui/filemngr.h @@ -7,38 +7,47 @@ MESS's clunky built-in file manager ***************************************************************************/ +#ifndef MAME_FRONTEND_UI_FILEMNGR_H +#define MAME_FRONTEND_UI_FILEMNGR_H #pragma once -#ifndef MAME_FRONTEND_UI_FILEMNGR_H -#define MAME_FRONTEND_UI_FILEMNGR_H +#include "ui/menu.h" +#include "ui/text.h" + +#include "notifier.h" + +#include <optional> +#include <string> +#include <vector> + namespace ui { + class menu_file_manager : public menu { public: - std::string current_directory; - std::string current_file; - device_image_interface *selected_device; - - static void force_file_manager(mame_ui_manager &mui, render_container &container, const char *warnings); + static void force_file_manager(mame_ui_manager &mui, render_container &container, std::string &&warnings); - menu_file_manager(mame_ui_manager &mui, render_container &container, const char *warnings); + menu_file_manager(mame_ui_manager &mui, render_container &container, std::string &&warnings); virtual ~menu_file_manager(); protected: - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) 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; - void fill_image_line(device_image_interface *img, std::string &instance, std::string &filename); + void fill_image_line(device_image_interface &img, std::string &instance, std::string &filename); - std::string m_warnings; - bool m_curr_selected; + std::string const m_warnings; + std::vector<util::notifier_subscription> m_notifiers; + std::optional<text_layout> m_warnings_layout; + device_image_interface *m_selected_device; }; } // namespace ui -#endif /* MAME_FRONTEND_UI_FILEMNGR_H */ +#endif // MAME_FRONTEND_UI_FILEMNGR_H diff --git a/src/frontend/mame/ui/filesel.cpp b/src/frontend/mame/ui/filesel.cpp index 49136d04f64..ac780c60d31 100644 --- a/src/frontend/mame/ui/filesel.cpp +++ b/src/frontend/mame/ui/filesel.cpp @@ -12,19 +12,27 @@ ***************************************************************************/ #include "emu.h" - #include "ui/filesel.h" + #include "ui/ui.h" #include "ui/utils.h" #include "imagedev/floppy.h" -#include "zippath.h" +#include "uiinput.h" + +#include "util/corestr.h" +#include "util/zippath.h" + +#include "bus/midi/midiinport.h" +#include "bus/midi/midioutport.h" #include <cstring> #include <locale> + namespace ui { + /*************************************************************************** CONSTANTS ***************************************************************************/ @@ -45,17 +53,30 @@ namespace ui { // ctor //------------------------------------------------- -menu_file_selector::menu_file_selector(mame_ui_manager &mui, render_container &container, device_image_interface *image, std::string ¤t_directory, std::string ¤t_file, bool has_empty, bool has_softlist, bool has_create, menu_file_selector::result &result) +menu_file_selector::menu_file_selector( + mame_ui_manager &mui, + render_container &container, + device_image_interface *image, + std::string_view directory, + std::string_view file, + bool has_empty, + bool has_softlist, + bool has_create, + handler_function &&handler) : menu(mui, container) + , m_handler(std::move(handler)) , m_image(image) - , m_current_directory(current_directory) - , m_current_file(current_file) + , m_current_directory(directory) + , m_current_file(file) + , m_result(result::INVALID) , m_has_empty(has_empty) , m_has_softlist(has_softlist) , m_has_create(has_create) - , m_result(result) + , m_is_midi(image->device().type() == MIDIIN || image->device().type() == MIDIOUT) + , m_clicked_directory(std::string::npos, std::string::npos) { (void)m_image; + set_process_flags(PROCESS_IGNOREPAUSE); } @@ -65,6 +86,23 @@ menu_file_selector::menu_file_selector(mame_ui_manager &mui, render_container &c menu_file_selector::~menu_file_selector() { + if (m_handler) + m_handler(m_result, std::move(m_current_directory), std::move(m_current_file)); +} + + +//------------------------------------------------- +// recompute_metrics - recompute metrics +//------------------------------------------------- + +void menu_file_selector::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + menu::recompute_metrics(width, height, aspect); + + m_path_layout.reset(); + m_clicked_directory = std::make_pair(std::string::npos, std::string::npos); + + set_custom_space(line_height() + 3.0F * tb_border(), 0.0F); } @@ -72,102 +110,131 @@ menu_file_selector::~menu_file_selector() // custom_render - perform our special rendering //------------------------------------------------- -void menu_file_selector::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_file_selector::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { // lay out extra text - auto layout = ui().create_layout(container()); - layout.add_text(m_current_directory.c_str()); + if (!m_path_layout) + { + m_path_layout.emplace(create_layout()); + m_path_layout->add_text(m_current_directory); + } + else + { + rgb_t const fgcolor = ui().colors().text_color(); + rgb_t const bgcolor = rgb_t::transparent(); + m_path_layout->restyle(0, m_current_directory.length(), &fgcolor, &bgcolor); + } // position this extra text - float x1, y1, x2, y2; - extra_text_position(origx1, origx2, origy1, top, layout, -1, x1, y1, x2, y2); + float x2, y2; + extra_text_position(origx1, origx2, origy1, top, *m_path_layout, -1, m_path_position.first, m_path_position.second, x2, y2); // draw a box - ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color()); + ui().draw_outlined_box(container(), m_path_position.first, m_path_position.second, x2, y2, ui().colors().background_color()); // take off the borders - x1 += ui().box_lr_border(); - y1 += ui().box_tb_border(); + m_path_position.first += lr_border(); + m_path_position.second += tb_border(); - size_t hit_start = 0, hit_span = 0; - if (is_mouse_hit() - && layout.hit_test(get_mouse_x() - x1, get_mouse_y() - y1, hit_start, hit_span) - && m_current_directory.substr(hit_start, hit_span) != PATH_SEPARATOR) + if (m_clicked_directory.second > m_clicked_directory.first) { - // we're hovering over a directory! highlight it - auto target_dir_start = m_current_directory.rfind(PATH_SEPARATOR, hit_start) + 1; - auto target_dir_end = m_current_directory.find(PATH_SEPARATOR, hit_start + hit_span); - m_hover_directory = m_current_directory.substr(0, target_dir_end + strlen(PATH_SEPARATOR)); - - // highlight the text in question - rgb_t fgcolor = ui().colors().mouseover_color(); - rgb_t bgcolor = ui().colors().mouseover_bg_color(); - layout.restyle(target_dir_start, target_dir_end - target_dir_start, &fgcolor, &bgcolor); + // see if it's still over the clicked path component + auto const [x, y] = pointer_location(); + size_t start = 0, span = 0; + if (m_path_layout->hit_test(x - m_path_position.first, y - m_path_position.second, start, span)) + { + if ((start >= m_clicked_directory.first) && ((start + span) <= m_clicked_directory.second)) + { + rgb_t const fgcolor = ui().colors().selected_color(); + rgb_t const bgcolor = ui().colors().selected_bg_color(); + m_path_layout->restyle(m_clicked_directory.first, m_clicked_directory.second - m_clicked_directory.first, &fgcolor, &bgcolor); + } + } } - else + else if (pointer_idle()) { - // we are not hovering over anything - m_hover_directory.clear(); + // see if it's hovering over a path component + auto const [x, y] = pointer_location(); + auto const [target_dir_start, target_dir_end] = get_directory_range(x, y); + if (target_dir_end > target_dir_start) + { + rgb_t const fgcolor = ui().colors().mouseover_color(); + rgb_t const bgcolor = ui().colors().mouseover_bg_color(); + m_path_layout->restyle(target_dir_start, target_dir_end - target_dir_start, &fgcolor, &bgcolor); + } } // draw the text within it - layout.emit(container(), x1, y1); + m_path_layout->emit(container(), m_path_position.first, m_path_position.second); } //------------------------------------------------- -// custom_mouse_down - perform our special mouse down +// custom_pointer_updated - perform our special +// pointer handling //------------------------------------------------- -bool menu_file_selector::custom_mouse_down() +std::tuple<int, bool, bool> menu_file_selector::custom_pointer_updated(bool changed, ui_event const &uievt) { - if (m_hover_directory.length() > 0) + // track pointer after clicking a path component + if (m_clicked_directory.second > m_clicked_directory.first) { - m_current_directory = m_hover_directory; - reset(reset_options::SELECT_FIRST); - return true; + if (ui_event::type::POINTER_ABORT == uievt.event_type) + { + // abort always cancels + m_clicked_directory = std::make_pair(std::string::npos, std::string::npos); + return std::make_tuple(IPT_INVALID, false, true); + } + else if (uievt.pointer_released & 0x01) + { + // releasing the primary button - check for dragging out + auto const [x, y] = pointer_location(); + size_t start = 0, span = 0; + if (m_path_layout->hit_test(x - m_path_position.first, y - m_path_position.second, start, span)) + { + // abuse IPT_CUSTOM to change to the clicked directory + if ((start >= m_clicked_directory.first) && ((start + span) <= m_clicked_directory.second)) + return std::make_tuple(IPT_CUSTOM, false, true); + } + m_clicked_directory = std::make_pair(std::string::npos, std::string::npos); + return std::make_tuple(IPT_INVALID, false, true); + } + else if (uievt.pointer_buttons & ~u32(1)) + { + // pressing more buttons cancels + m_clicked_directory = std::make_pair(std::string::npos, std::string::npos); + return std::make_tuple(IPT_INVALID, false, true); + } + else + { + // keep tracking the pointer + return std::make_tuple(IPT_INVALID, true, false); + } } - return false; + // check for clicks if we have up-to-date content on-screen + if (m_path_layout && pointer_idle() && (uievt.pointer_buttons & 0x01) && !(uievt.pointer_buttons & ~u32(0x01))) + { + auto const [x, y] = pointer_location(); + auto const [target_dir_start, target_dir_end] = get_directory_range(x, y); + if (target_dir_end > target_dir_start) + { + m_clicked_directory = std::make_pair(target_dir_start, target_dir_end); + return std::make_tuple(IPT_INVALID, true, true); + } + } + + return std::make_tuple(IPT_INVALID, false, false); } //------------------------------------------------- -// compare_file_selector_entries - sorting proc -// for file selector entries +// menu_activated - menu has gained focus //------------------------------------------------- -int menu_file_selector::compare_entries(const file_selector_entry *e1, const file_selector_entry *e2) +void menu_file_selector::menu_activated() { - int result; - const char *e1_basename = e1->basename.c_str(); - const char *e2_basename = e2->basename.c_str(); - - if (e1->type < e2->type) - { - result = -1; - } - else if (e1->type > e2->type) - { - result = 1; - } - else - { - result = core_stricmp(e1_basename, e2_basename); - if (result == 0) - { - result = strcmp(e1_basename, e2_basename); - if (result == 0) - { - if (e1 < e2) - result = -1; - else if (e1 > e2) - result = 1; - } - } - } - - return result; + m_clicked_directory = std::make_pair(std::string::npos, std::string::npos); } @@ -202,7 +269,7 @@ menu_file_selector::file_selector_entry &menu_file_selector::append_entry( entry.fullpath = std::move(entry_fullpath); // find the end of the list - return *m_entrylist.emplace(m_entrylist.end(), std::move(entry)); + return m_entrylist.emplace_back(std::move(entry)); } @@ -256,6 +323,10 @@ void menu_file_selector::append_entry_menu_item(const file_selector_entry *entry text = _("[empty slot]"); break; + case SELECTOR_ENTRY_TYPE_MIDI: + text = _("[midi port]"); + break; + case SELECTOR_ENTRY_TYPE_CREATE: text = _("[create]"); break; @@ -297,6 +368,12 @@ void menu_file_selector::select_item(const file_selector_entry &entry) stack_pop(); break; + case SELECTOR_ENTRY_TYPE_MIDI: + // create + m_result = result::MIDI; + stack_pop(); + break; + case SELECTOR_ENTRY_TYPE_CREATE: // create m_result = result::CREATE; @@ -310,18 +387,20 @@ void menu_file_selector::select_item(const file_selector_entry &entry) case SELECTOR_ENTRY_TYPE_DRIVE: case SELECTOR_ENTRY_TYPE_DIRECTORY: - // drive/directory - first check the path { + // drive/directory - first check the path util::zippath_directory::ptr dir; - osd_file::error const err = util::zippath_directory::open(entry.fullpath, dir); - if (err != osd_file::error::NONE) + std::error_condition const err = util::zippath_directory::open(entry.fullpath, dir); + if (err) { // this path is problematic; present the user with an error and bail ui().popup_time(1, _("Error accessing %s"), entry.fullpath); break; } } - m_current_directory.assign(entry.fullpath); + m_current_directory = entry.fullpath; + m_path_layout.reset(); + m_clicked_directory = std::make_pair(std::string::npos, std::string::npos); reset(reset_options::SELECT_FIRST); break; @@ -336,74 +415,102 @@ void menu_file_selector::select_item(const file_selector_entry &entry) //------------------------------------------------- -// type_search_char +// update_search //------------------------------------------------- -void menu_file_selector::type_search_char(char32_t ch) +void menu_file_selector::update_search() { - std::string const current(m_filename); - if (input_character(m_filename, ch, uchar_is_printable)) - { - ui().popup_time(ERROR_MESSAGE_TIME, "%s", m_filename.c_str()); + ui().popup_time(ERROR_MESSAGE_TIME, "%s", m_filename); - file_selector_entry const *const cur_selected(reinterpret_cast<file_selector_entry const *>(get_selection_ref())); + file_selector_entry const *const cur_selected(reinterpret_cast<file_selector_entry const *>(get_selection_ref())); - // if it's a perfect match for the current selection, don't move it - if (!cur_selected || core_strnicmp(cur_selected->basename.c_str(), m_filename.c_str(), m_filename.size())) + // if it's a perfect match for the current selection, don't move it + if (!cur_selected || core_strnicmp(cur_selected->basename.c_str(), m_filename.c_str(), m_filename.size())) + { + std::string::size_type bestmatch(0); + file_selector_entry const *selected_entry(cur_selected); + for (auto &entry : m_entrylist) { - std::string::size_type bestmatch(0); - file_selector_entry const *selected_entry(cur_selected); - for (auto &entry : m_entrylist) + // TODO: more efficient "common prefix" code + std::string::size_type match(0); + for (std::string::size_type i = 1; m_filename.size() >= i; ++i) { - // TODO: more efficient "common prefix" code - std::string::size_type match(0); - for (std::string::size_type i = 1; m_filename.size() >= i; ++i) - { - if (!core_strnicmp(entry.basename.c_str(), m_filename.c_str(), i)) - match = i; - else - break; - } - - if (match > bestmatch) - { - bestmatch = match; - selected_entry = &entry; - } + if (!core_strnicmp(entry.basename.c_str(), m_filename.c_str(), i)) + match = i; + else + break; } - if (selected_entry && (selected_entry != cur_selected)) + if (match > bestmatch) { - set_selection((void *)selected_entry); - centre_selection(); + bestmatch = match; + selected_entry = &entry; } } + + if (selected_entry && (selected_entry != cur_selected)) + { + set_selection((void *)selected_entry); + centre_selection(); + } } } //------------------------------------------------- +// get_directory_range +//------------------------------------------------- + +std::pair<size_t, size_t> menu_file_selector::get_directory_range(float x, float y) +{ + size_t start = 0, span = 0; + if (m_path_layout->hit_test(x - m_path_position.first, y - m_path_position.second, start, span)) + { + if (std::string_view(m_current_directory).substr(start, span) != PATH_SEPARATOR) + { + auto target_start = m_current_directory.rfind(PATH_SEPARATOR, start); + if (std::string::npos == target_start) + target_start = 0; + else + target_start += 1; + + auto target_end = m_current_directory.find(PATH_SEPARATOR, start + span); + if (std::string::npos == target_end) + target_end = m_current_directory.length(); + + return std::make_pair(target_start, target_end); + } + } + + return std::make_pair(std::string::npos, std::string::npos); +} + + +//------------------------------------------------- // populate //------------------------------------------------- -void menu_file_selector::populate(float &customtop, float &custombottom) +void menu_file_selector::populate() { const file_selector_entry *selected_entry = nullptr; - // clear out the menu entries m_entrylist.clear(); // open the directory util::zippath_directory::ptr directory; - osd_file::error const err = util::zippath_directory::open(m_current_directory, directory); + std::error_condition const err = util::zippath_directory::open(m_current_directory, directory); // add the "[empty slot]" entry if available if (m_has_empty) append_entry(SELECTOR_ENTRY_TYPE_EMPTY, "", ""); + // add the "[midi port]" entry if available + if (m_is_midi) + append_entry(SELECTOR_ENTRY_TYPE_MIDI, "", ""); + // add the "[create]" entry - if (m_has_create && !directory->is_archive()) + if (m_has_create && directory && !directory->is_archive()) append_entry(SELECTOR_ENTRY_TYPE_CREATE, "", ""); // add and select the "[software list]" entry if available @@ -411,17 +518,18 @@ void menu_file_selector::populate(float &customtop, float &custombottom) selected_entry = &append_entry(SELECTOR_ENTRY_TYPE_SOFTWARE_LIST, "", ""); // add the drives - int i = 0; - for (char const *volume_name = osd_get_volume_name(i); volume_name; volume_name = osd_get_volume_name(++i)) + for (std::string const &volume_name : osd_get_volume_names()) append_entry(SELECTOR_ENTRY_TYPE_DRIVE, volume_name, volume_name); // mark first filename entry std::size_t const first = m_entrylist.size() + 1; // build the menu for each item - if (osd_file::error::NONE != err) + if (err) { - osd_printf_verbose("menu_file_selector::populate: error opening directory '%s' (%d)\n", m_current_directory.c_str(), int(err)); + osd_printf_verbose( + "menu_file_selector::populate: error opening directory '%s' (%s:%d %s)\n", + m_current_directory, err.category().name(), err.value(), err.message()); } else { @@ -436,24 +544,28 @@ void menu_file_selector::populate(float &customtop, float &custombottom) selected_entry = entry; // do we have to select this file? - if (!core_stricmp(m_current_file.c_str(), dirent->name)) + if (!core_stricmp(m_current_file, dirent->name)) selected_entry = entry; } } } directory.reset(); - // sort the menu entries - const std::collate<wchar_t> &coll = std::use_facet<std::collate<wchar_t>>(std::locale()); - std::sort( - m_entrylist.begin() + first, - m_entrylist.end(), - [&coll] (file_selector_entry const &x, file_selector_entry const &y) - { - std::wstring const xstr = wstring_from_utf8(x.basename); - std::wstring const ystr = wstring_from_utf8(y.basename); - return coll.compare(xstr.data(), xstr.data()+xstr.size(), ystr.data(), ystr.data()+ystr.size()) < 0; - }); + if (m_entrylist.size() > first) + { + // sort the menu entries + std::locale const lcl; + std::collate<wchar_t> const &coll = std::use_facet<std::collate<wchar_t> >(lcl); + std::sort( + m_entrylist.begin() + first, + m_entrylist.end(), + [&coll] (file_selector_entry const &x, file_selector_entry const &y) + { + std::wstring const xstr = wstring_from_utf8(x.basename); + std::wstring const ystr = wstring_from_utf8(y.basename); + return coll.compare(xstr.data(), xstr.data() + xstr.size(), ystr.data(), ystr.data() + ystr.size()) < 0; + }); + } // append all of the menu entries for (file_selector_entry const &entry : m_entrylist) @@ -462,9 +574,6 @@ void menu_file_selector::populate(float &customtop, float &custombottom) // set the selection (if we have one) if (selected_entry) set_selection((void *)selected_entry); - - // set up custom render proc - customtop = ui().get_line_height() + 3.0f * ui().box_tb_border(); } @@ -472,31 +581,61 @@ void menu_file_selector::populate(float &customtop, float &custombottom) // handle //------------------------------------------------- -void menu_file_selector::handle() +bool menu_file_selector::handle(event const *ev) { - // process the menu - event const *const event = process(0); - if (event && event->itemref) + if (!ev) + return false; + + if (ev->iptkey == IPT_SPECIAL) { - // handle selections - if (event->iptkey == IPT_UI_SELECT) + // if it's any other key and we're not maxed out, update + if (input_character(m_filename, ev->unichar, uchar_is_printable)) { - select_item(*reinterpret_cast<file_selector_entry const *>(event->itemref)); - - // reset the char buffer when pressing IPT_UI_SELECT - m_filename.clear(); + update_search(); + return true; } - else if (event->iptkey == IPT_SPECIAL) + } + else if (ev->iptkey == IPT_UI_PASTE) + { + if (paste_text(m_filename, uchar_is_printable)) { - // if it's any other key and we're not maxed out, update - type_search_char(event->unichar); + update_search(); + return true; } - else if (event->iptkey == IPT_UI_CANCEL) + } + else if (ev->iptkey == IPT_UI_CANCEL) + { + // reset the char buffer also in this case + if (!m_filename.empty()) { - // reset the char buffer also in this case m_filename.clear(); + ui().popup_time(ERROR_MESSAGE_TIME, "%s", m_filename); + return true; } } + else if (ev->iptkey == IPT_CUSTOM) + { + // clicked a path component + if (m_clicked_directory.second > m_clicked_directory.first) + { + m_current_directory.resize(m_clicked_directory.second + strlen(PATH_SEPARATOR)); + m_path_layout.reset(); + m_clicked_directory = std::make_pair(std::string::npos, std::string::npos); + reset(reset_options::SELECT_FIRST); + return true; + } + } + else if (ev->itemref && (ev->iptkey == IPT_UI_SELECT)) + { + // handle selections + select_item(*reinterpret_cast<file_selector_entry const *>(ev->itemref)); + + // reset the char buffer when pressing IPT_UI_SELECT + m_filename.clear(); + return true; + } + + return false; } @@ -509,11 +648,15 @@ void menu_file_selector::handle() // ctor //------------------------------------------------- -menu_select_rw::menu_select_rw(mame_ui_manager &mui, render_container &container, - bool can_in_place, result &result) - : menu(mui, container), - m_can_in_place(can_in_place), - m_result(result) +menu_select_rw::menu_select_rw( + mame_ui_manager &mui, + render_container &container, + bool can_in_place, + handler_function &&handler) + : menu(mui, container) + , m_handler(std::move(handler)) + , m_can_in_place(can_in_place) + , m_result(result::INVALID) { } @@ -524,6 +667,8 @@ menu_select_rw::menu_select_rw(mame_ui_manager &mui, render_container &container menu_select_rw::~menu_select_rw() { + if (m_handler) + m_handler(m_result); } @@ -531,14 +676,15 @@ menu_select_rw::~menu_select_rw() // populate //------------------------------------------------- -void menu_select_rw::populate(float &customtop, float &custombottom) +void menu_select_rw::populate() { - item_append(_("Select access mode"), "", FLAG_DISABLE, nullptr); - item_append(_("Read-only"), "", 0, itemref_from_result(result::READONLY)); + set_heading(_("Select access mode")); + + item_append(_("Read-only"), 0, itemref_from_result(result::READONLY)); if (m_can_in_place) - item_append(_("Read-write"), "", 0, itemref_from_result(result::READWRITE)); - item_append(_("Read this image, write to another image"), "", 0, itemref_from_result(result::WRITE_OTHER)); - item_append(_("Read this image, write to diff"), "", 0, itemref_from_result(result::WRITE_DIFF)); + item_append(_("Read-write"), 0, itemref_from_result(result::READWRITE)); + item_append(_("Read this image, write to another image"), 0, itemref_from_result(result::WRITE_OTHER)); + item_append(_("Read this image, write to diff"), 0, itemref_from_result(result::WRITE_DIFF)); } @@ -546,15 +692,15 @@ void menu_select_rw::populate(float &customtop, float &custombottom) // handle //------------------------------------------------- -void menu_select_rw::handle() +bool menu_select_rw::handle(event const *ev) { - // process the menu - const event *event = process(0); - if (event != nullptr && event->iptkey == IPT_UI_SELECT) + if (ev && ev->iptkey == IPT_UI_SELECT) { - m_result = result_from_itemref(event->itemref); + m_result = result_from_itemref(ev->itemref); stack_pop(); } + + return false; } diff --git a/src/frontend/mame/ui/filesel.h b/src/frontend/mame/ui/filesel.h index 768cf1ba55e..653adc77c05 100644 --- a/src/frontend/mame/ui/filesel.h +++ b/src/frontend/mame/ui/filesel.h @@ -15,6 +15,15 @@ #include "ui/menu.h" +#include <functional> +#include <optional> +#include <string> +#include <string_view> +#include <tuple> +#include <utility> +#include <vector> + + namespace ui { // ======================> menu_file_selector @@ -28,29 +37,36 @@ public: EMPTY = 0x1000, SOFTLIST, CREATE, - FILE + FILE, + MIDI }; + using handler_function = std::function<void (result result, std::string &&directory, std::string &&file)>; + menu_file_selector( mame_ui_manager &mui, render_container &container, device_image_interface *image, - std::string ¤t_directory, - std::string ¤t_file, + std::string_view directory, + std::string_view file, bool has_empty, bool has_softlist, bool has_create, - result &result); + handler_function &&handler); virtual ~menu_file_selector() override; protected: - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; - virtual bool custom_mouse_down() override; + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) override; + virtual bool custom_ui_back() override { return !m_filename.empty(); } + virtual std::tuple<int, bool, bool> custom_pointer_updated(bool changed, ui_event const &uievt) override; + virtual void menu_activated() override; private: enum file_selector_entry_type { SELECTOR_ENTRY_TYPE_EMPTY, + SELECTOR_ENTRY_TYPE_MIDI, SELECTOR_ENTRY_TYPE_CREATE, SELECTOR_ENTRY_TYPE_SOFTWARE_LIST, SELECTOR_ENTRY_TYPE_DRIVE, @@ -60,37 +76,42 @@ private: struct file_selector_entry { - file_selector_entry() { } + file_selector_entry() = default; file_selector_entry(file_selector_entry &&) = default; file_selector_entry &operator=(file_selector_entry &&) = default; - file_selector_entry_type type; + + file_selector_entry_type type = SELECTOR_ENTRY_TYPE_EMPTY; std::string basename; std::string fullpath; }; // internal state - device_image_interface *const m_image; - std::string & m_current_directory; - std::string & m_current_file; - bool const m_has_empty; - bool const m_has_softlist; - bool const m_has_create; - result & m_result; + handler_function const m_handler; + device_image_interface *const m_image; + std::string m_current_directory; + std::string m_current_file; + std::optional<text_layout> m_path_layout; + std::pair<float, float> m_path_position; + result m_result; + bool const m_has_empty; + bool const m_has_softlist; + bool const m_has_create; + bool const m_is_midi; std::vector<file_selector_entry> m_entrylist; - std::string m_hover_directory; - std::string m_filename; + std::string m_filename; + std::pair<size_t, size_t> m_clicked_directory; - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; + virtual bool handle(event const *ev) override; // methods - int compare_entries(const file_selector_entry *e1, const file_selector_entry *e2); file_selector_entry &append_entry(file_selector_entry_type entry_type, const std::string &entry_basename, const std::string &entry_fullpath); file_selector_entry &append_entry(file_selector_entry_type entry_type, std::string &&entry_basename, std::string &&entry_fullpath); file_selector_entry *append_dirent_entry(const osd::directory::entry *dirent); void append_entry_menu_item(const file_selector_entry *entry); void select_item(const file_selector_entry &entry); - void type_search_char(char32_t ch); + void update_search(); + std::pair<size_t, size_t> get_directory_range(float x, float y); }; @@ -107,23 +128,27 @@ public: WRITE_OTHER, WRITE_DIFF }; + + using handler_function = std::function<void (result result)>; + menu_select_rw( mame_ui_manager &mui, render_container &container, bool can_in_place, - result &result); + handler_function &&handler); virtual ~menu_select_rw() override; +private: + virtual void populate() override; + virtual bool handle(event const *ev) override; + static void *itemref_from_result(result result); static result result_from_itemref(void *itemref); -private: - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; - // internal state - bool m_can_in_place; - result & m_result; + handler_function const m_handler; + bool const m_can_in_place; + result m_result; }; } // namespace ui diff --git a/src/frontend/mame/ui/floppycntrl.cpp b/src/frontend/mame/ui/floppycntrl.cpp index 1c44458f653..3f6f9bdfb61 100644 --- a/src/frontend/mame/ui/floppycntrl.cpp +++ b/src/frontend/mame/ui/floppycntrl.cpp @@ -12,155 +12,211 @@ #include "ui/filecreate.h" #include "ui/floppycntrl.h" +#include "formats/flopimg.h" +#include "formats/fsmgr.h" + #include "zippath.h" +#include <tuple> + namespace ui { + /*************************************************************************** IMPLEMENTATION ***************************************************************************/ -menu_control_floppy_image::menu_control_floppy_image(mame_ui_manager &mui, render_container &container, device_image_interface &image) : menu_control_device_image(mui, container, image) +menu_control_floppy_image::menu_control_floppy_image(mame_ui_manager &mui, render_container &container, device_image_interface &image) : + menu_control_device_image(mui, container, image), + fd(dynamic_cast<floppy_image_device &>(image)), + input_format(nullptr), + output_format(nullptr), + create_fs(nullptr), + input_filename(), + output_filename() { - floppy_image_device *fd = static_cast<floppy_image_device *>(&m_image); - const floppy_image_format_t *fif_list = fd->get_formats(); - int fcnt = 0; - for(const floppy_image_format_t *i = fif_list; i; i = i->next) - fcnt++; - - format_array = global_alloc_array(floppy_image_format_t *, fcnt); - input_format = output_format = nullptr; - input_filename = output_filename = ""; } menu_control_floppy_image::~menu_control_floppy_image() { - global_free_array(format_array); } void menu_control_floppy_image::do_load_create() { - floppy_image_device *fd = static_cast<floppy_image_device *>(&m_image); - if(input_filename.compare("")==0) { - image_init_result err = fd->create(output_filename, nullptr, nullptr); - if (err != image_init_result::PASS) { - machine().popmessage("Error: %s", fd->error()); + if(input_filename.empty()) { + auto [err, message] = fd.create(output_filename, nullptr, nullptr); + if (err) { + machine().popmessage(_("Error creating floppy image: %1$s"), !message.empty() ? message : err.message()); return; } + if (create_fs) { + // HACK: ensure the floppy_image structure is created since device_image_interface may not otherwise do so during "init phase" + err = fd.finish_load().first; + if (!err) { + fs::meta_data meta; + fd.init_fs(create_fs, meta); + } + } } else { - image_init_result err = fd->load(input_filename); - if ((err == image_init_result::PASS) && (output_filename.compare("") != 0)) - err = fd->reopen_for_write(output_filename) ? image_init_result::FAIL : image_init_result::PASS; - if (err != image_init_result::PASS) { - machine().popmessage("Error: %s", fd->error()); + auto [err, message] = fd.load(input_filename); + if (!err && !output_filename.empty()) { + message.clear(); + err = fd.reopen_for_write(output_filename); + } + if (err) { + machine().popmessage(_("Error opening floppy image: %1$s"), !message.empty() ? message : err.message()); return; } } if(output_format) - fd->setup_write(output_format); + fd.setup_write(output_format); } void menu_control_floppy_image::hook_load(const std::string &filename) { + std::error_condition err; input_filename = filename; - input_format = static_cast<floppy_image_device &>(m_image).identify(filename); + std::tie(err, input_format) = static_cast<floppy_image_device &>(m_image).identify(filename); if (!input_format) { - machine().popmessage("Error: %s\n", m_image.error()); + machine().popmessage("Error: %s", err.message()); stack_pop(); - return; } - - bool can_in_place = input_format->supports_save(); - if(can_in_place) { - osd_file::error filerr; - std::string tmp_path; - util::core_file::ptr tmp_file; - // attempt to open the file for writing but *without* create - filerr = util::zippath_fopen(filename, OPEN_FLAG_READ | OPEN_FLAG_WRITE, tmp_file, tmp_path); - if(filerr == osd_file::error::NONE) - tmp_file.reset(); - else - can_in_place = false; + else + { + bool can_in_place = input_format->supports_save(); + if (can_in_place) + { + std::string tmp_path; + util::core_file::ptr tmp_file; + // attempt to open the file for writing but *without* create + std::error_condition const filerr = util::zippath_fopen(filename, OPEN_FLAG_READ | OPEN_FLAG_WRITE, tmp_file, tmp_path); + if(!filerr) + tmp_file.reset(); + else + can_in_place = false; + } + menu::stack_push<menu_select_rw>( + ui(), + container(), + can_in_place, + [this] (menu_select_rw::result result) + { + switch (result) + { + case menu_select_rw::result::READONLY: + do_load_create(); + fd.setup_write(nullptr); + stack_pop(); + break; + + case menu_select_rw::result::READWRITE: + output_format = input_format; + do_load_create(); + stack_pop(); + break; + + case menu_select_rw::result::WRITE_DIFF: + machine().popmessage("Sorry, diffs are not supported yet\n"); + stack_pop(); + break; + + case menu_select_rw::result::WRITE_OTHER: + menu::stack_push<menu_file_create>(ui(), container(), &m_image, m_current_directory, m_current_file, m_create_ok); + m_state = CHECK_CREATE; + break; + + case menu_select_rw::result::INVALID: + m_state = START_FILE; + break; + } + }); } - m_submenu_result.rw = menu_select_rw::result::INVALID; - menu::stack_push<menu_select_rw>(ui(), container(), can_in_place, m_submenu_result.rw); - m_state = SELECT_RW; } -void menu_control_floppy_image::handle() +bool menu_control_floppy_image::can_format(const floppy_image_device::fs_info &fs) +{ + return !fs.m_manager || fs.m_manager->can_format(); +} + +void menu_control_floppy_image::menu_activated() { - floppy_image_device *fd = static_cast<floppy_image_device *>(&m_image); switch (m_state) { case DO_CREATE: { - floppy_image_format_t *fif_list = fd->get_formats(); - int ext_match; - int total_usable = 0; - for(floppy_image_format_t *i = fif_list; i; i = i->next) { + std::vector<const floppy_image_format_t *> format_array; + for(const floppy_image_format_t *i : fd.get_formats()) { if(!i->supports_save()) continue; if (i->extension_matches(m_current_file.c_str())) - format_array[total_usable++] = i; + format_array.push_back(i); } - ext_match = total_usable; - for(floppy_image_format_t *i = fif_list; i; i = i->next) { + int ext_match = format_array.size(); + for(const floppy_image_format_t *i : fd.get_formats()) { if(!i->supports_save()) continue; if (!i->extension_matches(m_current_file.c_str())) - format_array[total_usable++] = i; + format_array.push_back(i); } - m_submenu_result.i = -1; - menu::stack_push<menu_select_format>(ui(), container(), format_array, ext_match, total_usable, &m_submenu_result.i); + output_format = nullptr; + menu::stack_push<menu_select_format>(ui(), container(), format_array, ext_match, &output_format); m_state = SELECT_FORMAT; break; } case SELECT_FORMAT: - if(m_submenu_result.i == -1) { + if(!output_format) { m_state = START_FILE; - handle(); + menu_activated(); } else { + // get all formatable file systems + std::vector<std::reference_wrapper<const floppy_image_device::fs_info>> fs; + for (const auto &this_fs : fd.get_fs()) { + if (can_format(this_fs)) + fs.emplace_back(std::ref(this_fs)); + } + output_filename = util::zippath_combine(m_current_directory, m_current_file); - output_format = format_array[m_submenu_result.i]; - do_load_create(); - stack_pop(); + if(fs.size() == 1) { + create_fs = &(fs[0].get()); + do_load_create(); + stack_pop(); + } else { + m_submenu_result.i = -1; + menu::stack_push<menu_select_floppy_init>(ui(), container(), std::move(fs), &m_submenu_result.i); + m_state = SELECT_INIT; + } } break; - case SELECT_RW: - switch(m_submenu_result.rw) { - case menu_select_rw::result::READONLY: - do_load_create(); - fd->setup_write(nullptr); - stack_pop(); - break; + case SELECT_INIT: + // figure out which (if any) create file system was selected + create_fs = nullptr; + if(m_submenu_result.i >= 0) { + int i = 0; + for (const auto &this_fs : fd.get_fs()) { + if (can_format(this_fs)) { + if (i == m_submenu_result.i) { + create_fs = &this_fs; + break; + } + i++; + } + } + } - case menu_select_rw::result::READWRITE: - output_format = input_format; + if(!create_fs) { + m_state = START_FILE; + menu_activated(); + } else { do_load_create(); stack_pop(); - break; - - case menu_select_rw::result::WRITE_DIFF: - machine().popmessage("Sorry, diffs are not supported yet\n"); - stack_pop(); - break; - - case menu_select_rw::result::WRITE_OTHER: - menu::stack_push<menu_file_create>(ui(), container(), &m_image, m_current_directory, m_current_file, m_create_ok); - m_state = CHECK_CREATE; - break; - - case menu_select_rw::result::INVALID: - m_state = START_FILE; - break; } break; default: - menu_control_device_image::handle(); + menu_control_device_image::menu_activated(); } } diff --git a/src/frontend/mame/ui/floppycntrl.h b/src/frontend/mame/ui/floppycntrl.h index fafcd70a312..8bcb02d15b5 100644 --- a/src/frontend/mame/ui/floppycntrl.h +++ b/src/frontend/mame/ui/floppycntrl.h @@ -5,37 +5,42 @@ ui/floppycntrl.h ***************************************************************************/ - -#pragma once - #ifndef MAME_FRONTEND_UI_FLOPPYCNTRL_H #define MAME_FRONTEND_UI_FLOPPYCNTRL_H +#pragma once + #include "ui/imgcntrl.h" #include "imagedev/floppy.h" -#include "formats/flopimg.h" + +#include <memory> + namespace ui { + class menu_control_floppy_image : public menu_control_device_image { public: menu_control_floppy_image(mame_ui_manager &ui, render_container &container, device_image_interface &image); virtual ~menu_control_floppy_image() override; +protected: + virtual void menu_activated() override; + private: - enum { SELECT_FORMAT = LAST_ID, SELECT_MEDIA, SELECT_RW }; + enum { SELECT_FORMAT = LAST_ID, SELECT_MEDIA, SELECT_INIT }; - floppy_image_format_t **format_array; - floppy_image_format_t *input_format, *output_format; + floppy_image_device &fd; + const floppy_image_format_t *input_format, *output_format; + const floppy_image_device::fs_info *create_fs; std::string input_filename, output_filename; - virtual void handle() override; - void do_load_create(); virtual void hook_load(const std::string &filename) override; + static bool can_format(const floppy_image_device::fs_info &fs); }; } // namespace ui -#endif /* MAME_FRONTEND_UI_FLOPPYCNTRL_H */ +#endif // MAME_FRONTEND_UI_FLOPPYCNTRL_H diff --git a/src/frontend/mame/ui/icorender.cpp b/src/frontend/mame/ui/icorender.cpp index bea85f9dd60..7b7a58fa923 100644 --- a/src/frontend/mame/ui/icorender.cpp +++ b/src/frontend/mame/ui/icorender.cpp @@ -19,20 +19,20 @@ #include "emu.h" #include "icorender.h" +#include "util/ioprocs.h" +#include "util/msdib.h" #include "util/png.h" #include <algorithm> #include <cassert> #include <cstdint> #include <cstring> +#include <tuple> -// need to set LOG_OUTPUT_STREAM because there's no logerror outside devices -#define LOG_OUTPUT_STREAM std::cerr +// need to set LOG_OUTPUT_FUNC or LOG_OUTPUT_STREAM because there's no logerror outside devices +#define LOG_OUTPUT_FUNC osd_printf_verbose -#define LOG_GENERAL (1U << 0) -#define LOG_DIB (1U << 1) - -//#define VERBOSE (LOG_GENERAL | LOG_DIB) +//#define VERBOSE (LOG_GENERAL) #include "logmacro.h" @@ -41,16 +41,6 @@ namespace ui { namespace { -// DIB compression schemes -enum : uint32_t -{ - DIB_COMP_NONE = 0, - DIB_COMP_RLE8 = 1, - DIB_COMP_RLE4 = 2, - DIB_COMP_BITFIELDS = 3 -}; - - // ICO file header struct icon_dir_t { @@ -83,78 +73,16 @@ struct icon_dir_entry_t uint32_t offset; // offset to image data from start of file }; -// old-style DIB header -struct bitmap_core_header_t -{ - uint32_t size; // size of the header (12, 16 or 64) - int16_t width; // width of bitmap in pixels - int16_t height; // height of the image in pixels - uint16_t planes; // number of colour planes (must be 1) - uint16_t bpp; // bits per pixel -}; - -// new-style DIB header -struct bitmap_info_header_t -{ - uint32_t size; // size of the header - int32_t width; // width of bitmap in pixels - int32_t height; // height of bitmap in pixels - uint16_t planes; // number of colour planes (must be 1) - uint16_t bpp; // bits per pixel - uint32_t comp; // compression method - uint32_t rawsize; // size of bitmap data after decompression or 0 if uncompressed - int32_t hres; // horizontal resolution in pixels/metre - int32_t vres; // horizontal resolution in pixels/metre - uint32_t colors; // number of colours or 0 for 1 << bpp - uint32_t important; // number of important colours or 0 if all important - uint32_t red; // red field mask - must be contiguous - uint32_t green; // green field mask - must be contiguous - uint32_t blue; // blue field mask - must be contiguous - uint32_t alpha; // alpha field mask - must be contiguous -}; - - -bool dib_parse_mask(uint32_t mask, unsigned &shift, unsigned &bits) -{ - shift = count_leading_zeros(mask); - mask <<= shift; - bits = count_leading_ones(mask); - mask <<= shift; - shift = 32 - shift - bits; - return !mask; -} - - -void dib_truncate_channel(unsigned &shift, unsigned &bits) -{ - if (8U < bits) - { - unsigned const excess(bits - 8); - shift += excess; - bits -= excess; - } -} - -uint8_t dib_splat_sample(uint8_t val, unsigned bits) -{ - assert(8U >= bits); - for (val <<= (8U - bits); bits && (8U > bits); bits <<= 1) - val |= val >> bits; - return val; -} - - -bool load_ico_png(util::core_file &fp, icon_dir_entry_t const &dir, bitmap_argb32 &bitmap) +bool load_ico_png(util::random_read &fp, icon_dir_entry_t const &dir, bitmap_argb32 &bitmap) { // skip out if the data isn't a reasonable size - PNG magic alone is eight bytes if (9U >= dir.size) return false; fp.seek(dir.offset, SEEK_SET); - png_error const err(png_read_bitmap(fp, bitmap)); - switch (err) + std::error_condition const err(util::png_read_bitmap(fp, bitmap)); + if (!err) { - case PNGERR_NONE: // found valid PNG image assert(bitmap.valid()); if ((dir.get_width() == bitmap.width()) && ((dir.get_height() == bitmap.height()))) @@ -171,16 +99,20 @@ bool load_ico_png(util::core_file &fp, icon_dir_entry_t const &dir, bitmap_argb3 dir.get_height()); } return true; - - case PNGERR_BAD_SIGNATURE: + } + else if (util::png_error::BAD_SIGNATURE == err) + { // doesn't look like PNG data - just fall back to DIB without the file header return false; - - default: + } + else + { // invalid PNG data or I/O error LOG( - "Error %u reading PNG image data from ICO file at offset %u (directory size %u)\n", - unsigned(err), + "Error %s:%d %s reading PNG image data from ICO file at offset %u (directory size %u)\n", + err.category().name(), + err.value(), + err.message(), dir.offset, dir.size); return false; @@ -188,432 +120,43 @@ bool load_ico_png(util::core_file &fp, icon_dir_entry_t const &dir, bitmap_argb3 } -bool load_ico_dib(util::core_file &fp, icon_dir_entry_t const &dir, bitmap_argb32 &bitmap) +bool load_ico_dib(util::random_read &fp, icon_dir_entry_t const &dir, bitmap_argb32 &bitmap) { - // check that these things haven't been padded somehow - static_assert(sizeof(bitmap_core_header_t) == 12U, "compiler has applied padding to bitmap_core_header_t"); - static_assert(sizeof(bitmap_info_header_t) == 56U, "compiler has applied padding to bitmap_info_header_t"); - - // ensure the header fits in the space for the image data - union { bitmap_core_header_t core; bitmap_info_header_t info; } header; - assert(&header.core.size == &header.info.size); - if (sizeof(header.core) > dir.size) - return false; - std::memset(&header, 0, sizeof(header)); fp.seek(dir.offset, SEEK_SET); - if (fp.read(&header.core.size, sizeof(header.core.size)) != sizeof(header.core.size)) - { - LOG( - "Error reading DIB header size from ICO file at offset %u (directory size %u)\n", - dir.offset, - dir.size); - return false; - } - header.core.size = little_endianize_int32(header.core.size); - if (dir.size < header.core.size) - { - LOG( - "ICO file image data at %u (%u bytes) is too small for DIB header (%u bytes)\n", - dir.offset, - dir.size, - header.core.size); - return false; - } - - // identify and read the header - convert OS/2 headers to Windows 3 format - unsigned palette_bytes(4U); - switch (header.core.size) + util::msdib_error const err(util::msdib_read_bitmap_data(fp, bitmap, dir.size, dir.get_height())); + switch (err) { - case 16U: - case 64U: - // extended OS/2 bitmap header with support for compression - LOG( - "ICO image data at %u (%u bytes) uses unsupported OS/2 DIB header (size %u)\n", - dir.offset, - dir.size, - header.core.size); - return false; - - case 12U: - // introduced in OS/2 and Windows 2.0 + case util::msdib_error::NONE: + // found valid DIB image + assert(bitmap.valid()); + if ((dir.get_width() == bitmap.width()) && ((dir.get_height() == bitmap.height()))) { - palette_bytes = 3U; - uint32_t const header_read(std::min<uint32_t>(header.core.size, sizeof(header.core)) - sizeof(header.core.size)); - if (fp.read(&header.core.width, header_read) != header_read) - { - LOG("Error reading DIB core header from ICO file image data at %u (%u bytes)\n", dir.offset, dir.size); - return false; - } - fp.seek(header.core.size - sizeof(header.core.size) - header_read, SEEK_CUR); - header.core.width = little_endianize_int16(header.core.width); - header.core.height = little_endianize_int16(header.core.height); - header.core.planes = little_endianize_int16(header.core.planes); - header.core.bpp = little_endianize_int16(header.core.bpp); - LOGMASKED( - LOG_DIB, - "Read DIB core header from ICO file image data at %u: %d*%d, %u planes, %u bpp\n", - dir.offset, - header.core.width, - header.core.height, - header.core.planes, - header.core.bpp); - - // this works because the core header only aliases the width/height of the info header - header.info.bpp = header.core.bpp; - header.info.planes = header.core.planes; - header.info.height = header.core.height; - header.info.width = header.core.width; - header.info.size = 40U; + LOG("Loaded %d*%d pixel DIB image from ICO file\n", bitmap.width(), bitmap.height()); } - break; - - default: - // the next version will be longer - if (124U >= header.core.size) + else { LOG( - "ICO image data at %u (%u bytes) uses unsupported DIB header format (size %u)\n", - dir.offset, - dir.size, - header.core.size); - return false; - } - // fall through - case 40U: - case 52U: - case 56U: - case 108U: - case 124U: - // the Windows 3 bitmap header with optional extensions - { - palette_bytes = 4U; - uint32_t const header_read(std::min<uint32_t>(header.info.size, sizeof(header.info)) - sizeof(header.info.size)); - if (fp.read(&header.info.width, header_read) != header_read) - { - LOG("Error reading DIB info header from ICO file image data at %u (%u bytes)\n", dir.offset, dir.size); - return false; - } - fp.seek(header.info.size - sizeof(header.info.size) - header_read, SEEK_CUR); - header.info.width = little_endianize_int32(header.info.width); - header.info.height = little_endianize_int32(header.info.height); - header.info.planes = little_endianize_int16(header.info.planes); - header.info.bpp = little_endianize_int16(header.info.bpp); - header.info.comp = little_endianize_int32(header.info.comp); - header.info.rawsize = little_endianize_int32(header.info.rawsize); - header.info.hres = little_endianize_int32(header.info.hres); - header.info.vres = little_endianize_int32(header.info.vres); - header.info.colors = little_endianize_int32(header.info.colors); - header.info.important = little_endianize_int32(header.info.important); - header.info.red = little_endianize_int32(header.info.red); - header.info.green = little_endianize_int32(header.info.green); - header.info.blue = little_endianize_int32(header.info.blue); - header.info.alpha = little_endianize_int32(header.info.alpha); - LOGMASKED( - LOG_DIB, - "Read DIB info header from ICO file image data at %u: %d*%d (%d*%d ppm), %u planes, %u bpp %u/%s%u colors\n", - dir.offset, - header.info.width, - header.info.height, - header.info.hres, - header.info.vres, - header.info.planes, - header.info.bpp, - header.info.important, - header.info.colors ? "" : "2^", - header.info.colors ? header.info.colors : header.info.bpp); - } - break; - } - - // check for unsupported planes/bit depth - if ((1U != header.info.planes) || !header.info.bpp || (32U < header.info.bpp) || ((8U < header.info.bpp) ? (header.info.bpp % 8) : (8 % header.info.bpp))) - { - LOG( - "ICO file DIB image data at %u uses unsupported planes/bits per pixel %u*%u\n", - dir.offset, - header.info.planes, - header.info.bpp); - return false; - } - - // check dimensions - if ((0 >= header.info.width) || (0 == header.info.height)) - { - LOG( - "ICO file DIB image data at %u has invalid dimensions %u*%u\n", - dir.offset, - header.info.width, - header.info.height); - return false; - } - bool const top_down(0 > header.info.height); - if (top_down) - header.info.height = -header.info.height; - bool have_and_mask((2 * dir.get_height()) == header.info.height); - if (!have_and_mask && (dir.get_height() != header.info.height)) - { - osd_printf_verbose( - "ICO file DIB image data at %lu height %ld doesn't match directory height %u with or without AND mask\n", - (unsigned long)dir.offset, - (long)header.info.height, - dir.get_height()); - return false; - } - if (have_and_mask) - header.info.height >>= 1; - - // ensure compression scheme is supported - bool indexed(true), no_palette(false); - switch (header.info.comp) - { - case DIB_COMP_NONE: - // uncompressed - direct colour with implied bitfields if more than eight bits/pixel - indexed = 8U >= header.info.bpp; - if (indexed) - { - if ((1U << header.info.bpp) < header.info.colors) - { - osd_printf_verbose( - "ICO file DIB image data at %lu has oversized palette with %lu entries for %u bits per pixel\n", - (unsigned long)dir.offset, - (unsigned long)header.info.colors, - (unsigned)header.info.bpp); - } - } - if (!indexed) - { - no_palette = true; - switch(header.info.bpp) - { - case 16U: - header.info.red = 0x00007c00; - header.info.green = 0x000003e0; - header.info.blue = 0x0000001f; - header.info.alpha = 0x00000000; - break; - case 24U: - case 32U: - header.info.red = 0x00ff0000; - header.info.green = 0x0000ff00; - header.info.blue = 0x000000ff; - header.info.alpha = 0x00000000; - break; - } - } - break; - - case DIB_COMP_BITFIELDS: - // uncompressed direct colour with explicitly-specified bitfields - indexed = false; - if (offsetof(bitmap_info_header_t, alpha) > header.info.size) - { - osd_printf_verbose( - "ICO file DIB image data at %lu specifies bit masks but is too small (size %lu)\n", - (unsigned long)dir.offset, - (unsigned long)header.info.size); - return false; + "Loaded %d*%d pixel DIB image from ICO file (directory indicated %u*%u)\n", + bitmap.width(), + bitmap.height(), + dir.get_width(), + dir.get_height()); } - break; + return true; default: - LOG("ICO file DIB image data at %u uses unsupported compression scheme %u\n", header.info.comp); - return false; - } - - // we can now calculate the size of the palette and row data - size_t const palette_entries( - indexed - ? ((1U == header.info.bpp) ? 2U : header.info.colors ? header.info.colors : (1U << header.info.bpp)) - : (no_palette ? 0U : header.info.colors)); - size_t const palette_size(palette_bytes * palette_entries); - size_t const row_bytes(((31 + (header.info.width * header.info.bpp)) >> 5) << 2); - size_t const mask_row_bytes(((31 + header.info.width) >> 5) << 2); - size_t const required_size( - header.info.size + - palette_size + - ((row_bytes + (have_and_mask ? mask_row_bytes : 0U)) * header.info.height)); - if (required_size > dir.size) - { + // invalid DIB data or I/O error LOG( - "ICO file image data at %u (%u bytes) smaller than calculated DIB data size (%u bytes)\n", + "Error %u reading DIB image data from ICO file at offset %u (directory size %u)\n", + unsigned(err), dir.offset, - dir.size, - required_size); + dir.size); return false; } - - // load the palette for indexed colour formats or the shifts for direct colour formats - unsigned red_shift(0), green_shift(0), blue_shift(0), alpha_shift(0); - unsigned red_bits(0), green_bits(0), blue_bits(0), alpha_bits(0); - std::unique_ptr<rgb_t []> palette; - if (indexed) - { - // read palette and convert - std::unique_ptr<uint8_t []> palette_data(new uint8_t [palette_size]); - if (fp.read(palette_data.get(), palette_size) != palette_size) - { - LOG("Error reading palette from ICO file DIB image data at %u (%u bytes)\n", dir.offset, dir.size); - return false; - } - size_t const palette_usable(std::min<size_t>(palette_entries, size_t(1) << header.info.bpp)); - palette.reset(new rgb_t [palette_usable]); - uint8_t const *ptr(palette_data.get()); - for (size_t i = 0; palette_usable > i; ++i, ptr += palette_bytes) - palette[i] = rgb_t(ptr[2], ptr[1], ptr[0]); - } - else - { - // skip over the palette if necessary - if (palette_entries) - fp.seek(palette_bytes * palette_entries, SEEK_CUR); - - // convert masks to shifts - bool const masks_contiguous( - dib_parse_mask(header.info.red, red_shift, red_bits) && - dib_parse_mask(header.info.green, green_shift, green_bits) && - dib_parse_mask(header.info.blue, blue_shift, blue_bits) && - dib_parse_mask(header.info.alpha, alpha_shift, alpha_bits)); - if (!masks_contiguous) - { - osd_printf_verbose( - "ICO file DIB image data at %lu specifies non-contiguous channel masks 0x%lx | 0x%lx | 0x%lx | 0x%lx\n", - (unsigned long)dir.offset, - (unsigned long)header.info.red, - (unsigned long)header.info.green, - (unsigned long)header.info.blue, - (unsigned long)header.info.alpha); - } - if ((32U != header.info.bpp) && ((header.info.red | header.info.green | header.info.blue | header.info.alpha) >> header.info.bpp)) - { - LOG( - "ICO file DIB image data at %lu specifies channel masks 0x%x | 0x%x | 0x%x | 0x%x that exceed %u bits per pixel\n", - dir.offset, - header.info.red, - header.info.green, - header.info.blue, - header.info.alpha, - header.info.bpp); - return false; - } - LOGMASKED( - LOG_DIB, - "DIB from ICO file image data at %1$u using channels: R((x >> %3$u) & 0x%4$0*2$x) G((x >> %5$u) & 0x%6$0*2$x) B((x >> %7$u) & 0x%8$0*2$x) A((x >> %9$u) & 0x%10$0*2$x)\n", - dir.offset, - (header.info.bpp + 3) >> 2, - red_shift, - (uint32_t(1) << red_bits) - 1, - green_shift, - (uint32_t(1) << green_bits) - 1, - blue_shift, - (uint32_t(1) << blue_bits) - 1, - alpha_shift, - (uint32_t(1) << alpha_bits) - 1); - - // the MAME bitmap only supports 8 bits/sample maximum - dib_truncate_channel(red_shift, red_bits); - dib_truncate_channel(green_shift, green_bits); - dib_truncate_channel(blue_shift, blue_bits); - dib_truncate_channel(alpha_shift, alpha_bits); - } - - // allocate the bitmap and process row data - std::unique_ptr<uint8_t []> row_data(new uint8_t [row_bytes]); - bitmap.allocate(header.info.width, header.info.height); - int const y_inc(top_down ? 1 : -1); - for (int32_t i = 0, y = top_down ? 0 : (header.info.height - 1); header.info.height > i; ++i, y += y_inc) - { - if (fp.read(row_data.get(), row_bytes) != row_bytes) - { - LOG("Error reading DIB row %d data from ICO image data at %u\n", i, dir.offset); - return false; - } - uint8_t *src(row_data.get()); - uint32_t *dest(&bitmap.pix(y)); - unsigned shift(0U); - for (int32_t x = 0; header.info.width > x; ++x, ++dest) - { - // extract or compose a pixel - uint32_t pix(0U); - if (8U >= header.info.bpp) - { - assert(8U > shift); - pix = *src >> (8U - header.info.bpp); - *src <<= header.info.bpp; - shift += header.info.bpp; - if (8U <= shift) - { - shift = 0U; - ++src; - } - } - else for (shift = 0; header.info.bpp > shift; shift += 8U, ++src) - { - pix |= uint32_t(*src) << shift; - } - - // convert to RGB - if (indexed) - { - if (palette_entries > pix) - { - *dest = palette[pix]; - } - else - { - *dest = rgb_t::transparent(); - osd_printf_verbose( - "ICO file DIB image data at %lu has out-of-range color %lu at (%ld, %ld) with %lu palette entries\n", - (unsigned long)dir.offset, - (unsigned long)pix, - (long)x, - (long)y, - (unsigned long)palette_entries); - } - } - else - { - uint8_t r(dib_splat_sample((pix >> red_shift) & ((uint32_t(1) << red_bits) - 1), red_bits)); - uint8_t g(dib_splat_sample((pix >> green_shift) & ((uint32_t(1) << green_bits) - 1), green_bits)); - uint8_t b(dib_splat_sample((pix >> blue_shift) & ((uint32_t(1) << blue_bits) - 1), blue_bits)); - uint8_t a(dib_splat_sample((pix >> alpha_shift) & ((uint32_t(1) << alpha_bits) - 1), alpha_bits)); - *dest = rgb_t(alpha_bits ? a : 255, r, g, b); - } - } - } - - // process the AND mask if present - if (have_and_mask) - { - for (int32_t i = 0, y = top_down ? 0 : (header.info.height - 1); header.info.height > i; ++i, y += y_inc) - { - if (fp.read(row_data.get(), mask_row_bytes) != mask_row_bytes) - { - LOG("Error reading DIB mask row %d data from ICO image data at %u\n", i, dir.offset); - return false; - } - uint8_t *src(row_data.get()); - uint32_t *dest(&bitmap.pix(y)); - unsigned shift(0U); - for (int32_t x = 0; header.info.width > x; ++x, ++dest) - { - assert(8U > shift); - rgb_t pix(*dest); - *dest = pix.set_a(BIT(*src, 7U - shift) ? 0U : pix.a()); - if (8U <= ++shift) - { - shift = 0U; - ++src; - } - } - } - } - - // we're done! - return true; } -bool load_ico_image(util::core_file &fp, unsigned index, icon_dir_entry_t const &dir, bitmap_argb32 &bitmap) +bool load_ico_image(util::random_read &fp, unsigned index, icon_dir_entry_t const &dir, bitmap_argb32 &bitmap) { // try loading PNG image data (contains PNG file magic if used), and then fall back if (load_ico_png(fp, dir, bitmap)) @@ -632,12 +175,16 @@ bool load_ico_image(util::core_file &fp, unsigned index, icon_dir_entry_t const } -bool load_ico_image(util::core_file &fp, unsigned count, unsigned index, bitmap_argb32 &bitmap) +bool load_ico_image(util::random_read &fp, unsigned count, unsigned index, bitmap_argb32 &bitmap) { // read the directory entry + std::error_condition err; + size_t actual; icon_dir_entry_t dir; - fp.seek(sizeof(icon_dir_t) + (sizeof(icon_dir_entry_t) * index), SEEK_SET); - if (fp.read(&dir, sizeof(dir)) != sizeof(dir)) + err = fp.seek(sizeof(icon_dir_t) + (sizeof(icon_dir_entry_t) * index), SEEK_SET); + if (!err) + std::tie(err, actual) = read(fp, &dir, sizeof(dir)); + if (err || (sizeof(dir) != actual)) { LOG("Failed to read ICO file directory entry %u\n", index); return false; @@ -661,12 +208,16 @@ bool load_ico_image(util::core_file &fp, unsigned count, unsigned index, bitmap_ } // anonymous namespace -int images_in_ico(util::core_file &fp) +int images_in_ico(util::random_read &fp) { // read and check the icon file header + std::error_condition err; + size_t actual; icon_dir_t header; - fp.seek(0, SEEK_SET); - if (fp.read(&header, sizeof(header)) != sizeof(header)) + err = fp.seek(0, SEEK_SET); + if (!err) + std::tie(err, actual) = read(fp, &header, sizeof(header)); + if (err || (sizeof(header) != actual)) { LOG("Failed to read ICO file header\n"); return -1; @@ -688,7 +239,7 @@ int images_in_ico(util::core_file &fp) } -void render_load_ico(util::core_file &fp, unsigned index, bitmap_argb32 &bitmap) +void render_load_ico(util::random_read &fp, unsigned index, bitmap_argb32 &bitmap) { // check that these things haven't been padded somehow static_assert(sizeof(icon_dir_t) == 6U, "compiler has applied padding to icon_dir_t"); @@ -712,7 +263,7 @@ void render_load_ico(util::core_file &fp, unsigned index, bitmap_argb32 &bitmap) } -void render_load_ico_first(util::core_file &fp, bitmap_argb32 &bitmap) +void render_load_ico_first(util::random_read &fp, bitmap_argb32 &bitmap) { int const count(images_in_ico(fp)); for (int i = 0; count > i; ++i) @@ -724,57 +275,70 @@ void render_load_ico_first(util::core_file &fp, bitmap_argb32 &bitmap) } -void render_load_ico_highest_detail(util::core_file &fp, bitmap_argb32 &bitmap) +void render_load_ico_highest_detail(util::random_read &fp, bitmap_argb32 &bitmap) { // read and check the icon file header - logs a message on error int const count(images_in_ico(fp)); - if (0 <= count) + if (0 > count) + { + bitmap.reset(); + return; + } + + // now load all the directory entries + size_t const dir_bytes(sizeof(icon_dir_entry_t) * count); + std::unique_ptr<icon_dir_entry_t []> dir(new (std::nothrow) icon_dir_entry_t [count]); + std::unique_ptr<unsigned []> index(new (std::nothrow) unsigned [count]); + if (count) { - // now load all the directory entries - size_t const dir_bytes(sizeof(icon_dir_entry_t) * count); - std::unique_ptr<icon_dir_entry_t []> dir(new icon_dir_entry_t [count]); - std::unique_ptr<unsigned []> index(new unsigned [count]); - if (count && (fp.read(dir.get(), dir_bytes) != dir_bytes)) + if (!dir || !index) { - LOG("Failed to read ICO file directory entries\n"); + LOG("Failed to allocate memory for ICO file directory entries\n"); + bitmap.reset(); + return; } - else + auto const [err, actual] = read(fp, dir.get(), dir_bytes); + if (err || (dir_bytes != actual)) { - // byteswap and sort by (pixels, depth) - for (int i = 0; count > i; ++i) - { - dir[i].byteswap(); - index[i] = i; - } - std::stable_sort( - index.get(), - index.get() + count, - [&dir] (unsigned x, unsigned y) - { - unsigned const x_pixels(dir[x].get_width() * dir[x].get_height()); - unsigned const y_pixels(dir[y].get_width() * dir[y].get_height()); - if (x_pixels > y_pixels) - return true; - else if (x_pixels < y_pixels) - return false; - else - return dir[x].bpp > dir[y].bpp; - }); - - // walk down until something works - for (int i = 0; count > i; ++i) - { - LOG( - "Try loading ICO file entry %u: %u*%u, %u bits per pixel\n", - index[i], - dir[index[i]].get_width(), - dir[index[i]].get_height(), - dir[index[i]].bpp); - if (load_ico_image(fp, index[i], dir[index[i]], bitmap)) - return; - } + LOG("Failed to read ICO file directory entries\n"); + bitmap.reset(); + return; } } + + // byteswap and sort by (pixels, depth) + for (int i = 0; count > i; ++i) + { + dir[i].byteswap(); + index[i] = i; + } + std::stable_sort( + index.get(), + index.get() + count, + [&dir] (unsigned x, unsigned y) + { + unsigned const x_pixels(dir[x].get_width() * dir[x].get_height()); + unsigned const y_pixels(dir[y].get_width() * dir[y].get_height()); + if (x_pixels > y_pixels) + return true; + else if (x_pixels < y_pixels) + return false; + else + return dir[x].bpp > dir[y].bpp; + }); + + // walk down until something works + for (int i = 0; count > i; ++i) + { + LOG( + "Try loading ICO file entry %u: %u*%u, %u bits per pixel\n", + index[i], + dir[index[i]].get_width(), + dir[index[i]].get_height(), + dir[index[i]].bpp); + if (load_ico_image(fp, index[i], dir[index[i]], bitmap)) + return; + } bitmap.reset(); } diff --git a/src/frontend/mame/ui/icorender.h b/src/frontend/mame/ui/icorender.h index bb3f1a083be..32fa67a3424 100644 --- a/src/frontend/mame/ui/icorender.h +++ b/src/frontend/mame/ui/icorender.h @@ -18,16 +18,16 @@ namespace ui { // get number of images in icon file (-1 on error) -int images_in_ico(util::core_file &fp); +int images_in_ico(util::random_read &fp); // load specified icon from file (zero-based) -void render_load_ico(util::core_file &fp, unsigned index, bitmap_argb32 &bitmap); +void render_load_ico(util::random_read &fp, unsigned index, bitmap_argb32 &bitmap); // load first supported icon from file -void render_load_ico_first(util::core_file &fp, bitmap_argb32 &bitmap); +void render_load_ico_first(util::random_read &fp, bitmap_argb32 &bitmap); // load highest detail supported icon from file -void render_load_ico_highest_detail(util::core_file &fp, bitmap_argb32 &bitmap); +void render_load_ico_highest_detail(util::random_read &fp, bitmap_argb32 &bitmap); } // namespace ui diff --git a/src/frontend/mame/ui/imgcntrl.cpp b/src/frontend/mame/ui/imgcntrl.cpp index 3312e5d54e7..01106a7ba48 100644 --- a/src/frontend/mame/ui/imgcntrl.cpp +++ b/src/frontend/mame/ui/imgcntrl.cpp @@ -9,22 +9,28 @@ ***************************************************************************/ #include "emu.h" - #include "ui/imgcntrl.h" -#include "ui/ui.h" -#include "ui/filesel.h" #include "ui/filecreate.h" +#include "ui/filesel.h" +#include "ui/midiinout.h" #include "ui/swlist.h" +#include "ui/ui.h" + +#include "bus/midi/midiinport.h" +#include "bus/midi/midioutport.h" #include "audit.h" #include "drivenum.h" #include "emuopts.h" +#include "image.h" #include "softlist_dev.h" -#include "zippath.h" + +#include "util/zippath.h" namespace ui { + /*************************************************************************** IMPLEMENTATION ***************************************************************************/ @@ -38,20 +44,55 @@ menu_control_device_image::menu_control_device_image(mame_ui_manager &mui, rende , m_image(image) , m_create_ok(false) , m_create_confirmed(false) + , m_swi(nullptr) + , m_swp(nullptr) + , m_sld(nullptr) { m_submenu_result.i = -1; if (m_image.software_list_name()) m_sld = software_list_device::find_by_name(mui.machine().config(), m_image.software_list_name()); - else - m_sld = nullptr; m_swi = m_image.software_entry(); m_swp = m_image.part_entry(); - if (m_swi != nullptr) + // if there's no image mounted, check for a software item with compatible parts mounted elsewhere + if (!m_image.exists() && m_image.image_interface()) + { + assert(!m_swi); + + for (device_image_interface &other : image_interface_enumerator(mui.machine().root_device())) + { + if (other.loaded_through_softlist() && (!m_sld || (m_sld->list_name() == other.software_list_name()))) + { + software_info const &swi = *other.software_entry(); + for (software_part const &swp : swi.parts()) + { + if (swp.interface() == m_image.image_interface()) + { + if (!m_sld) + m_sld = software_list_device::find_by_name(mui.machine().config(), other.software_list_name()); + m_swi = &swi; + break; + } + } + } + + if (m_swi) + break; + } + } + + if (m_swi) { m_state = START_OTHER_PART; m_current_directory = m_image.working_directory(); + + // check to see if we've never initialized the working directory + if (m_current_directory.empty()) + { + m_current_directory = machine().image().setup_working_directory(); + m_image.set_working_directory(m_current_directory); + } } else { @@ -61,16 +102,23 @@ menu_control_device_image::menu_control_device_image(mame_ui_manager &mui, rende if (m_image.exists()) { m_current_file.assign(m_image.filename()); - util::zippath_parent(m_current_directory, m_current_file); + m_current_directory = util::zippath_parent(m_current_file); } else { m_current_directory = m_image.working_directory(); + + // check to see if we've never initialized the working directory + if (m_current_directory.empty()) + { + m_current_directory = machine().image().setup_working_directory(); + m_image.set_working_directory(m_current_directory); + } } // check to see if the path exists; if not then set to current directory util::zippath_directory::ptr dir; - if (util::zippath_directory::open(m_current_directory, dir) != osd_file::error::NONE) + if (util::zippath_directory::open(m_current_directory, dir)) osd_get_full_path(m_current_directory, "."); } } @@ -95,7 +143,7 @@ void menu_control_device_image::test_create(bool &can_create, bool &need_confirm auto path = util::zippath_combine(m_current_directory, m_current_file); // does a file or a directory exist at the path - auto entry = osd_stat(path.c_str()); + auto entry = osd_stat(path); auto file_type = (entry != nullptr) ? entry->type : osd::directory::entry::entry_type::NONE; switch(file_type) @@ -138,17 +186,20 @@ void menu_control_device_image::load_software_part() driver_enumerator drivlist(machine().options(), machine().options().system_name()); drivlist.next(); media_auditor auditor(drivlist); - media_auditor::summary summary = auditor.audit_software(m_sld->list_name(), (software_info *)m_swi, AUDIT_VALIDATE_FAST); + media_auditor::summary summary = auditor.audit_software(*m_sld, *m_swi, AUDIT_VALIDATE_FAST); // if everything looks good, load software if (summary == media_auditor::CORRECT || summary == media_auditor::BEST_AVAILABLE || summary == media_auditor::NONE_NEEDED) { - m_image.load_software(temp_name); + auto [err, msg] = m_image.load_software(temp_name); + if (err) + machine().popmessage(_("Error loading software item: %1$s"), !msg.empty() ? msg : err.message()); stack_pop(); } else { - machine().popmessage(_("The software selected is missing one or more required ROM or CHD images. Please select a different one.")); + machine().popmessage(_("The software selected is missing one or more required ROM or CHD images.\nPlease acquire the correct files or select a different one.")); m_state = SELECT_SOFTLIST; + menu_activated(); } } @@ -159,7 +210,9 @@ void menu_control_device_image::load_software_part() void menu_control_device_image::hook_load(const std::string &name) { - m_image.load(name); + auto [err, msg] = m_image.load(name); + if (err) + machine().popmessage(_("Error loading media image: %1$s"), !msg.empty() ? msg : err.message()); stack_pop(); } @@ -168,8 +221,9 @@ void menu_control_device_image::hook_load(const std::string &name) // populate //------------------------------------------------- -void menu_control_device_image::populate(float &customtop, float &custombottom) +void menu_control_device_image::populate() { + throw emu_fatalerror("menu_control_device_image::populate: Shouldn't get here!"); } @@ -177,14 +231,62 @@ void menu_control_device_image::populate(float &customtop, float &custombottom) // handle //------------------------------------------------- -void menu_control_device_image::handle() +bool menu_control_device_image::handle(event const *ev) +{ + throw emu_fatalerror("menu_control_device_image::handle: Shouldn't get here!"); +} + + +//------------------------------------------------- +// menu_activated +//------------------------------------------------- + +void menu_control_device_image::menu_activated() { switch(m_state) { case START_FILE: - m_submenu_result.filesel = menu_file_selector::result::INVALID; - menu::stack_push<menu_file_selector>(ui(), container(), &m_image, m_current_directory, m_current_file, true, m_image.image_interface()!=nullptr, m_image.is_creatable(), m_submenu_result.filesel); - m_state = SELECT_FILE; + menu::stack_push<menu_file_selector>( + ui(), container(), + &m_image, + m_current_directory, + m_current_file, + true, + m_image.image_interface() != nullptr, + m_image.is_creatable(), + [this] (menu_file_selector::result result, std::string &&directory, std::string &&file) + { + m_current_directory = std::move(directory); + m_current_file = std::move(file); + switch (result) + { + case menu_file_selector::result::EMPTY: + m_image.unload(); + stack_pop(); + break; + + case menu_file_selector::result::FILE: + hook_load(m_current_file); + break; + + case menu_file_selector::result::CREATE: + menu::stack_push<menu_file_create>(ui(), container(), &m_image, m_current_directory, m_current_file, m_create_ok); + m_state = CHECK_CREATE; + break; + + case menu_file_selector::result::SOFTLIST: + m_state = START_SOFTLIST; + break; + + case menu_file_selector::result::MIDI: + m_state = START_MIDI; + break; + + default: // return to system + stack_pop(); + break; + } + }); break; case START_SOFTLIST: @@ -193,9 +295,25 @@ void menu_control_device_image::handle() m_state = SELECT_SOFTLIST; break; + case START_MIDI: + m_midi = ""; + menu::stack_push<menu_midi_inout>(ui(), container(), m_image.device().type() == MIDIIN, &m_midi); + m_state = SELECT_MIDI; + break; + + case SELECT_MIDI: + if(!m_midi.empty()) + { + auto [err, msg] = m_image.load(m_midi); + if (err) + machine().popmessage(_("Error connecting to midi port: %1$s"), !msg.empty() ? msg : err.message()); + } + stack_pop(); + break; + case START_OTHER_PART: m_submenu_result.swparts = menu_software_parts::result::INVALID; - menu::stack_push<menu_software_parts>(ui(), container(), m_swi, m_swp->interface().c_str(), &m_swp, true, m_submenu_result.swparts); + menu::stack_push<menu_software_parts>(ui(), container(), m_swi, m_image.image_interface(), &m_swp, true, m_submenu_result.swparts); m_state = SELECT_OTHER_PART; break; @@ -203,17 +321,22 @@ void menu_control_device_image::handle() if (!m_sld) { stack_pop(); - break; } - m_software_info_name.clear(); - menu::stack_push_special_main<menu_software_list>(ui(), container(), m_sld, m_image.image_interface(), m_software_info_name); - m_state = SELECT_PARTLIST; + else + { + m_software_info_name.clear(); + menu::stack_push<menu_software_list>(ui(), container(), m_sld, m_image.image_interface(), m_software_info_name); + m_state = SELECT_PARTLIST; + } break; case SELECT_PARTLIST: - m_swi = m_sld->find(m_software_info_name.c_str()); + m_swi = m_sld->find(m_software_info_name); if (!m_swi) + { m_state = START_SOFTLIST; + menu_activated(); + } else if (m_swi->has_multiple_parts(m_image.image_interface())) { m_submenu_result.swparts = menu_software_parts::result::INVALID; @@ -229,28 +352,29 @@ void menu_control_device_image::handle() break; case SELECT_ONE_PART: - switch(m_submenu_result.swparts) { - case menu_software_parts::result::ENTRY: { + switch (m_submenu_result.swparts) + { + case menu_software_parts::result::ENTRY: load_software_part(); break; - } default: // return to list m_state = SELECT_SOFTLIST; + menu_activated(); break; - } break; case SELECT_OTHER_PART: - switch(m_submenu_result.swparts) { + switch (m_submenu_result.swparts) + { case menu_software_parts::result::ENTRY: load_software_part(); break; case menu_software_parts::result::FMGR: m_state = START_FILE; - handle(); + menu_activated(); break; case menu_software_parts::result::EMPTY: @@ -260,81 +384,60 @@ void menu_control_device_image::handle() case menu_software_parts::result::SWLIST: m_state = START_SOFTLIST; - handle(); + menu_activated(); break; case menu_software_parts::result::INVALID: // return to system stack_pop(); break; - } break; - case SELECT_FILE: - switch(m_submenu_result.filesel) + case CREATE_FILE: { - case menu_file_selector::result::EMPTY: - m_image.unload(); - stack_pop(); - break; - - case menu_file_selector::result::FILE: - hook_load(m_current_file); - break; - - case menu_file_selector::result::CREATE: - menu::stack_push<menu_file_create>(ui(), container(), &m_image, m_current_directory, m_current_file, m_create_ok); - m_state = CHECK_CREATE; - break; - - case menu_file_selector::result::SOFTLIST: - m_state = START_SOFTLIST; - handle(); - break; - - default: // return to system - stack_pop(); - break; - } - break; - - case CREATE_FILE: { - bool can_create, need_confirm; - test_create(can_create, need_confirm); - if(can_create) { - if(need_confirm) { - menu::stack_push<menu_confirm_save_as>(ui(), container(), &m_create_confirmed); - m_state = CREATE_CONFIRM; - } else { - m_state = DO_CREATE; - handle(); + bool can_create, need_confirm; + test_create(can_create, need_confirm); + if (can_create) + { + if (need_confirm) + { + menu::stack_push<menu_confirm_save_as>(ui(), container(), m_create_confirmed); + m_state = CREATE_CONFIRM; + } + else + { + m_state = DO_CREATE; + menu_activated(); + } + } + else + { + m_state = START_FILE; + menu_activated(); } - } else { - m_state = START_FILE; - handle(); } break; - } case CREATE_CONFIRM: m_state = m_create_confirmed ? DO_CREATE : START_FILE; - handle(); + menu_activated(); break; case CHECK_CREATE: m_state = m_create_ok ? CREATE_FILE : START_FILE; - handle(); + menu_activated(); break; - case DO_CREATE: { - auto path = util::zippath_combine(m_current_directory, m_current_file); - image_init_result err = m_image.create(path, nullptr, nullptr); - if (err != image_init_result::PASS) - machine().popmessage("Error: %s", m_image.error()); - stack_pop(); + case DO_CREATE: + { + auto path = util::zippath_combine(m_current_directory, m_current_file); + auto [err, msg] = m_image.create(path, nullptr, nullptr); + if (err) + machine().popmessage(_("Error creating media image: %1$s"), !msg.empty() ? msg : err.message()); + stack_pop(); + } break; } - } } } // namespace ui diff --git a/src/frontend/mame/ui/imgcntrl.h b/src/frontend/mame/ui/imgcntrl.h index a41a3f85f77..c41f6a25993 100644 --- a/src/frontend/mame/ui/imgcntrl.h +++ b/src/frontend/mame/ui/imgcntrl.h @@ -4,20 +4,21 @@ ui/imgcntrl.h - MESS's clunky built-in file manager + MAME's clunky built-in file manager ***************************************************************************/ -#pragma once - #ifndef MAME_FRONTEND_UI_IMAGECNTRL_H #define MAME_FRONTEND_UI_IMAGECNTRL_H +#pragma once + #include "ui/menu.h" -#include "ui/filesel.h" #include "ui/swlist.h" + namespace ui { + // ======================> menu_control_device_image class menu_control_device_image : public menu @@ -29,9 +30,9 @@ public: protected: enum { - START_FILE, START_OTHER_PART, START_SOFTLIST, + START_FILE, START_OTHER_PART, START_SOFTLIST, START_MIDI, SELECT_PARTLIST, SELECT_ONE_PART, SELECT_OTHER_PART, - SELECT_FILE, CREATE_FILE, CREATE_CONFIRM, CHECK_CREATE, DO_CREATE, SELECT_SOFTLIST, + CREATE_FILE, CREATE_CONFIRM, CHECK_CREATE, DO_CREATE, SELECT_SOFTLIST, SELECT_MIDI, LAST_ID }; @@ -39,9 +40,7 @@ protected: // results we could get from child menus union { - menu_file_selector::result filesel; menu_software_parts::result swparts; - menu_select_rw::result rw; int i; } m_submenu_result; @@ -53,8 +52,9 @@ protected: bool m_create_ok; // methods + virtual void menu_activated() override; + virtual bool handle(event const *ev) override; virtual void hook_load(const std::string &filename); - virtual void handle() override; private: // instance variables @@ -63,13 +63,14 @@ private: const software_part * m_swp; class software_list_device * m_sld; std::string m_software_info_name; + std::string m_midi; // methods - virtual void populate(float &customtop, float &custombottom) override; + virtual void populate() override; void test_create(bool &can_create, bool &need_confirm); void load_software_part(); }; } // namespace ui -#endif /* MAME_FRONTEND_UI_IMAGECNTRL_H */ +#endif // MAME_FRONTEND_UI_IMAGECNTRL_H diff --git a/src/frontend/mame/ui/info.cpp b/src/frontend/mame/ui/info.cpp index b28af0eb817..8db48046bc4 100644 --- a/src/frontend/mame/ui/info.cpp +++ b/src/frontend/mame/ui/info.cpp @@ -9,46 +9,233 @@ ***************************************************************************/ #include "emu.h" - #include "ui/info.h" + +#include "ui/systemlist.h" #include "ui/ui.h" +#include "infoxml.h" + #include "drivenum.h" +#include "emuopts.h" #include "romload.h" +#include "screen.h" #include "softlist.h" -#include "emuopts.h" +#include "speaker.h" + +#include "util/unicode.h" + +#include <locale> +#include <set> +#include <sstream> +#include <type_traits> +#include <utility> namespace ui { namespace { -constexpr machine_flags::type MACHINE_ERRORS = machine_flags::NOT_WORKING | machine_flags::MECHANICAL; +constexpr machine_flags::type MACHINE_ERRORS = machine_flags::MECHANICAL; constexpr machine_flags::type MACHINE_WARNINGS = machine_flags::NO_COCKTAIL | machine_flags::REQUIRES_ARTWORK; constexpr machine_flags::type MACHINE_BTANB = machine_flags::NO_SOUND_HW | machine_flags::IS_INCOMPLETE; +constexpr device_t::flags_type DEVICE_ERRORS = device_t::flags::NOT_WORKING; constexpr std::pair<device_t::feature_type, char const *> FEATURE_NAMES[] = { - { device_t::feature::PROTECTION, __("protection") }, - { device_t::feature::TIMING, __("timing") }, - { device_t::feature::GRAPHICS, __("graphics") }, - { device_t::feature::PALETTE, __("color palette") }, - { device_t::feature::SOUND, __("sound") }, - { device_t::feature::CAPTURE, __("capture hardware") }, - { device_t::feature::CAMERA, __("camera") }, - { device_t::feature::MICROPHONE, __("microphone") }, - { device_t::feature::CONTROLS, __("controls") }, - { device_t::feature::KEYBOARD, __("keyboard") }, - { device_t::feature::MOUSE, __("mouse") }, - { device_t::feature::MEDIA, __("media") }, - { device_t::feature::DISK, __("disk") }, - { device_t::feature::PRINTER, __("printer") }, - { device_t::feature::TAPE, __("magnetic tape") }, - { device_t::feature::PUNCH, __("punch tape") }, - { device_t::feature::DRUM, __("magnetic drum") }, - { device_t::feature::ROM, __("solid state storage") }, - { device_t::feature::COMMS, __("communications") }, - { device_t::feature::LAN, __("LAN") }, - { device_t::feature::WAN, __("WAN") } }; + { device_t::feature::PROTECTION, N_p("emulation-feature", "protection") }, + { device_t::feature::TIMING, N_p("emulation-feature", "timing") }, + { device_t::feature::GRAPHICS, N_p("emulation-feature", "graphics") }, + { device_t::feature::PALETTE, N_p("emulation-feature", "color palette") }, + { device_t::feature::SOUND, N_p("emulation-feature", "sound") }, + { device_t::feature::CAPTURE, N_p("emulation-feature", "capture hardware") }, + { device_t::feature::CAMERA, N_p("emulation-feature", "camera") }, + { device_t::feature::MICROPHONE, N_p("emulation-feature", "microphone") }, + { device_t::feature::CONTROLS, N_p("emulation-feature", "controls") }, + { device_t::feature::KEYBOARD, N_p("emulation-feature", "keyboard") }, + { device_t::feature::MOUSE, N_p("emulation-feature", "mouse") }, + { device_t::feature::MEDIA, N_p("emulation-feature", "media") }, + { device_t::feature::DISK, N_p("emulation-feature", "disk") }, + { device_t::feature::PRINTER, N_p("emulation-feature", "printer") }, + { device_t::feature::TAPE, N_p("emulation-feature", "magnetic tape") }, + { device_t::feature::PUNCH, N_p("emulation-feature", "punch tape") }, + { device_t::feature::DRUM, N_p("emulation-feature", "magnetic drum") }, + { device_t::feature::ROM, N_p("emulation-feature", "solid state storage") }, + { device_t::feature::COMMS, N_p("emulation-feature", "communications") }, + { device_t::feature::LAN, N_p("emulation-feature", "LAN") }, + { device_t::feature::WAN, N_p("emulation-feature", "WAN") } }; + +void get_general_warnings( + std::ostream &buf, + running_machine &machine, + machine_flags::type machflags, + device_t::flags_type devflags, + device_t::feature_type unemulated, + device_t::feature_type imperfect, + bool has_nonworking_devices) +{ + // add a warning if any ROMs were loaded with warnings + bool bad_roms(false); + if (machine.rom_load().warnings() > 0) + { + bad_roms = true; + buf << _("One or more ROMs/disk images for this system are incorrect. The system may not run correctly.\n"); + } + if (!machine.rom_load().software_load_warnings_message().empty()) + { + bad_roms = true; + buf << machine.rom_load().software_load_warnings_message(); + } + + // if we have at least one warning flag, print the general header + if ((machine.rom_load().knownbad() > 0) || (machflags & (MACHINE_ERRORS | MACHINE_WARNINGS | MACHINE_BTANB)) || (devflags & DEVICE_ERRORS) || unemulated || imperfect || has_nonworking_devices) + { + if (bad_roms) + buf << '\n'; + buf << _("There are known problems with this system:\n\n"); + } + + // add a warning if any ROMs are flagged BAD_DUMP/NO_DUMP + if (machine.rom_load().knownbad() > 0) + buf << _("One or more ROMs/disk images for this system have not been correctly dumped.\n"); +} + +void get_device_warnings(std::ostream &buf, device_t::flags_type flags, device_t::feature_type unemulated, device_t::feature_type imperfect) +{ + // add line for not working + if (flags & device_t::flags::NOT_WORKING) + buf << _("THIS DEVICE DOES NOT WORK.\n"); + + // add line for unemulated features + if (unemulated) + { + buf << _("Completely unemulated features: "); + bool first = true; + for (auto const &feature : FEATURE_NAMES) + { + if (unemulated & feature.first) + { + util::stream_format(buf, first ? _("%s") : _(", %s"), _("emulation-feature", feature.second)); + first = false; + } + } + buf << '\n'; + } + + // add line for imperfect features + if (imperfect) + { + buf << _("Imperfectly emulated features: "); + bool first = true; + for (auto const &feature : FEATURE_NAMES) + { + if (imperfect & feature.first) + { + util::stream_format(buf, first ? _("%s") : _(", %s"), _("emulation-feature", feature.second)); + first = false; + } + } + buf << '\n'; + } +} + +void get_system_warnings( + std::ostream &buf, + running_machine &machine, + machine_flags::type machflags, + device_t::flags_type devflags, + device_t::feature_type unemulated, + device_t::feature_type imperfect, + bool has_nonworking_devices) +{ + std::streampos start_position = buf.tellp(); + + // start with the unemulated/imperfect features + get_device_warnings(buf, device_t::flags::NONE, unemulated, imperfect); + + // add one line per machine warning flag + if (machflags & ::machine_flags::NO_COCKTAIL) + buf << _("Screen flipping in cocktail mode is not supported.\n"); + if (machflags & ::machine_flags::REQUIRES_ARTWORK) + buf << _("This system requires external artwork files.\n"); + + // add the 'BTANB' warnings + if (machflags & ::machine_flags::IS_INCOMPLETE) + { + if (buf.tellp() > start_position) + buf << '\n'; + buf << _("This system was never completed. It may exhibit strange behavior or missing elements that are not bugs in the emulation.\n"); + } + if (machflags & ::machine_flags::NO_SOUND_HW) + { + if (buf.tellp() > start_position) + buf << '\n'; + buf << _("This system has no sound hardware, MAME will produce no sounds, this is expected behavior.\n"); + } + + // list devices that don't work + if (has_nonworking_devices) + { + if (buf.tellp() > start_position) + buf << '\n'; + buf << _("The following devices do not work: "); + bool first = true; + std::set<std::add_pointer_t<device_type> > seen; + for (device_t &device : device_enumerator(machine.root_device())) + { + if ((&machine.root_device() != &device) && (device.type().emulation_flags() & device_t::flags::NOT_WORKING) && seen.insert(&device.type()).second) + { + util::stream_format(buf, first ? _("%s") : _(", %s"), device.type().fullname()); + first = false; + } + } + buf << '\n'; + } + + // these are more severe warnings + if (machflags & ::machine_flags::MECHANICAL) + { + if (buf.tellp() > start_position) + buf << '\n'; + buf << _("Elements of this system cannot be emulated accurately as they require physical interaction or consist of mechanical devices. It is not possible to fully experience this system.\n"); + } + if (devflags & device_t::flags::NOT_WORKING) + { + if (buf.tellp() > start_position) + buf << '\n'; + buf << _("THIS SYSTEM DOESN'T WORK. The emulation for this system is not yet complete. There is nothing you can do to fix this problem except wait for the developers to improve the emulation.\n"); + } + + if ((machflags & MACHINE_ERRORS) || (devflags & DEVICE_ERRORS) || ((machine.system().type.unemulated_features() | machine.system().type.imperfect_features()) & device_t::feature::PROTECTION)) + { + // find the parent of this driver + driver_enumerator drivlist(machine.options()); + int maindrv = drivlist.find(machine.system()); + int clone_of = drivlist.non_bios_clone(maindrv); + if (clone_of != -1) + maindrv = clone_of; + + // scan the driver list for any working clones and add them + bool foundworking = false; + while (drivlist.next()) + { + if (drivlist.current() == maindrv || drivlist.clone() == maindrv) + { + game_driver const &driver(drivlist.driver()); + if (!(driver.flags & MACHINE_ERRORS) && !(driver.type.emulation_flags() & DEVICE_ERRORS) && !((driver.type.unemulated_features() | driver.type.imperfect_features()) & device_t::feature::PROTECTION)) + { + // this one works, add a header and display the name of the clone + if (!foundworking) + util::stream_format(buf, _("\n\nThere are working clones of this system: %s"), driver.name); + else + util::stream_format(buf, _(", %s"), driver.name); + foundworking = true; + } + } + } + if (foundworking) + buf << '\n'; + } +} } // anonymous namespace @@ -71,8 +258,10 @@ machine_static_info::machine_static_info(const ui_options &options, machine_conf machine_static_info::machine_static_info(const ui_options &options, machine_config const &config, ioport_list const *ports) : m_options(options) , m_flags(config.gamedrv().flags) + , m_emulation_flags(config.gamedrv().type.emulation_flags()) , m_unemulated_features(config.gamedrv().type.unemulated_features()) , m_imperfect_features(config.gamedrv().type.imperfect_features()) + , m_has_nonworking_devices(false) , m_has_bioses(false) , m_has_dips(false) , m_has_configs(false) @@ -81,22 +270,30 @@ machine_static_info::machine_static_info(const ui_options &options, machine_conf , m_has_analog(false) { ioport_list local_ports; - std::string sink; - for (device_t &device : device_iterator(config.root_device())) + std::ostringstream sink; + for (device_t &device : device_enumerator(config.root_device())) { // the "no sound hardware" warning doesn't make sense when you plug in a sound card - if (dynamic_cast<device_sound_interface *>(&device)) + if (dynamic_cast<speaker_device *>(&device)) m_flags &= ~::machine_flags::NO_SOUND_HW; // build overall emulation status + m_emulation_flags |= device.type().emulation_flags() & ~device_t::flags::NOT_WORKING; m_unemulated_features |= device.type().unemulated_features(); m_imperfect_features |= device.type().imperfect_features(); + if (&config.root_device() != &device) + m_has_nonworking_devices = m_has_nonworking_devices || (device.type().emulation_flags() & device_t::flags::NOT_WORKING); // look for BIOS options - for (tiny_rom_entry const *rom = device.rom_region(); !m_has_bioses && rom && !ROMENTRY_ISEND(rom); ++rom) + device_t const *const parent(device.owner()); + device_slot_interface const *const slot(dynamic_cast<device_slot_interface const *>(parent)); + if (!parent || (slot && (slot->get_card_device() == &device))) { - if (ROMENTRY_ISSYSTEM_BIOS(rom)) - m_has_bioses = true; + for (tiny_rom_entry const *rom = device.rom_region(); !m_has_bioses && rom && !ROMENTRY_ISEND(rom); ++rom) + { + if (ROMENTRY_ISSYSTEM_BIOS(rom)) + m_has_bioses = true; + } } // if we don't have ports passed in, build here @@ -104,6 +301,17 @@ machine_static_info::machine_static_info(const ui_options &options, machine_conf local_ports.append(device, sink); } + // suppress "requires external artwork" warning when external artwork was loaded + if (config.root_device().has_running_machine()) + { + for (render_target const &target : config.root_device().machine().render().targets()) + if (!target.hidden() && target.external_artwork()) + { + m_flags &= ~::machine_flags::REQUIRES_ARTWORK; + break; + } + } + // unemulated trumps imperfect when aggregating (always be pessimistic) m_imperfect_features &= ~m_unemulated_features; @@ -128,15 +336,46 @@ machine_static_info::machine_static_info(const ui_options &options, machine_conf //------------------------------------------------- +// has_warnings - returns true if the system has +// issues that warrant a yellow/red message +//------------------------------------------------- + +bool machine_static_info::has_warnings() const noexcept +{ + return + (machine_flags() & (MACHINE_ERRORS | MACHINE_WARNINGS)) || + (emulation_flags() & DEVICE_ERRORS) || + unemulated_features() || + imperfect_features() || + has_nonworking_devices(); +} + + +//------------------------------------------------- +// has_severe_warnings - returns true if the +// system has issues that warrant a red message +//------------------------------------------------- + +bool machine_static_info::has_severe_warnings() const noexcept +{ + return + (machine_flags() & MACHINE_ERRORS) || + (emulation_flags() & DEVICE_ERRORS) || + (unemulated_features() & (device_t::feature::PROTECTION | device_t::feature::GRAPHICS | device_t::feature::SOUND)) || + (imperfect_features() & device_t::feature::PROTECTION); +} + + +//------------------------------------------------- // status_color - returns suitable colour for // driver status box //------------------------------------------------- -rgb_t machine_static_info::status_color() const +rgb_t machine_static_info::status_color() const noexcept { - if ((machine_flags() & MACHINE_ERRORS) || ((unemulated_features() | imperfect_features()) & device_t::feature::PROTECTION)) + if (has_severe_warnings()) return UI_RED_COLOR; - else if ((machine_flags() & MACHINE_WARNINGS) || unemulated_features() || imperfect_features()) + else if ((machine_flags() & MACHINE_WARNINGS & ~::machine_flags::REQUIRES_ARTWORK) || unemulated_features() || imperfect_features()) return UI_YELLOW_COLOR; else return UI_GREEN_COLOR; @@ -148,9 +387,9 @@ rgb_t machine_static_info::status_color() const // warning message based on severity //------------------------------------------------- -rgb_t machine_static_info::warnings_color() const +rgb_t machine_static_info::warnings_color() const noexcept { - if ((machine_flags() & MACHINE_ERRORS) || ((unemulated_features() | imperfect_features()) & device_t::feature::PROTECTION)) + if (has_severe_warnings()) return UI_RED_COLOR; else if ((machine_flags() & MACHINE_WARNINGS) || unemulated_features() || imperfect_features()) return UI_YELLOW_COLOR; @@ -183,109 +422,8 @@ machine_info::machine_info(running_machine &machine) std::string machine_info::warnings_string() const { std::ostringstream buf; - - // add a warning if any ROMs were loaded with warnings - if (m_machine.rom_load().warnings() > 0) - buf << _("One or more ROMs/CHDs for this machine are incorrect. The machine may not run correctly.\n"); - - if (!m_machine.rom_load().software_load_warnings_message().empty()) - buf << m_machine.rom_load().software_load_warnings_message(); - - // if we have at least one warning flag, print the general header - if ((m_machine.rom_load().knownbad() > 0) || (machine_flags() & (MACHINE_ERRORS | MACHINE_WARNINGS | MACHINE_BTANB)) || unemulated_features() || imperfect_features()) - { - if (!buf.str().empty()) - buf << '\n'; - buf << _("There are known problems with this machine\n\n"); - } - - // add a warning if any ROMs are flagged BAD_DUMP/NO_DUMP - if (m_machine.rom_load().knownbad() > 0) - buf << _("One or more ROMs/CHDs for this machine have not been correctly dumped.\n"); - - // add line for unemulated features - if (unemulated_features()) - { - buf << _("Completely unemulated features: "); - bool first = true; - for (auto const &feature : FEATURE_NAMES) - { - if (unemulated_features() & feature.first) - { - util::stream_format(buf, first ? _("%s") : _(", %s"), _(feature.second)); - first = false; - } - } - buf << '\n'; - } - - // add line for imperfect features - if (imperfect_features()) - { - buf << _("Imperfectly emulated features: "); - bool first = true; - for (auto const &feature : FEATURE_NAMES) - { - if (imperfect_features() & feature.first) - { - util::stream_format(buf, first ? _("%s") : _(", %s"), _(feature.second)); - first = false; - } - } - buf << '\n'; - } - - // add one line per machine warning flag - if (machine_flags() & ::machine_flags::NO_COCKTAIL) - buf << _("Screen flipping in cocktail mode is not supported.\n"); - if (machine_flags() & ::machine_flags::REQUIRES_ARTWORK) // check if external artwork is present before displaying this warning? - buf << _("This machine requires external artwork files.\n"); - if (machine_flags() & ::machine_flags::IS_INCOMPLETE ) - buf << _("This machine was never completed. It may exhibit strange behavior or missing elements that are not bugs in the emulation.\n"); - if (machine_flags() & ::machine_flags::NO_SOUND_HW ) - buf << _("This machine has no sound hardware, MAME will produce no sounds, this is expected behaviour.\n"); - - // these are more severe warnings - if (machine_flags() & ::machine_flags::NOT_WORKING) - buf << _("\nTHIS MACHINE DOESN'T WORK. The emulation for this machine is not yet complete. There is nothing you can do to fix this problem except wait for the developers to improve the emulation.\n"); - if (machine_flags() & ::machine_flags::MECHANICAL) - buf << _("\nElements of this machine cannot be emulated as they requires physical interaction or consist of mechanical devices. It is not possible to fully experience this machine.\n"); - - if ((machine_flags() & MACHINE_ERRORS) || ((m_machine.system().type.unemulated_features() | m_machine.system().type.imperfect_features()) & device_t::feature::PROTECTION)) - { - // find the parent of this driver - driver_enumerator drivlist(m_machine.options()); - int maindrv = drivlist.find(m_machine.system()); - int clone_of = drivlist.non_bios_clone(maindrv); - if (clone_of != -1) - maindrv = clone_of; - - // scan the driver list for any working clones and add them - bool foundworking = false; - while (drivlist.next()) - { - if (drivlist.current() == maindrv || drivlist.clone() == maindrv) - { - game_driver const &driver(drivlist.driver()); - if (!(driver.flags & MACHINE_ERRORS) && !((driver.type.unemulated_features() | driver.type.imperfect_features()) & device_t::feature::PROTECTION)) - { - // this one works, add a header and display the name of the clone - if (!foundworking) - util::stream_format(buf, _("\n\nThere are working clones of this machine: %s"), driver.name); - else - util::stream_format(buf, _(", %s"), driver.name); - foundworking = true; - } - } - } - if (foundworking) - buf << '\n'; - } - - // add the 'press OK' string - if (!buf.str().empty()) - buf << _("\n\nPress any key to continue"); - + get_general_warnings(buf, m_machine, machine_flags(), emulation_flags(), unemulated_features(), imperfect_features(), has_nonworking_devices()); + get_system_warnings(buf, m_machine, machine_flags(), emulation_flags(), unemulated_features(), imperfect_features(), has_nonworking_devices()); return buf.str(); } @@ -298,22 +436,29 @@ std::string machine_info::game_info_string() const { std::ostringstream buf; + // get decimal separator + std::string point; + { + wchar_t const s(std::use_facet<std::numpunct<wchar_t> >(std::locale()).decimal_point()); + point = utf8_from_wstring(std::wstring_view(&s, 1)); + } + // print description, manufacturer, and CPU: - util::stream_format(buf, _("%1$s\n%2$s %3$s\nDriver: %4$s\n\nCPU:\n"), - m_machine.system().type.fullname(), + util::stream_format(buf, _("%1$s\n%2$s %3$s\nSource file: %4$s\n\nCPU:\n"), + system_list::instance().systems()[driver_list::find(m_machine.system().name)].description, m_machine.system().year, m_machine.system().manufacturer, - core_filename_extract_base(m_machine.system().type.source())); + info_xml_creator::format_sourcefile(m_machine.system().type.source())); // loop over all CPUs - execute_interface_iterator execiter(m_machine.root_device()); + execute_interface_enumerator execiter(m_machine.root_device()); std::unordered_set<std::string> exectags; for (device_execute_interface &exec : execiter) { if (!exectags.insert(exec.device().tag()).second) continue; // get cpu specific clock that takes internal multiplier/dividers into account - int clock = exec.device().clock(); + u32 clock = exec.device().clock(); // count how many identical CPUs we have int count = 1; @@ -325,25 +470,32 @@ std::string machine_info::game_info_string() const count++; } - // if more than one, prepend a #x in front of the CPU name - // display clock in kHz or MHz + std::string hz(std::to_string(clock)); + int d = (clock >= 1'000'000'000) ? 9 : (clock >= 1'000'000) ? 6 : (clock >= 1000) ? 3 : 0; + if (d > 0) + { + size_t dpos = hz.length() - d; + hz.insert(dpos, point); + size_t last = hz.find_last_not_of('0'); + hz = hz.substr(0, last + (last != dpos ? 1 : 0)); + } + + // if more than one, prepend a #x in front of the CPU name and display clock util::stream_format(buf, - (count > 1) ? "%1$d" UTF8_MULTIPLY "%2$s %3$d.%4$0*5$d%6$s\n" : "%2$s %3$d.%4$0*5$d%6$s\n", - count, - name, - (clock >= 1000000) ? (clock / 1000000) : (clock / 1000), - (clock >= 1000000) ? (clock % 1000000) : (clock % 1000), - (clock >= 1000000) ? 6 : 3, - (clock >= 1000000) ? _("MHz") : _("kHz")); + (count > 1) + ? ((clock != 0) ? u8"%1$d×%2$s %3$s\u00a0%4$s\n" : u8"%1$d×%2$s\n") + : ((clock != 0) ? u8"%2$s %3$s\u00a0%4$s\n" : "%2$s\n"), + count, name, hz, + (d == 9) ? _("GHz") : (d == 6) ? _("MHz") : (d == 3) ? _("kHz") : _("Hz")); } // loop over all sound chips - sound_interface_iterator snditer(m_machine.root_device()); + sound_interface_enumerator snditer(m_machine.root_device()); std::unordered_set<std::string> soundtags; bool found_sound = false; for (device_sound_interface &sound : snditer) { - if (!sound.issound() || !soundtags.insert(sound.device().tag()).second) + if (!soundtags.insert(sound.device().tag()).second) continue; // append the Sound: string @@ -360,24 +512,29 @@ std::string machine_info::game_info_string() const count++; } - // if more than one, prepend a #x in front of the CPU name - // display clock in kHz or MHz - int clock = sound.device().clock(); + const u32 clock = sound.device().clock(); + std::string hz(std::to_string(clock)); + int d = (clock >= 1'000'000'000) ? 9 : (clock >= 1'000'000) ? 6 : (clock >= 1000) ? 3 : 0; + if (d > 0) + { + size_t dpos = hz.length() - d; + hz.insert(dpos, point); + size_t last = hz.find_last_not_of('0'); + hz = hz.substr(0, last + (last != dpos ? 1 : 0)); + } + + // if more than one, prepend a #x in front of the soundchip name and display clock util::stream_format(buf, (count > 1) - ? ((clock != 0) ? "%1$d" UTF8_MULTIPLY "%2$s %3$d.%4$0*5$d%6$s\n" : "%1$d" UTF8_MULTIPLY "%2$s\n") - : ((clock != 0) ? "%2$s %3$d.%4$0*5$d%6$s\n" : "%2$s\n"), - count, - sound.device().name(), - (clock >= 1000000) ? (clock / 1000000) : (clock / 1000), - (clock >= 1000000) ? (clock % 1000000) : (clock % 1000), - (clock >= 1000000) ? 6 : 3, - (clock >= 1000000) ? _("MHz") : _("kHz")); + ? ((clock != 0) ? u8"%1$d×%2$s %3$s\u00a0%4$s\n" : u8"%1$d×%2$s\n") + : ((clock != 0) ? u8"%2$s %3$s\u00a0%4$s\n" : "%2$s\n"), + count, sound.device().name(), hz, + (d == 9) ? _("GHz") : (d == 6) ? _("MHz") : (d == 3) ? _("kHz") : _("Hz")); } // display screen information buf << _("\nVideo:\n"); - screen_device_iterator scriter(m_machine.root_device()); + screen_device_enumerator scriter(m_machine.root_device()); int scrcount = scriter.count(); if (scrcount == 0) buf << _("None\n"); @@ -390,11 +547,22 @@ std::string machine_info::game_info_string() const detail = _("Vector"); else { + const u32 rate = u32(screen.frame_period().as_hz() * 1'000'000 + 0.5); + const bool valid = rate >= 1'000'000; + std::string hz(valid ? std::to_string(rate) : "?"); + if (valid) + { + size_t dpos = hz.length() - 6; + hz.insert(dpos, point); + size_t last = hz.find_last_not_of('0'); + hz = hz.substr(0, last + (last != dpos ? 1 : 0)); + } + const rectangle &visarea = screen.visible_area(); - detail = string_format("%d " UTF8_MULTIPLY " %d (%s) %f" UTF8_NBSP "Hz", + detail = string_format(u8"%d × %d (%s) %s\u00a0Hz", visarea.width(), visarea.height(), (screen.orientation() & ORIENTATION_SWAP_XY) ? "V" : "H", - screen.frame_period().as_hz()); + hz); } util::stream_format(buf, @@ -414,7 +582,7 @@ std::string machine_info::game_info_string() const std::string machine_info::get_screen_desc(screen_device &screen) const { - if (screen_device_iterator(m_machine.root_device()).count() > 1) + if (screen_device_enumerator(m_machine.root_device()).count() > 1) return string_format(_("Screen '%1$s'"), screen.tag()); else return _("Screen"); @@ -423,57 +591,127 @@ std::string machine_info::get_screen_desc(screen_device &screen) const /*------------------------------------------------- - menu_game_info - handle the game information - menu - -------------------------------------------------*/ + menu_game_info - handle the game information menu +-------------------------------------------------*/ -menu_game_info::menu_game_info(mame_ui_manager &mui, render_container &container) : menu(mui, container) +menu_game_info::menu_game_info(mame_ui_manager &mui, render_container &container) : menu_textbox(mui, container) { + set_process_flags(PROCESS_CUSTOM_NAV); } menu_game_info::~menu_game_info() { } -void menu_game_info::populate(float &customtop, float &custombottom) +void menu_game_info::menu_activated() +{ + // screen modes can be reconfigured while the menu isn't displayed, etc. + reset_layout(); +} + +void menu_game_info::populate_text(std::optional<text_layout> &layout, float &width, int &lines) { - std::string tempstring = ui().machine_info().game_info_string(); - item_append(std::move(tempstring), "", FLAG_MULTILINE, nullptr); + if (!layout || (layout->width() != width)) + { + rgb_t const color = ui().colors().text_color(); + layout.emplace(create_layout(width)); + layout->add_text(ui().machine_info().game_info_string(), color); + lines = layout->lines(); + } + width = layout->actual_width(); } -void menu_game_info::handle() +void menu_game_info::populate() { - // process the menu - process(0); } /*------------------------------------------------- - menu_image_info - handle the image information - menu - -------------------------------------------------*/ + menu_warn_info - handle the emulation warnings menu +-------------------------------------------------*/ + +menu_warn_info::menu_warn_info(mame_ui_manager &mui, render_container &container) : menu_textbox(mui, container) +{ + set_process_flags(PROCESS_CUSTOM_NAV); +} + +menu_warn_info::~menu_warn_info() +{ +} + +void menu_warn_info::populate_text(std::optional<text_layout> &layout, float &width, int &lines) +{ + if (!layout || (layout->width() != width)) + { + std::ostringstream buf; + std::set<std::add_pointer_t<device_type> > seen; + bool first(!machine().rom_load().knownbad()); + + machine_info const &info(ui().machine_info()); + device_t &root(machine().root_device()); + get_general_warnings(buf, machine(), info.machine_flags(), info.emulation_flags(), info.unemulated_features(), info.imperfect_features(), info.has_nonworking_devices()); + if ((info.machine_flags() & (MACHINE_ERRORS | MACHINE_WARNINGS | MACHINE_BTANB)) || (root.type().emulation_flags() & DEVICE_ERRORS) || root.type().unemulated_features() || root.type().imperfect_features()) + { + seen.insert(&root.type()); + if (!first) + buf << '\n'; + first = false; + util::stream_format(buf, _("%1$s:\n"), root.name()); + get_system_warnings(buf, machine(), info.machine_flags(), root.type().emulation_flags(), root.type().unemulated_features(), root.type().imperfect_features(), false); + } + + for (device_t const &device : device_enumerator(root)) + { + if (((device.type().emulation_flags() & DEVICE_ERRORS) || device.type().unemulated_features() || device.type().imperfect_features()) && seen.insert(&device.type()).second) + { + if (!first) + buf << '\n'; + first = false; + util::stream_format(buf, _("%1$s:\n"), device.name()); + get_device_warnings(buf, device.type().emulation_flags(), device.type().unemulated_features(), device.type().imperfect_features()); + } + } + + rgb_t const color(ui().colors().text_color()); + layout.emplace(create_layout(width)); + layout->add_text(std::move(buf).str(), color); + lines = layout->lines(); + } + width = layout->actual_width(); +} + +void menu_warn_info::populate() +{ +} + + +/*------------------------------------------------- + menu_image_info - handle the image information menu +-------------------------------------------------*/ menu_image_info::menu_image_info(mame_ui_manager &mui, render_container &container) : menu(mui, container) { + set_heading(_("Media Image Information")); } menu_image_info::~menu_image_info() { } -void menu_image_info::populate(float &customtop, float &custombottom) +void menu_image_info::menu_activated() { - item_append(machine().system().type.fullname(), "", FLAG_DISABLE, nullptr); - item_append("", "", FLAG_DISABLE, nullptr); + reset(reset_options::REMEMBER_POSITION); +} - for (device_image_interface &image : image_interface_iterator(machine().root_device())) - image_info(&image); +void menu_image_info::populate() +{ + for (device_image_interface &image : image_interface_enumerator(machine().root_device())) + image_info(image); } -void menu_image_info::handle() +bool menu_image_info::handle(event const *ev) { - // process the menu - process(0); + return false; } @@ -482,39 +720,57 @@ void menu_image_info::handle() image interface device -------------------------------------------------*/ -void menu_image_info::image_info(device_image_interface *image) +void menu_image_info::image_info(device_image_interface &image) { - if (image->exists()) + if (!image.user_loadable()) + return; + + m_notifiers.emplace_back(image.add_media_change_notifier(delegate(&menu_image_info::reload, this))); + + if (image.exists()) { // display device type and filename - item_append(image->brief_instance_name(), image->basename(), 0, nullptr); + item_append(image.brief_instance_name(), image.basename(), 0, &image); // if image has been loaded through softlist, let's add some more info - if (image->loaded_through_softlist()) + if (image.loaded_through_softlist()) { - // display long filename - item_append(image->longname(), "", FLAG_DISABLE, nullptr); + software_info const &swinfo(*image.software_entry()); - // display manufacturer and year - item_append(string_format("%s, %s", image->manufacturer(), image->year()), "", FLAG_DISABLE, nullptr); + // display full name, publisher and year + item_append(swinfo.longname(), FLAG_DISABLE, nullptr); + item_append(string_format("%1$s, %2$s", swinfo.publisher(), swinfo.year()), FLAG_DISABLE, nullptr); // display supported information, if available - switch (image->supported()) + switch (swinfo.supported()) { - case SOFTWARE_SUPPORTED_NO: - item_append(_("Not supported"), "", FLAG_DISABLE, nullptr); - break; - case SOFTWARE_SUPPORTED_PARTIAL: - item_append(_("Partially supported"), "", FLAG_DISABLE, nullptr); - break; - default: - break; + case software_support::UNSUPPORTED: + item_append(_("Not supported"), FLAG_DISABLE, nullptr); + break; + case software_support::PARTIALLY_SUPPORTED: + item_append(_("Partially supported"), FLAG_DISABLE, nullptr); + break; + case software_support::SUPPORTED: + break; } } } else - item_append(image->brief_instance_name(), _("[empty]"), 0, nullptr); - item_append("", "", FLAG_DISABLE, nullptr); + { + item_append(image.brief_instance_name(), _("[empty]"), 0, &image); + } + item_append(menu_item_type::SEPARATOR); +} + + +/*------------------------------------------------- + reload - refresh the menu after a media change +-------------------------------------------------*/ + +void menu_image_info::reload(device_image_interface::media_change_event ev) +{ + m_notifiers.clear(); + reset(reset_options::REMEMBER_REF); } } // namespace ui diff --git a/src/frontend/mame/ui/info.h b/src/frontend/mame/ui/info.h index a3746ce6b03..49e67dea5ae 100644 --- a/src/frontend/mame/ui/info.h +++ b/src/frontend/mame/ui/info.h @@ -13,7 +13,13 @@ #pragma once -#include "ui/menu.h" +#include "ui/textbox.h" + +#include "notifier.h" + +#include <string> +#include <vector> + namespace ui { @@ -24,23 +30,27 @@ public: machine_static_info(const ui_options &options, machine_config const &config); // overall emulation status - ::machine_flags::type machine_flags() const { return m_flags; } - device_t::feature_type unemulated_features() const { return m_unemulated_features; } - device_t::feature_type imperfect_features() const { return m_imperfect_features; } + ::machine_flags::type machine_flags() const noexcept { return m_flags; } + device_t::flags_type emulation_flags() const noexcept { return m_emulation_flags; } + device_t::feature_type unemulated_features() const noexcept { return m_unemulated_features; } + device_t::feature_type imperfect_features() const noexcept { return m_imperfect_features; } // has... getters - bool has_bioses() const { return m_has_bioses; } + bool has_nonworking_devices() const noexcept { return m_has_nonworking_devices; } + bool has_bioses() const noexcept { return m_has_bioses; } // has input types getters - bool has_dips() const { return m_has_dips; } - bool has_configs() const { return m_has_configs; } - bool has_keyboard() const { return m_has_keyboard; } - bool has_test_switch() const { return m_has_test_switch; } - bool has_analog() const { return m_has_analog; } - - // message colour - rgb_t status_color() const; - rgb_t warnings_color() const; + bool has_dips() const noexcept { return m_has_dips; } + bool has_configs() const noexcept { return m_has_configs; } + bool has_keyboard() const noexcept { return m_has_keyboard; } + bool has_test_switch() const noexcept { return m_has_test_switch; } + bool has_analog() const noexcept { return m_has_analog; } + + // warning severity indications + bool has_warnings() const noexcept; + bool has_severe_warnings() const noexcept; + rgb_t status_color() const noexcept; + rgb_t warnings_color() const noexcept; protected: machine_static_info(const ui_options &options, machine_config const &config, ioport_list const &ports); @@ -52,10 +62,12 @@ private: // overall feature status ::machine_flags::type m_flags; + device_t::flags_type m_emulation_flags; device_t::feature_type m_unemulated_features; device_t::feature_type m_imperfect_features; // has... + bool m_has_nonworking_devices; bool m_has_bioses; // has input types @@ -84,15 +96,32 @@ private: }; -class menu_game_info : public menu +class menu_game_info : public menu_textbox { public: menu_game_info(mame_ui_manager &mui, render_container &container); virtual ~menu_game_info() override; +protected: + virtual void menu_activated() override; + virtual void populate_text(std::optional<text_layout> &layout, float &width, int &lines) override; + +private: + virtual void populate() override; +}; + + +class menu_warn_info : public menu_textbox +{ +public: + menu_warn_info(mame_ui_manager &mui, render_container &container); + virtual ~menu_warn_info() override; + +protected: + virtual void populate_text(std::optional<text_layout> &layout, float &width, int &lines) override; + private: - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; }; @@ -102,10 +131,16 @@ public: menu_image_info(mame_ui_manager &mui, render_container &container); virtual ~menu_image_info() override; +protected: + virtual void menu_activated() override; + private: - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; - void image_info(device_image_interface *image); + virtual void populate() override; + virtual bool handle(event const *ev) override; + void image_info(device_image_interface &image); + void reload(device_image_interface::media_change_event ev); + + std::vector<util::notifier_subscription> m_notifiers; }; } // namespace ui diff --git a/src/frontend/mame/ui/info_pty.cpp b/src/frontend/mame/ui/info_pty.cpp index deed38ae932..6aa4826e241 100644 --- a/src/frontend/mame/ui/info_pty.cpp +++ b/src/frontend/mame/ui/info_pty.cpp @@ -19,31 +19,29 @@ namespace ui { menu_pty_info::menu_pty_info(mame_ui_manager &mui, render_container &container) : menu(mui, container) { + set_heading(_("Pseudo Terminals")); } menu_pty_info::~menu_pty_info() { } -void menu_pty_info::populate(float &customtop, float &custombottom) +void menu_pty_info::populate() { - item_append(_("Pseudo terminals"), "", FLAG_DISABLE, nullptr); - item_append("", "", FLAG_DISABLE, nullptr); - - for (device_pty_interface &pty : pty_interface_iterator(machine().root_device())) + for (device_pty_interface &pty : pty_interface_enumerator(machine().root_device())) { const char *port_name = pty.device().owner()->tag() + 1; if (pty.is_open()) item_append(port_name, pty.slave_name(), FLAG_DISABLE, nullptr); else item_append(port_name, _("[failed]"), FLAG_DISABLE, nullptr); - item_append("", "", FLAG_DISABLE, nullptr); + item_append(std::string(), FLAG_DISABLE, nullptr); } } -void menu_pty_info::handle() +bool menu_pty_info::handle(event const *ev) { - process(0); + return false; } } // namespace ui diff --git a/src/frontend/mame/ui/info_pty.h b/src/frontend/mame/ui/info_pty.h index 55249a34d34..203209fddc4 100644 --- a/src/frontend/mame/ui/info_pty.h +++ b/src/frontend/mame/ui/info_pty.h @@ -16,6 +16,7 @@ #include "ui/menu.h" namespace ui { + class menu_pty_info : public menu { public: @@ -23,8 +24,8 @@ public: virtual ~menu_pty_info() 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; }; } // namespace ui diff --git a/src/frontend/mame/ui/inifile.cpp b/src/frontend/mame/ui/inifile.cpp index b68be00122b..5e4be6502ef 100644 --- a/src/frontend/mame/ui/inifile.cpp +++ b/src/frontend/mame/ui/inifile.cpp @@ -13,12 +13,19 @@ #include "ui/moptions.h" +#include "language.h" + #include "drivenum.h" +#include "fileio.h" #include "softlist_dev.h" +#include "corestr.h" +#include "path.h" + #include <algorithm> #include <cstring> #include <iterator> +#include <locale> namespace { @@ -44,14 +51,25 @@ inifile_manager::inifile_manager(ui_options &options) if (core_filename_ends_with(name, ".ini")) { emu_file file(m_options.categoryini_path(), OPEN_FLAG_READ); - if (file.open(name) == osd_file::error::NONE) + if (!file.open(name)) { init_category(std::move(name), file); file.close(); } } } - std::stable_sort(m_ini_index.begin(), m_ini_index.end(), [] (auto const &x, auto const &y) { return 0 > core_stricmp(x.first.c_str(), y.first.c_str()); }); + std::locale const lcl; + std::collate<wchar_t> const &coll = std::use_facet<std::collate<wchar_t> >(lcl); + std::stable_sort( + m_ini_index.begin(), + m_ini_index.end(), + [&coll] (auto const &x, auto const &y) + { + std::wstring const wx = wstring_from_utf8(x.first); + std::wstring const wy = wstring_from_utf8(y.first); + return 0 > coll.compare(wx.data(), wx.data() + wx.size(), wy.data(), wy.data() + wy.size()); + } + ); } //------------------------------------------------- @@ -62,9 +80,9 @@ void inifile_manager::load_ini_category(size_t file, size_t category, std::unord { std::string const &filename(m_ini_index[file].first); emu_file fp(m_options.categoryini_path(), OPEN_FLAG_READ); - if (fp.open(filename) != osd_file::error::NONE) + if (fp.open(filename)) { - osd_printf_error("Failed to open category file %s for reading\n", filename.c_str()); + osd_printf_error("Failed to open category file %s for reading\n", filename); return; } @@ -72,7 +90,7 @@ void inifile_manager::load_ini_category(size_t file, size_t category, std::unord if (fp.seek(offset, SEEK_SET) || (fp.tell() != offset)) { fp.close(); - osd_printf_error("Failed to seek to category offset in file %s\n", filename.c_str()); + osd_printf_error("Failed to seek to category offset in file %s\n", filename); return; } @@ -93,12 +111,12 @@ void inifile_manager::load_ini_category(size_t file, size_t category, std::unord // initialize category //------------------------------------------------- -void inifile_manager::init_category(std::string &&filename, emu_file &file) +void inifile_manager::init_category(std::string &&filename, util::core_file &file) { categoryindex index; char rbuf[MAX_CHAR_INFO]; std::string name; - while (file.gets(rbuf, ARRAY_LENGTH(rbuf))) + while (file.gets(rbuf, std::size(rbuf))) { if ('[' == rbuf[0]) { @@ -106,12 +124,29 @@ void inifile_manager::init_category(std::string &&filename, emu_file &file) auto const tail(std::find_if(head, std::end(rbuf), [] (char ch) { return !ch || (']' == ch); })); name.assign(head, tail); if ("FOLDER_SETTINGS" != name) - index.emplace_back(std::move(name), file.tell()); + { + u64 result; + if (!file.tell(result)) + index.emplace_back(std::move(name), result); + } } } - std::stable_sort(index.begin(), index.end(), [] (auto const &x, auto const &y) { return 0 > core_stricmp(x.first.c_str(), y.first.c_str()); }); if (!index.empty()) + { + std::locale const lcl; + std::collate<wchar_t> const &coll = std::use_facet<std::collate<wchar_t> >(lcl); + std::stable_sort( + index.begin(), + index.end(), + [&coll] (auto const &x, auto const &y) + { + std::wstring const wx = wstring_from_utf8(x.first); + std::wstring const wy = wstring_from_utf8(y.first); + return 0 > coll.compare(wx.data(), wx.data() + wx.size(), wy.data(), wy.data() + wy.size()); + } + ); m_ini_index.emplace_back(std::move(filename), std::move(index)); + } } @@ -142,7 +177,7 @@ bool favorite_manager::favorite_compare::operator()(ui_software_info const &lhs, return true; } - return 0 > std::strncmp(lhs.driver->name, rhs.driver->name, ARRAY_LENGTH(lhs.driver->name)); + return 0 > std::strncmp(lhs.driver->name, rhs.driver->name, std::size(lhs.driver->name)); } bool favorite_manager::favorite_compare::operator()(ui_software_info const &lhs, game_driver const &rhs) const @@ -152,7 +187,7 @@ bool favorite_manager::favorite_compare::operator()(ui_software_info const &lhs, if (!lhs.startempty) return false; else - return 0 > std::strncmp(lhs.driver->name, rhs.name, ARRAY_LENGTH(rhs.name)); + return 0 > std::strncmp(lhs.driver->name, rhs.name, std::size(rhs.name)); } bool favorite_manager::favorite_compare::operator()(game_driver const &lhs, ui_software_info const &rhs) const @@ -162,7 +197,7 @@ bool favorite_manager::favorite_compare::operator()(game_driver const &lhs, ui_s if (!rhs.startempty) return true; else - return 0 > std::strncmp(lhs.name, rhs.driver->name, ARRAY_LENGTH(lhs.name)); + return 0 > std::strncmp(lhs.name, rhs.driver->name, std::size(lhs.name)); } bool favorite_manager::favorite_compare::operator()(ui_software_info const &lhs, running_software_key const &rhs) const @@ -181,7 +216,7 @@ bool favorite_manager::favorite_compare::operator()(ui_software_info const &lhs, else if (lhs.shortname > std::get<2>(rhs)) return false; else - return 0 > std::strncmp(lhs.driver->name, std::get<0>(rhs).name, ARRAY_LENGTH(lhs.driver->name)); + return 0 > std::strncmp(lhs.driver->name, std::get<0>(rhs).name, std::size(lhs.driver->name)); } bool favorite_manager::favorite_compare::operator()(running_software_key const &lhs, ui_software_info const &rhs) const @@ -200,7 +235,7 @@ bool favorite_manager::favorite_compare::operator()(running_software_key const & else if (std::get<2>(lhs) > rhs.shortname) return false; else - return 0 > std::strncmp(std::get<0>(lhs).name, rhs.driver->name, ARRAY_LENGTH(rhs.driver->name)); + return 0 > std::strncmp(std::get<0>(lhs).name, rhs.driver->name, std::size(rhs.driver->name)); } @@ -215,52 +250,62 @@ favorite_manager::favorite_manager(ui_options &options) , m_need_sort(true) { emu_file file(m_options.ui_path(), OPEN_FLAG_READ); - if (file.open(FAVORITE_FILENAME) == osd_file::error::NONE) + if (!file.open(FAVORITE_FILENAME)) { char readbuf[1024]; - file.gets(readbuf, 1024); + file.gets(readbuf, std::size(readbuf)); while (readbuf[0] == '[') - file.gets(readbuf, 1024); + file.gets(readbuf, std::size(readbuf)); - while (file.gets(readbuf, 1024)) + while (file.gets(readbuf, std::size(readbuf))) { ui_software_info tmpmatches; tmpmatches.shortname = chartrimcarriage(readbuf); - file.gets(readbuf, 1024); + file.gets(readbuf, std::size(readbuf)); tmpmatches.longname = chartrimcarriage(readbuf); - file.gets(readbuf, 1024); + file.gets(readbuf, std::size(readbuf)); tmpmatches.parentname = chartrimcarriage(readbuf); - file.gets(readbuf, 1024); + file.gets(readbuf, std::size(readbuf)); tmpmatches.year = chartrimcarriage(readbuf); - file.gets(readbuf, 1024); + file.gets(readbuf, std::size(readbuf)); tmpmatches.publisher = chartrimcarriage(readbuf); - file.gets(readbuf, 1024); - tmpmatches.supported = atoi(readbuf); - file.gets(readbuf, 1024); + file.gets(readbuf, std::size(readbuf)); + tmpmatches.supported = software_support(atoi(readbuf)); + file.gets(readbuf, std::size(readbuf)); tmpmatches.part = chartrimcarriage(readbuf); - file.gets(readbuf, 1024); + file.gets(readbuf, std::size(readbuf)); chartrimcarriage(readbuf); auto dx = driver_list::find(readbuf); if (0 > dx) continue; tmpmatches.driver = &driver_list::driver(dx); - file.gets(readbuf, 1024); + file.gets(readbuf, std::size(readbuf)); tmpmatches.listname = chartrimcarriage(readbuf); - file.gets(readbuf, 1024); + file.gets(readbuf, std::size(readbuf)); tmpmatches.interface = chartrimcarriage(readbuf); - file.gets(readbuf, 1024); + file.gets(readbuf, std::size(readbuf)); tmpmatches.instance = chartrimcarriage(readbuf); - file.gets(readbuf, 1024); + file.gets(readbuf, std::size(readbuf)); tmpmatches.startempty = atoi(readbuf); - file.gets(readbuf, 1024); + file.gets(readbuf, std::size(readbuf)); tmpmatches.parentlongname = chartrimcarriage(readbuf); - file.gets(readbuf, 1024); - tmpmatches.usage = chartrimcarriage(readbuf); - file.gets(readbuf, 1024); + file.gets(readbuf, std::size(readbuf)); + //tmpmatches.usage = chartrimcarriage(readbuf); TODO: recover multi-line info + file.gets(readbuf, std::size(readbuf)); tmpmatches.devicetype = chartrimcarriage(readbuf); - file.gets(readbuf, 1024); + file.gets(readbuf, std::size(readbuf)); tmpmatches.available = atoi(readbuf); + + // need to populate this, it isn't displayed anywhere else + tmpmatches.infotext.append(tmpmatches.longname); + tmpmatches.infotext.append(1, '\n'); + tmpmatches.infotext.append(_("swlist-info", "Software list/item")); + tmpmatches.infotext.append(1, '\n'); + tmpmatches.infotext.append(tmpmatches.listname); + tmpmatches.infotext.append(1, ':'); + tmpmatches.infotext.append(tmpmatches.shortname); + m_favorites.emplace(std::move(tmpmatches)); } file.close(); @@ -291,25 +336,18 @@ void favorite_manager::add_favorite(running_machine &machine) if (imagedev) { // creating this is fairly expensive, but we'll assume this usually succeeds - ui_software_info info; software_part const *const part(imagedev->part_entry()); assert(software); assert(part); - - // start with simple stuff that can just be copied - info.shortname = software->shortname(); - info.longname = imagedev->longname(); - info.parentname = software->parentname(); - info.year = imagedev->year(); - info.publisher = imagedev->manufacturer(); - info.supported = imagedev->supported(); - info.part = part->name(); - info.driver = &driver; - info.listname = imagedev->software_list_name(); - info.interface = part->interface(); - info.instance = imagedev->instance_name(); - info.startempty = 0; - info.devicetype = strensure(imagedev->image_type_name()); + ui_software_info info( + *software, + *part, + driver, + imagedev->software_list_name(), + imagedev->instance_name(), + imagedev->image_type_name() ? imagedev->image_type_name() : ""); + + // assume it's available if it's mounted info.available = true; // look up the parent in the list if necessary (eugh, O(n) walk) @@ -327,16 +365,6 @@ void favorite_manager::add_favorite(running_machine &machine) } } - // fill in with the first usage entry we find - for (feature_list_item const &feature : software->other_info()) - { - if (feature.name() == "usage") - { - info.usage = feature.value(); - break; - } - } - // hooray for move semantics! add_impl(std::move(info)); } @@ -462,31 +490,23 @@ void favorite_manager::apply_running_machine(running_machine &machine, T &&actio { bool done(false); - // TODO: this should be changed - it interacts poorly with cartslots on arcade systems - if ((machine.system().flags & machine_flags::MASK_TYPE) == machine_flags::TYPE_ARCADE) - { - action(machine.system(), nullptr, nullptr, done); - } - else + bool have_software(false); + for (device_image_interface &image_dev : image_interface_enumerator(machine.root_device())) { - bool have_software(false); - for (device_image_interface &image_dev : image_interface_iterator(machine.root_device())) + software_info const *const sw(image_dev.software_entry()); + if (image_dev.exists() && image_dev.loaded_through_softlist() && sw) { - software_info const *const sw(image_dev.software_entry()); - if (image_dev.exists() && image_dev.loaded_through_softlist() && sw) - { - assert(image_dev.software_list_name()); + assert(image_dev.software_list_name()); - have_software = true; - action(machine.system(), &image_dev, sw, done); - if (done) - return; - } + have_software = true; + action(machine.system(), &image_dev, sw, done); + if (done) + return; } - - if (!have_software) - action(machine.system(), nullptr, nullptr, done); } + + if (!have_software) + action(machine.system(), nullptr, nullptr, done); } void favorite_manager::update_sorted() @@ -507,7 +527,7 @@ void favorite_manager::update_sorted() int cmp; - cmp = core_stricmp(lhs.longname.c_str(), rhs.longname.c_str()); + cmp = core_stricmp(lhs.longname, rhs.longname); if (0 > cmp) return true; else if (0 < cmp) @@ -536,7 +556,7 @@ void favorite_manager::save_favorites() { // attempt to open the output file emu_file file(m_options.ui_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); - if (file.open(FAVORITE_FILENAME) == osd_file::error::NONE) + if (!file.open(FAVORITE_FILENAME)) { if (m_favorites.empty()) { @@ -558,7 +578,7 @@ void favorite_manager::save_favorites() buf << info.parentname << '\n'; buf << info.year << '\n'; buf << info.publisher << '\n'; - util::stream_format(buf, "%d\n", info.supported); + util::stream_format(buf, "%d\n", int(info.supported)); buf << info.part << '\n'; util::stream_format(buf, "%s\n", info.driver->name); buf << info.listname << '\n'; @@ -566,12 +586,11 @@ void favorite_manager::save_favorites() buf << info.instance << '\n'; util::stream_format(buf, "%d\n", info.startempty); buf << info.parentlongname << '\n'; - buf << info.usage << '\n'; + buf << '\n'; //buf << info.usage << '\n'; TODO: store multi-line info in a recoverable format buf << info.devicetype << '\n'; util::stream_format(buf, "%d\n", info.available); - buf.put('\0'); - file.puts(&buf.vec()[0]); + file.puts(util::buf_to_string_view(buf)); } } file.close(); diff --git a/src/frontend/mame/ui/inifile.h b/src/frontend/mame/ui/inifile.h index 68adca3c917..68abaac566a 100644 --- a/src/frontend/mame/ui/inifile.h +++ b/src/frontend/mame/ui/inifile.h @@ -46,7 +46,7 @@ private: // ini file structure using categoryindex = std::vector<std::pair<std::string, int64_t>>; - void init_category(std::string &&filename, emu_file &file); + void init_category(std::string &&filename, util::core_file &file); // internal state ui_options &m_options; diff --git a/src/frontend/mame/ui/inputdevices.cpp b/src/frontend/mame/ui/inputdevices.cpp new file mode 100644 index 00000000000..8f006ea55e7 --- /dev/null +++ b/src/frontend/mame/ui/inputdevices.cpp @@ -0,0 +1,310 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + ui/inputdevices.cpp + + Input devices menu. + +***************************************************************************/ + +#include "emu.h" +#include "inputdevices.h" + +#include "inputdev.h" + +// FIXME: allow OSD module headers to be included in a less ugly way +#include "../osd/modules/lib/osdlib.h" + + +namespace ui { + +namespace { + +class menu_input_device : public menu +{ +public: + menu_input_device(mame_ui_manager &mui, render_container &container, input_device &device) + : menu(mui, container) + , m_device(device) + , m_have_analog(false) + { + set_heading( + util::string_format(_("menu-inputdev", "%1$s (%2$s %3$d)"), + device.name(), + machine().input().device_class(device.devclass()).name(), + device.devindex() + 1)); + } + +protected: + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override + { + menu::recompute_metrics(width, height, aspect); + + set_custom_space(0.0F, (line_height() * (m_have_analog ? 2.0F : 1.0F)) + (tb_border() * 3.0F)); + } + + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) override + { + if (selectedref) + { + // get the complete token for the highlighted input + input_device_item &input = *reinterpret_cast<input_device_item *>(selectedref); + input_code code = input.code(); + if (!machine().input().device_class(m_device.devclass()).multi()) + code.set_device_index(0); + std::string const token = machine().input().code_to_token(code); + + // measure the name of the token string + float const tokenwidth = (std::min)(get_string_width(token) + (gutter_width() * 2.0F), 1.0F); + float const boxwidth = (std::max)(tokenwidth, origx2 - origx1); + rgb_t const fgcolor(ui().colors().text_color()); + + // draw the outer box + ui().draw_outlined_box( + container(), + (1.0F - boxwidth) * 0.5F, origy2 + tb_border(), + (1.0F + boxwidth) * 0.5F, origy2 + bottom, + ui().colors().background_color()); + + // show the token + draw_text_normal( + token, + (1.0F - boxwidth) * 0.5F, origy2 + (tb_border() * 2.0F), boxwidth, + text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, + fgcolor); + + // first show the token + switch (input.itemclass()) + { + case ITEM_CLASS_ABSOLUTE: + case ITEM_CLASS_RELATIVE: + { + // draw the indicator + float const indleft = origx1 + gutter_width(); + float const indright = origx2 - gutter_width(); + float const indtop = origy2 + (tb_border() * 2.0F) + (line_height() * 1.2F); + float const indbottom = origy2 + (tb_border() * 2.0F) + (line_height() * 1.8F); + float const indcentre = (origx1 + origx2) * 0.5F; + s32 const value = (input.itemclass() == ITEM_CLASS_ABSOLUTE) ? input.read_as_absolute(ITEM_MODIFIER_NONE) : input.read_as_relative(ITEM_MODIFIER_NONE); + if (0 < value) + { + float const fillright = indcentre + (float(value) / float(osd::input_device::ABSOLUTE_MAX) * (indright - indcentre)); + container().add_rect(indcentre, indtop, (std::min)(fillright, indright), indbottom, fgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + } + else if (0 > value) + { + float const fillleft = indcentre - (float(value) / float(osd::input_device::ABSOLUTE_MIN) * (indcentre - indleft)); + container().add_rect((std::max)(fillleft, indleft), indtop, indcentre, indbottom, fgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + } + container().add_line(indleft, indtop, indright, indtop, UI_LINE_WIDTH, fgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + container().add_line(indright, indtop, indright, indbottom, UI_LINE_WIDTH, fgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + container().add_line(indright, indbottom, indleft, indbottom, UI_LINE_WIDTH, fgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + container().add_line(indleft, indbottom, indleft, indtop, UI_LINE_WIDTH, fgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + container().add_line(indcentre, indtop, indcentre, indbottom, UI_LINE_WIDTH, fgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + } + break; + default: + break; + } + } + } + +private: + virtual void populate() override + { + item_append(_("menu-inputdev", "Copy Device ID"), 0U, nullptr); + item_append(menu_item_type::SEPARATOR); + + for (input_item_id itemid = ITEM_ID_FIRST_VALID; m_device.maxitem() >= itemid; ++itemid) + { + input_device_item *const input = m_device.item(itemid); + if (input) + { + switch (input->itemclass()) + { + case ITEM_CLASS_ABSOLUTE: + case ITEM_CLASS_RELATIVE: + m_have_analog = true; + break; + default: + break; + } + item_append(input->name(), format_value(*input), 0U, input); + } + } + + item_append(menu_item_type::SEPARATOR); + + set_custom_space(0.0F, (line_height() * (m_have_analog ? 2.0F : 1.0F)) + (tb_border() * 3.0F)); + } + + virtual bool handle(event const *ev) override + { + // FIXME: hacky, depending on first item being "copy ID", but need a better model for item reference values + if (ev && ev->item && (IPT_UI_SELECT == ev->iptkey) && (&item(0) == ev->item)) + { + if (!osd_set_clipboard_text(m_device.id())) + machine().popmessage(_("menu-inputdev", "Copied device ID to clipboard")); + else + machine().popmessage(_("menu-inputdev", "Error copying device ID to clipboard")); + } + + bool updated = false; + for (int i = 0; item_count() > i; ++i) + { + void *const ref(item(i).ref()); + if (ref) + { + input_device_item &input = *reinterpret_cast<input_device_item *>(ref); + std::string value(format_value(input)); + if (item(i).subtext() != value) + { + item(i).set_subtext(std::move(value)); + updated = true; + } + } + } + + return updated; + } + + static std::string format_value(input_device_item &input) + { + switch (input.itemclass()) + { + default: + case ITEM_CLASS_SWITCH: + return util::string_format("%d", input.read_as_switch(ITEM_MODIFIER_NONE)); + case ITEM_CLASS_ABSOLUTE: + return util::string_format("%d", input.read_as_absolute(ITEM_MODIFIER_NONE)); + case ITEM_CLASS_RELATIVE: + return util::string_format("%d", input.read_as_relative(ITEM_MODIFIER_NONE)); + } + } + + input_device &m_device; + bool m_have_analog; +}; + +} // anonymous namespace + + + +menu_input_devices::menu_input_devices(mame_ui_manager &mui, render_container &container) + : menu(mui, container) +{ + set_heading(_("menu-inputdev", "Input Devices")); +} + + +menu_input_devices::~menu_input_devices() +{ +} + + +void menu_input_devices::populate() +{ + // iterate input device classes and devices within each class + bool found = false; + for (input_device_class classno = DEVICE_CLASS_FIRST_VALID; DEVICE_CLASS_LAST_VALID >= classno; ++classno) + { + input_class &devclass = machine().input().device_class(classno); + if (devclass.enabled()) + { + bool first = true; + for (int devnum = 0; devclass.maxindex() >= devnum; ++devnum) + { + input_device *const device = devclass.device(devnum); + if (device) + { + // add a device class heading + found = true; + if (first) + { + first = false; + item_append(devclass.name(), FLAG_UI_HEADING | FLAG_DISABLE, nullptr); + } + + // add the item for the device itself + item_append(util::string_format("%d", device->devindex() + 1), device->name(), 0U, device); + } + } + } + } + + // highly unlikely - at least one keyboard or mouse will be enabled in almost all cases + if (!found) + item_append(_("menu-inputdev", "[no input devices are enabled]"), FLAG_DISABLE, nullptr); + + item_append(menu_item_type::SEPARATOR); +} + + +bool menu_input_devices::handle(event const *ev) +{ + if (!ev || !ev->itemref) + return false; + + input_device &dev = *reinterpret_cast<input_device *>(ev->itemref); + switch (ev->iptkey) + { + case IPT_UI_SELECT: + stack_push<menu_input_device>(ui(), container(), dev); + break; + + case IPT_UI_PREV_GROUP: + { + auto group = dev.devclass(); + bool found_break = false; + int target = 0; + for (auto i = selected_index(); 0 < i--; ) + { + input_device *const candidate = reinterpret_cast<input_device *>(item(i).ref()); + if (candidate) + { + if (candidate->devclass() == group) + { + target = i; + } + else if (!found_break) + { + group = candidate->devclass(); + found_break = true; + target = i; + } + else + { + set_selected_index(target); + return true; + } + } + if (!i && found_break) + { + set_selected_index(target); + return true; + } + } + } + break; + + case IPT_UI_NEXT_GROUP: + { + auto const group = dev.devclass(); + for (auto i = selected_index(); item_count() > ++i; ) + { + input_device *const candidate = reinterpret_cast<input_device *>(item(i).ref()); + if (candidate && (candidate->devclass() != group)) + { + set_selected_index(i); + return true; + } + } + } + break; + } + + return false; +} + +} // namespace ui diff --git a/src/frontend/mame/ui/inputdevices.h b/src/frontend/mame/ui/inputdevices.h new file mode 100644 index 00000000000..3d4bfb51e4e --- /dev/null +++ b/src/frontend/mame/ui/inputdevices.h @@ -0,0 +1,34 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + ui/inputdevices.h + + Input devices menu. + +***************************************************************************/ + +#ifndef MAME_FRONTEND_UI_INPUTDEVICES_H +#define MAME_FRONTEND_UI_INPUTDEVICES_H + +#pragma once + +#include "ui/menu.h" + + +namespace ui { + +class menu_input_devices : public menu +{ +public: + menu_input_devices(mame_ui_manager &mui, render_container &container); + virtual ~menu_input_devices(); + +private: + virtual void populate() override; + virtual bool handle(event const *ev) override; +}; + +} // namespace ui + +#endif // MAME_FRONTEND_UI_INPUTDEVICES_H diff --git a/src/frontend/mame/ui/inputmap.cpp b/src/frontend/mame/ui/inputmap.cpp index eb90215520e..b135780a08f 100644 --- a/src/frontend/mame/ui/inputmap.cpp +++ b/src/frontend/mame/ui/inputmap.cpp @@ -9,145 +9,140 @@ *********************************************************************/ #include "emu.h" +#include "ui/inputmap.h" #include "uiinput.h" #include "ui/ui.h" -#include "ui/menu.h" -#include "ui/inputmap.h" #include <algorithm> namespace ui { -/*************************************************************************** - CONSTANTS -***************************************************************************/ - -#define MAX_PHYSICAL_DIPS 10 -#define MAX_INPUT_PORTS 32 -#define MAX_BITS_PER_PORT 32 - -/* DIP switch rendering parameters */ -#define DIP_SWITCH_HEIGHT 0.05f -#define DIP_SWITCH_SPACING 0.01f -#define SINGLE_TOGGLE_SWITCH_FIELD_WIDTH 0.025f -#define SINGLE_TOGGLE_SWITCH_WIDTH 0.020f -/* make the switch 80% of the width space and 1/2 of the switch height */ -#define PERCENTAGE_OF_HALF_FIELD_USED 0.80f -#define SINGLE_TOGGLE_SWITCH_HEIGHT ((DIP_SWITCH_HEIGHT / 2) * PERCENTAGE_OF_HALF_FIELD_USED) - - /*------------------------------------------------- - menu_input_groups_populate - populate the - input groups menu + menu_input_groups - handle the input groups + menu -------------------------------------------------*/ menu_input_groups::menu_input_groups(mame_ui_manager &mui, render_container &container) : menu(mui, container) { + set_heading(_("Input Assignments (general)")); } -void menu_input_groups::populate(float &customtop, float &custombottom) +menu_input_groups::~menu_input_groups() { - int player; +} - /* build up the menu */ - item_append(_("User Interface"), "", 0, (void *)(IPG_UI + 1)); - for (player = 0; player < MAX_PLAYERS; player++) +void menu_input_groups::populate() +{ + // build up the menu + item_append(_("User Interface"), 0, (void *)uintptr_t(IPG_UI + 1)); + for (int player = 0; player < MAX_PLAYERS; player++) { - auto s = string_format("Player %d Controls", player + 1); - item_append(s, "", 0, (void *)(uintptr_t)(IPG_PLAYER1 + player + 1)); + auto s = string_format(_("Player %1$d Controls"), player + 1); + item_append(s, 0, (void *)uintptr_t(IPG_PLAYER1 + player + 1)); } - item_append(_("Other Controls"), "", 0, (void *)(uintptr_t)(IPG_OTHER + 1)); + item_append(_("Other Controls"), 0, (void *)uintptr_t(IPG_OTHER + 1)); + item_append(menu_item_type::SEPARATOR); } -menu_input_groups::~menu_input_groups() +bool menu_input_groups::handle(event const *ev) { -} - -/*------------------------------------------------- - menu_input_groups - handle the input groups - menu --------------------------------------------------*/ + if (ev && (ev->iptkey == IPT_UI_SELECT)) + { + menu::stack_push<menu_input_general>( + ui(), + container(), + int(uintptr_t(ev->itemref) - 1), + util::string_format(_("Input Assignments (%1$s)"), ev->item->text())); + } -void menu_input_groups::handle() -{ - /* process the menu */ - const event *menu_event = process(0); - if (menu_event != nullptr && menu_event->iptkey == IPT_UI_SELECT) - menu::stack_push<menu_input_general>(ui(), container(), int((long long)(menu_event->itemref)-1)); + return false; } - /*------------------------------------------------- menu_input_general - handle the general input menu -------------------------------------------------*/ -menu_input_general::menu_input_general(mame_ui_manager &mui, render_container &container, int _group) : menu_input(mui, container) +menu_input_general::menu_input_general(mame_ui_manager &mui, render_container &container, int _group, std::string &&heading) + : menu_input(mui, container) + , group(_group) { - group = _group; + set_heading(std::move(heading)); } -void menu_input_general::populate(float &customtop, float &custombottom) +menu_input_general::~menu_input_general() { - input_item_data *itemlist = nullptr; +} - /* iterate over the input ports and add menu items */ - for (input_type_entry &entry : machine().ioport().types()) +void menu_input_general::menu_activated() +{ + // scripts can change settings out from under us + reset(reset_options::REMEMBER_POSITION); +} - /* add if we match the group and we have a valid name */ - if (entry.group() == group && entry.name() != nullptr && entry.name()[0] != 0) - { - input_seq_type seqtype; +void menu_input_general::populate() +{ + if (data.empty()) + { + assert(!pollingitem); - /* loop over all sequence types */ - for (seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; ++seqtype) + // iterate over the input ports and add menu items + for (const input_type_entry &entry : machine().ioport().types()) + { + // add if we match the group and we have a valid name + if (entry.group() == group) { - /* build an entry for the standard sequence */ - input_item_data *item = (input_item_data *)m_pool_alloc(sizeof(*item)); - memset(item, 0, sizeof(*item)); - item->ref = &entry; - if(pollingitem && pollingref == &entry && pollingseq == seqtype) - pollingitem = item; - item->seqtype = seqtype; - item->seq = machine().ioport().type_seq(entry.type(), entry.player(), seqtype); - item->defseq = &entry.defseq(seqtype); - item->group = entry.group(); - item->type = ioport_manager::type_is_analog(entry.type()) ? (INPUT_TYPE_ANALOG + seqtype) : INPUT_TYPE_DIGITAL; - item->is_optional = false; - item->name = entry.name(); - item->owner_name = nullptr; - item->next = itemlist; - itemlist = item; - - /* stop after one, unless we're analog */ - if (item->type == INPUT_TYPE_DIGITAL) - break; + std::string name = entry.name(); + if (!name.empty()) + { + // loop over all sequence types + for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; ++seqtype) + { + // build an entry for the standard sequence + input_item_data &item(data.emplace_back()); + item.ref = &entry; + item.seqtype = seqtype; + item.seq = machine().ioport().type_seq(entry.type(), entry.player(), seqtype); + item.defseq = &entry.defseq(seqtype); + item.group = entry.group(); + item.type = ioport_manager::type_is_analog(entry.type()) ? (INPUT_TYPE_ANALOG + seqtype) : INPUT_TYPE_DIGITAL; + item.is_optional = false; + item.name = name; + item.owner = nullptr; + + // stop after one, unless we're analog + if (item.type == INPUT_TYPE_DIGITAL) + break; + } + } } } - - - // first count the number of items - int numitems = 0; - for (input_item_data const *item = itemlist; item != nullptr; item = item->next) - numitems++; - - // now allocate an array of items and fill it up - std::vector<input_item_data *> itemarray(numitems); - int curitem = numitems; - for (input_item_data *item = itemlist; item != nullptr; item = item->next) - itemarray[--curitem] = item; + } + else + { + for (input_item_data &item : data) + { + const input_type_entry &entry(*reinterpret_cast<const input_type_entry *>(item.ref)); + item.seq = machine().ioport().type_seq(entry.type(), entry.player(), item.seqtype); + } + } // populate the menu in a standard fashion - populate_sorted(std::move(itemarray)); + populate_sorted(); + item_append(menu_item_type::SEPARATOR); } -menu_input_general::~menu_input_general() +void menu_input_general::update_input(input_item_data &seqchangeditem) { + const input_type_entry &entry = *reinterpret_cast<const input_type_entry *>(seqchangeditem.ref); + machine().ioport().set_type_seq(entry.type(), entry.player(), seqchangeditem.seqtype, seqchangeditem.seq); + seqchangeditem.seq = machine().ioport().type_seq(entry.type(), entry.player(), seqchangeditem.seqtype); } + /*------------------------------------------------- menu_input_specific - handle the game-specific input menu @@ -155,115 +150,155 @@ menu_input_general::~menu_input_general() menu_input_specific::menu_input_specific(mame_ui_manager &mui, render_container &container) : menu_input(mui, container) { + set_heading(_("Input Assignments (this system)")); +} + +menu_input_specific::~menu_input_specific() +{ } -void menu_input_specific::populate(float &customtop, float &custombottom) +void menu_input_specific::menu_activated() { - input_item_data *itemlist = nullptr; + // scripts can change settings out from under us + assert(!pollingitem); + data.clear(); + reset(reset_options::REMEMBER_POSITION); +} - /* iterate over the input ports and add menu items */ - for (auto &port : machine().ioport().ports()) +void menu_input_specific::populate() +{ + if (data.empty()) { - for (ioport_field &field : port.second->fields()) - { - ioport_type_class type_class = field.type_class(); + assert(!pollingitem); - /* add if we match the group and we have a valid name */ - if (field.enabled() && (type_class == INPUT_CLASS_CONTROLLER || type_class == INPUT_CLASS_MISC || type_class == INPUT_CLASS_KEYBOARD)) + // iterate over the input ports and add menu items + for (auto &port : machine().ioport().ports()) + { + for (ioport_field &field : port.second->fields()) { - /* loop over all sequence types */ - for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; ++seqtype) + const ioport_type_class type_class = field.type_class(); + + // add if it's enabled and it's a system-specific class + if (field.enabled() && (type_class == INPUT_CLASS_CONTROLLER || type_class == INPUT_CLASS_MISC || type_class == INPUT_CLASS_KEYBOARD)) { - /* build an entry for the standard sequence */ - input_item_data *item = (input_item_data *)m_pool_alloc(sizeof(*item)); - memset(item, 0, sizeof(*item)); - item->ref = &field; - item->seqtype = seqtype; - if(pollingitem && pollingref == item->ref && pollingseq == seqtype) - pollingitem = item; - item->seq = field.seq(seqtype); - item->defseq = &field.defseq(seqtype); - item->group = machine().ioport().type_group(field.type(), field.player()); - item->type = field.is_analog() ? (INPUT_TYPE_ANALOG + seqtype) : INPUT_TYPE_DIGITAL; - item->is_optional = field.optional(); - item->name = field.name(); - item->owner_name = field.device().tag(); - item->next = itemlist; - itemlist = item; - - /* stop after one, unless we're analog */ - if (item->type == INPUT_TYPE_DIGITAL) - break; + // loop over all sequence types + for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; ++seqtype) + { + // build an entry for the standard sequence + input_item_data &item(data.emplace_back()); + item.ref = &field; + item.seqtype = seqtype; + item.seq = field.seq(seqtype); + item.defseq = &field.defseq(seqtype); + item.group = machine().ioport().type_group(field.type(), field.player()); + item.type = field.is_analog() ? (INPUT_TYPE_ANALOG + seqtype) : INPUT_TYPE_DIGITAL; + item.is_optional = field.optional(); + item.name = field.name(); + item.owner = &field.device(); + + // stop after one, unless we're analog + if (item.type == INPUT_TYPE_DIGITAL) + break; + } } } } - } - // first count the number of items - int numitems = 0; - for (input_item_data const *item = itemlist; item != nullptr; item = item->next) - numitems++; - - // now allocate an array of items and fill it up - std::vector<input_item_data *> itemarray(numitems); - int curitem = 0; - for (input_item_data *item = itemlist; item != nullptr; item = item->next) - itemarray[curitem++] = item; - - // sort it - std::sort(itemarray.begin(), itemarray.end(), [](const input_item_data *i1, const input_item_data *i2) { - int cmp = strcmp(i1->owner_name, i2->owner_name); - if (cmp < 0) - return true; - if (cmp > 0) - return false; - if (i1->group < i2->group) - return true; - if (i1->group > i2->group) - return false; - const ioport_field &field1 = *reinterpret_cast<const ioport_field *>(i1->ref); - const ioport_field &field2 = *reinterpret_cast<const ioport_field *>(i2->ref); - if (field1.type() < field2.type()) - return true; - if (field1.type() > field2.type()) - return false; - std::vector<char32_t> codes1 = field1.keyboard_codes(0); - std::vector<char32_t> codes2 = field2.keyboard_codes(0); - if (!codes1.empty() && (codes2.empty() || codes1[0] < codes2[0])) - return true; - if (!codes2.empty() && (codes1.empty() || codes1[0] > codes2[0])) - return false; - cmp = strcmp(i1->name, i2->name); - if (cmp < 0) - return true; - if (cmp > 0) - return false; - return i1->type < i2->type; - }); + // sort it + std::sort( + data.begin(), + data.end(), + [] (const input_item_data &i1, const input_item_data &i2) + { + int cmp = strcmp(i1.owner->tag(), i2.owner->tag()); + if (cmp < 0) + return true; + if (cmp > 0) + return false; + if (i1.group < i2.group) + return true; + if (i1.group > i2.group) + return false; + const ioport_field &field1 = *reinterpret_cast<const ioport_field *>(i1.ref); + const ioport_field &field2 = *reinterpret_cast<const ioport_field *>(i2.ref); + if (field1.type() < field2.type()) + return true; + if (field1.type() > field2.type()) + return false; + std::vector<char32_t> codes1 = field1.keyboard_codes(0); + std::vector<char32_t> codes2 = field2.keyboard_codes(0); + if (!codes1.empty() && (codes2.empty() || codes1[0] < codes2[0])) + return true; + if (!codes2.empty() && (codes1.empty() || codes1[0] > codes2[0])) + return false; + cmp = i1.name.compare(i2.name); + if (cmp < 0) + return true; + if (cmp > 0) + return false; + return i1.type < i2.type; + }); + } + else + { + for (input_item_data &item : data) + { + const ioport_field &field(*reinterpret_cast<const ioport_field *>(item.ref)); + item.seq = field.seq(item.seqtype); + } + } // populate the menu in a standard fashion - populate_sorted(std::move(itemarray)); + if (!data.empty()) + populate_sorted(); + else + item_append(_("[no assignable inputs are enabled]"), FLAG_DISABLE, nullptr); + + item_append(menu_item_type::SEPARATOR); } -menu_input_specific::~menu_input_specific() +void menu_input_specific::update_input(input_item_data &seqchangeditem) { + ioport_field::user_settings settings; + + // yeah, the const_cast is naughty, but we know we stored a non-const reference in it + ioport_field const &field(*reinterpret_cast<ioport_field const *>(seqchangeditem.ref)); + field.get_user_settings(settings); + settings.seq[seqchangeditem.seqtype] = seqchangeditem.seq; + if (seqchangeditem.seq.is_default()) + settings.cfg[seqchangeditem.seqtype].clear(); + else if (!seqchangeditem.seq.length()) + settings.cfg[seqchangeditem.seqtype] = "NONE"; + else + settings.cfg[seqchangeditem.seqtype] = machine().input().seq_to_tokens(seqchangeditem.seq); + const_cast<ioport_field &>(field).set_user_settings(settings); + seqchangeditem.seq = field.seq(seqchangeditem.seqtype); } + /*------------------------------------------------- menu_input - display a menu for inputs -------------------------------------------------*/ -menu_input::menu_input(mame_ui_manager &mui, render_container &container) : menu(mui, container), record_next(false) + +menu_input::menu_input(mame_ui_manager &mui, render_container &container) + : menu(mui, container) + , data() + , pollingitem(nullptr) + , seq_poll() + , errormsg() + , erroritem(nullptr) + , lastitem(nullptr) + , record_next(false) + , modified_ticks(0) { - lastitem = nullptr; - pollingitem = nullptr; - pollingref = nullptr; - pollingseq = SEQ_TYPE_STANDARD; + set_process_flags(PROCESS_LR_ALWAYS); } menu_input::~menu_input() { } + /*------------------------------------------------- toggle_none_default - toggle between "NONE" and the default item @@ -271,665 +306,320 @@ menu_input::~menu_input() void menu_input::toggle_none_default(input_seq &selected_seq, input_seq &original_seq, const input_seq &selected_defseq) { - /* if we used to be "none", toggle to the default value */ - if (original_seq.length() == 0) - selected_seq = selected_defseq; - - /* otherwise, toggle to "none" */ - else + if (original_seq.empty()) // if we used to be "none", toggle to the default value + selected_seq.set_default(); + else // otherwise, toggle to "none" selected_seq.reset(); } -void menu_input::handle() -{ - input_item_data *seqchangeditem = nullptr; - const event *menu_event; - int invalidate = false; - - /* process the menu */ - menu_event = process((pollingitem != nullptr) ? PROCESS_NOKEYS : 0); - - /* if we are polling, handle as a special case */ - if (pollingitem != nullptr) - { - input_item_data *item = pollingitem; - - /* if UI_CANCEL is pressed, abort */ - if (machine().ui_input().pressed(IPT_UI_CANCEL)) - { - pollingitem = nullptr; - record_next = false; - toggle_none_default(item->seq, starting_seq, *item->defseq); - seqchangeditem = item; - } - - /* poll again; if finished, update the sequence */ - if (machine().input().seq_poll()) - { - pollingitem = nullptr; - record_next = true; - item->seq = machine().input().seq_poll_final(); - seqchangeditem = item; - } - } - /* otherwise, handle the events */ - else if (menu_event != nullptr && menu_event->itemref != nullptr) - { - input_item_data *item = (input_item_data *)menu_event->itemref; - switch (menu_event->iptkey) - { - /* an item was selected: begin polling */ - case IPT_UI_SELECT: - pollingitem = item; - lastitem = item; - starting_seq = item->seq; - machine().input().seq_poll_start((item->type == INPUT_TYPE_ANALOG) ? ITEM_CLASS_ABSOLUTE : ITEM_CLASS_SWITCH, record_next ? &item->seq : nullptr); - invalidate = true; - break; - - /* if the clear key was pressed, reset the selected item */ - case IPT_UI_CLEAR: - toggle_none_default(item->seq, item->seq, *item->defseq); - record_next = false; - seqchangeditem = item; - break; - } - - /* if the selection changed, reset the "record next" flag */ - if (item != lastitem) - record_next = false; - lastitem = item; - } - - /* if the sequence changed, update it */ - if (seqchangeditem != nullptr) - { - update_input(seqchangeditem); - - /* invalidate the menu to force an update */ - invalidate = true; - } - - /* if the menu is invalidated, clear it now */ - if (invalidate) - { - pollingref = nullptr; - if (pollingitem != nullptr) - { - pollingref = pollingitem->ref; - pollingseq = pollingitem->seqtype; - } - reset(reset_options::REMEMBER_POSITION); - } -} - -void menu_input_general::update_input(struct input_item_data *seqchangeditem) -{ - const input_type_entry *entry = (const input_type_entry *)seqchangeditem->ref; - machine().ioport().set_type_seq(entry->type(), entry->player(), seqchangeditem->seqtype, seqchangeditem->seq); -} - -void menu_input_specific::update_input(struct input_item_data *seqchangeditem) +void menu_input::recompute_metrics(uint32_t width, uint32_t height, float aspect) { - ioport_field::user_settings settings; + menu::recompute_metrics(width, height, aspect); - ((ioport_field *)seqchangeditem->ref)->get_user_settings(settings); - settings.seq[seqchangeditem->seqtype] = seqchangeditem->seq; - ((ioport_field *)seqchangeditem->ref)->set_user_settings(settings); + // leave space for showing the input sequence below the menu + set_custom_space(0.0F, 2.0F * line_height() + 3.0F * tb_border()); } -//------------------------------------------------- -// populate_sorted - take a sorted list of -// input_item_data objects and build up the -// menu from them -//------------------------------------------------- - -void menu_input::populate_sorted(std::vector<input_item_data *> &&itemarray) +void menu_input::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { - const char *nameformat[INPUT_TYPE_TOTAL] = { nullptr }; - std::string subtext; - std::string prev_owner; - bool first_entry = true; - - /* create a mini lookup table for name format based on type */ - nameformat[INPUT_TYPE_DIGITAL] = "%s"; - nameformat[INPUT_TYPE_ANALOG] = "%s Analog"; - nameformat[INPUT_TYPE_ANALOG_INC] = "%s Analog Inc"; - nameformat[INPUT_TYPE_ANALOG_DEC] = "%s Analog Dec"; - - /* build the menu */ - for (input_item_data *item : itemarray) + if (pollingitem) { - uint32_t flags = 0; - - /* generate the name of the item itself, based off the base name and the type */ - assert(nameformat[item->type] != nullptr); - - if (item->owner_name && strcmp(item->owner_name, prev_owner.c_str()) != 0) + const std::string seqname = machine().input().seq_name(seq_poll->sequence()); + char const *const text[] = { seqname.c_str() }; + draw_text_box( + std::begin(text), std::end(text), + origx1, origx2, origy2 + tb_border(), origy2 + bottom, + text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, false, + ui().colors().text_color(), ui().colors().background_color()); + } + else + { + if (erroritem && (selectedref != erroritem)) { - if (first_entry) - first_entry = false; - else - item_append(menu_item_type::SEPARATOR); - item_append(string_format("[root%s]", item->owner_name), "", 0, nullptr); - prev_owner.assign(item->owner_name); + errormsg.clear(); + erroritem = nullptr; } - std::string text = string_format(nameformat[item->type], item->name); - if (item->is_optional) - text = "(" + text + ")"; - - /* if we're polling this item, use some spaces with left/right arrows */ - if (pollingref == item->ref && pollingseq == item->seqtype) + if (erroritem) { - subtext.assign(" "); - flags |= FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW; + char const *const text[] = { errormsg.c_str() }; + draw_text_box( + std::begin(text), std::end(text), + origx1, origx2, origy2 + tb_border(), origy2 + bottom, + text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, false, + ui().colors().text_color(), UI_RED_COLOR); } - - /* otherwise, generate the sequence name and invert it if different from the default */ - else + else if (selectedref) { - subtext = machine().input().seq_name(item->seq); - flags |= (item->seq != *item->defseq) ? FLAG_INVERT : 0; + const input_item_data &item = *reinterpret_cast<input_item_data *>(selectedref); + if ((INPUT_TYPE_ANALOG != item.type) && machine().input().seq_pressed(item.seq)) + { + char const *const text[] = { _("Pressed") }; + draw_text_box( + std::begin(text), std::end(text), + origx1, origx2, origy2 + tb_border(), origy2 + bottom, + text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, false, + ui().colors().text_color(), ui().colors().background_color()); + } + else + { + char const *const text[] = { + record_next ? appendprompt.c_str() : assignprompt.c_str(), + (!item.seq.empty() || item.defseq->empty()) ? clearprompt.c_str() : defaultprompt.c_str() }; + draw_text_box( + std::begin(text), std::end(text), + origx1, origx2, origy2 + tb_border(), origy2 + bottom, + text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, false, + ui().colors().text_color(), ui().colors().background_color()); + } } - - /* add the item */ - item_append(text, subtext, flags, item); } } - -/*------------------------------------------------- - menu_settings_dip_switches - handle the DIP - switches menu --------------------------------------------------*/ - -menu_settings_dip_switches::menu_settings_dip_switches(mame_ui_manager &mui, render_container &container) : menu_settings(mui, container, IPT_DIPSWITCH) +bool menu_input::handle(event const *ev) { -} - -menu_settings_dip_switches::~menu_settings_dip_switches() -{ -} - -/*------------------------------------------------- - menu_settings_driver_config - handle the - driver config menu --------------------------------------------------*/ - -menu_settings_driver_config::menu_settings_driver_config(mame_ui_manager &mui, render_container &container) : menu_settings(mui, container, IPT_CONFIG) -{ -} - -menu_settings_driver_config::~menu_settings_driver_config() -{ -} - -/*------------------------------------------------- - menu_settings_common - handle one of the - switches menus --------------------------------------------------*/ + input_item_data *seqchangeditem = nullptr; + bool invalidate = false; + bool redraw = false; -void menu_settings::handle() -{ // process the menu - const event *menu_event = process(0); - - // handle events - if (menu_event != nullptr && menu_event->itemref != nullptr) + if (pollingitem) { - // reset - if ((uintptr_t)menu_event->itemref == 1) + // if we are polling, handle as a special case + input_item_data *const item = pollingitem; + + // prevent race condition between ui_input().pressed() and poll() + if (modified_ticks == 0 && seq_poll->modified()) + modified_ticks = osd_ticks(); + + if (machine().ui_input().pressed(IPT_UI_CANCEL)) { - if (menu_event->iptkey == IPT_UI_SELECT) - machine().schedule_hard_reset(); + // if UI_CANCEL is pressed, abort and abandon changes + pollingitem = nullptr; + set_process_flags(PROCESS_LR_ALWAYS); + invalidate = true; + seq_poll.reset(); + machine().ui_input().reset(); } - // actual settings - else + else if (seq_poll->poll()) // poll again; if finished, update the sequence { - ioport_field *field = (ioport_field *)menu_event->itemref; - ioport_field::user_settings settings; - int changed = false; - - switch (menu_event->iptkey) + pollingitem = nullptr; + set_process_flags(PROCESS_LR_ALWAYS); + if (seq_poll->valid()) + { + record_next = true; + item->seq = seq_poll->sequence(); + seqchangeditem = item; + } + else { - /* if selected, reset to default value */ - case IPT_UI_SELECT: - field->get_user_settings(settings); - settings.value = field->defvalue(); - field->set_user_settings(settings); - changed = true; - break; - - /* left goes to previous setting */ - case IPT_UI_LEFT: - field->select_previous_setting(); - changed = true; - break; - - /* right goes to next setting */ - case IPT_UI_RIGHT: - field->select_next_setting(); - changed = true; - break; + // entered invalid sequence - abandon change + invalidate = true; + errormsg = _("Invalid combination entered"); + erroritem = item; } - - /* if anything changed, rebuild the menu, trying to stay on the same field */ - if (changed) - reset(reset_options::REMEMBER_REF); + seq_poll.reset(); + machine().ui_input().reset(); + } + else + { + // always redraw to ensure it updates as soon as possible in response to changes + redraw = true; } } -} - - -/*------------------------------------------------- - menu_settings_populate - populate one of the - switches menus --------------------------------------------------*/ - -menu_settings::menu_settings(mame_ui_manager &mui, render_container &container, uint32_t _type) : menu(mui, container), diplist(nullptr), dipcount(0) -{ - type = _type; -} + else if (ev && ev->itemref) + { + // otherwise, handle the events + input_item_data &item = *reinterpret_cast<input_item_data *>(ev->itemref); + input_item_data *newsel = &item; + switch (ev->iptkey) + { + case IPT_UI_SELECT: // an item was selected: begin polling + set_process_flags(PROCESS_NOKEYS); + errormsg.clear(); + erroritem = nullptr; + modified_ticks = 0; + pollingitem = &item; + lastitem = &item; + starting_seq = item.seq; + if (INPUT_TYPE_ANALOG == item.type) + seq_poll.reset(new axis_sequence_poller(machine().input())); + else + seq_poll.reset(new switch_sequence_poller(machine().input())); + if (record_next) + seq_poll->start(item.seq); + else + seq_poll->start(); + invalidate = true; + break; + + case IPT_UI_CLEAR: // if the clear key was pressed, reset the selected item + errormsg.clear(); + erroritem = nullptr; + toggle_none_default(item.seq, item.seq, *item.defseq); + record_next = false; + seqchangeditem = &item; + break; -void menu_settings::populate(float &customtop, float &custombottom) -{ - dip_descriptor **diplist_tailptr; - std::string prev_owner; - bool first_entry = true; - - /* reset the dip switch tracking */ - dipcount = 0; - diplist = nullptr; - diplist_tailptr = &diplist; - - /* loop over input ports and set up the current values */ - for (auto &port : machine().ioport().ports()) - for (ioport_field &field : port.second->fields()) - if (field.type() == type && field.enabled()) + case IPT_UI_PREV_GROUP: { - if (!field.settings().empty()) + auto current = std::distance(data.data(), &item); + bool found_break = false; + while (0 < current) { - uint32_t flags = 0; - - /* set the left/right flags appropriately */ - if (field.has_previous_setting()) - flags |= FLAG_LEFT_ARROW; - if (field.has_next_setting()) - flags |= FLAG_RIGHT_ARROW; - - /* add the menu item */ - if (strcmp(field.device().tag(), prev_owner.c_str()) != 0) + if (!found_break) { - if (first_entry) - first_entry = false; - else - item_append(menu_item_type::SEPARATOR); - string_format("[root%s]", field.device().tag()); - item_append(string_format("[root%s]", field.device().tag()), "", 0, nullptr); - prev_owner.assign(field.device().tag()); + if (data[--current].owner != item.owner) + found_break = true; + } + else if (data[current].owner != data[current - 1].owner) + { + newsel = &data[current]; + set_selection(newsel); + set_top_line(selected_index() - 1); + break; + } + else + { + --current; + } + if (found_break && !current) + { + newsel = &data[current]; + set_selection(newsel); + set_top_line(selected_index() - 1); + break; } - - item_append(field.name(), field.setting_name(), flags, (void *)&field); } + } + break; - /* for DIP switches, build up the model */ - if (type == IPT_DIPSWITCH && !field.diplocations().empty()) + case IPT_UI_NEXT_GROUP: + { + auto current = std::distance(data.data(), &item); + while (data.size() > ++current) { - ioport_field::user_settings settings; - uint32_t accummask = field.mask(); - - /* get current settings */ - field.get_user_settings(settings); - - /* iterate over each bit in the field */ - for (const ioport_diplocation &diploc : field.diplocations()) + if (data[current].owner != item.owner) { - uint32_t mask = accummask & ~(accummask - 1); - dip_descriptor *dip; - - /* find the matching switch name */ - for (dip = diplist; dip != nullptr; dip = dip->next) - if (dip->owner == &field.device() && strcmp(dip->name, diploc.name()) == 0) - break; - - /* allocate new if none */ - if (dip == nullptr) - { - dip = (dip_descriptor *)m_pool_alloc(sizeof(*dip)); - dip->next = nullptr; - dip->name = diploc.name(); - dip->owner = &field.device(); - dip->mask = dip->state = 0; - *diplist_tailptr = dip; - diplist_tailptr = &dip->next; - dipcount++; - } - - /* apply the bits */ - dip->mask |= 1 << (diploc.number() - 1); - if (((settings.value & mask) != 0 && !diploc.inverted()) || ((settings.value & mask) == 0 && diploc.inverted())) - dip->state |= 1 << (diploc.number() - 1); - - /* clear the relevant bit in the accumulated mask */ - accummask &= ~mask; + newsel = &data[current]; + set_selection(newsel); + set_top_line(selected_index() - 1); + break; } } } - if (type == IPT_DIPSWITCH) - custombottom = dipcount ? dipcount * (DIP_SWITCH_HEIGHT + DIP_SWITCH_SPACING) + DIP_SWITCH_SPACING : 0; - - item_append(menu_item_type::SEPARATOR); - item_append(_("Reset"), "", 0, (void *)1); -} - -menu_settings::~menu_settings() -{ -} - -/*------------------------------------------------- - menu_settings_custom_render - perform our special - rendering --------------------------------------------------*/ - -void menu_settings_dip_switches::custom_render(void *selectedref, float top, float bottom, float x1, float y1, float x2, float y2) -{ - // catch if no diploc has to be drawn - if (bottom == 0) - return; - - // add borders - y1 = y2 + ui().box_tb_border(); - y2 = y1 + bottom; - - // draw extra menu area - ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color()); - y1 += (float)DIP_SWITCH_SPACING; - - // iterate over DIP switches - for (dip_descriptor *dip = diplist; dip != nullptr; dip = dip->next) - { - uint32_t selectedmask = 0; + break; + } - // determine the mask of selected bits - if ((uintptr_t)selectedref != 1) + // if the selection changed, reset the "record next" flag + if (newsel != lastitem) { - ioport_field *field = (ioport_field *)selectedref; - - if (field != nullptr && !field->diplocations().empty()) - for (const ioport_diplocation &diploc : field->diplocations()) - if (dip->owner == &field->device() && strcmp(dip->name, diploc.name()) == 0) - selectedmask |= 1 << (diploc.number() - 1); + if (erroritem) + { + errormsg.clear(); + erroritem = nullptr; + } + record_next = false; + lastitem = &item; + redraw = true; } - // draw one switch - custom_render_one(x1, y1, x2, y1 + DIP_SWITCH_HEIGHT, dip, selectedmask); - y1 += (float)(DIP_SWITCH_SPACING + DIP_SWITCH_HEIGHT); + // flip between set and append + // not very discoverable, but with the prompt it isn't completely opaque + if ((IPT_UI_LEFT == ev->iptkey) || (IPT_UI_RIGHT == ev->iptkey)) + { + if (erroritem) + { + errormsg.clear(); + erroritem = nullptr; + } + else if (record_next || !item.seq.empty()) + { + record_next = !record_next; + } + redraw = true; + } } -} - - -/*------------------------------------------------- - menu_settings_custom_render_one - draw a single - DIP switch --------------------------------------------------*/ -void menu_settings_dip_switches::custom_render_one(float x1, float y1, float x2, float y2, const dip_descriptor *dip, uint32_t selectedmask) -{ - float switch_field_width = SINGLE_TOGGLE_SWITCH_FIELD_WIDTH * container().manager().ui_aspect(); - float switch_width = SINGLE_TOGGLE_SWITCH_WIDTH * container().manager().ui_aspect(); - int numtoggles, toggle; - float switch_toggle_gap; - float y1_off, y1_on; - - /* determine the number of toggles in the DIP */ - numtoggles = 32 - count_leading_zeros(dip->mask); - - /* center based on the number of switches */ - x1 += (x2 - x1 - numtoggles * switch_field_width) / 2; - - /* draw the dip switch name */ - ui().draw_text_full(container(), - dip->name, - 0, - y1 + (DIP_SWITCH_HEIGHT - ui().target_font_height()) / 2, - x1 - ui().get_string_width(" "), - ui::text_layout::RIGHT, - ui::text_layout::NEVER, - mame_ui_manager::NORMAL, - ui().colors().text_color(), - PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA), - nullptr , - nullptr); - - /* compute top and bottom for on and off positions */ - switch_toggle_gap = ((DIP_SWITCH_HEIGHT/2) - SINGLE_TOGGLE_SWITCH_HEIGHT)/2; - y1_off = y1 + UI_LINE_WIDTH + switch_toggle_gap; - y1_on = y1 + DIP_SWITCH_HEIGHT/2 + switch_toggle_gap; - - /* iterate over toggles */ - for (toggle = 0; toggle < numtoggles; toggle++) + // if the sequence changed, update it + if (seqchangeditem) { - float innerx1; + update_input(*seqchangeditem); - /* first outline the switch */ - ui().draw_outlined_box(container(), x1, y1, x1 + switch_field_width, y2, ui().colors().background_color()); - - /* compute x1/x2 for the inner filled in switch */ - innerx1 = x1 + (switch_field_width - switch_width) / 2; + // invalidate the menu to force an update + invalidate = true; + } - /* see if the switch is actually used */ - if (dip->mask & (1 << toggle)) - { - float innery1 = (dip->state & (1 << toggle)) ? y1_on : y1_off; - container().add_rect(innerx1, innery1, innerx1 + switch_width, innery1 + SINGLE_TOGGLE_SWITCH_HEIGHT, - (selectedmask & (1 << toggle)) ? ui().colors().dipsw_color() : ui().colors().text_color(), - PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); - } - else - { - container().add_rect(innerx1, y1_off, innerx1 + switch_width, y1_on + SINGLE_TOGGLE_SWITCH_HEIGHT, - ui().colors().unavailable_color(), - PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); - } + // if the menu is invalidated, clear it now + if (invalidate) + reset(reset_options::REMEMBER_POSITION); - /* advance to the next switch */ - x1 += switch_field_width; - } + return redraw && !invalidate; } -/*------------------------------------------------- - menu_analog - handle the analog settings menu --------------------------------------------------*/ +//------------------------------------------------- +// populate_sorted - take a sorted list of +// input_item_data objects and build up the +// menu from them +//------------------------------------------------- -void menu_analog::handle() +void menu_input::populate_sorted() { - /* process the menu */ - const event *menu_event = process(PROCESS_LR_REPEAT); + const char *nameformat[INPUT_TYPE_TOTAL] = { nullptr }; - /* handle events */ - if (menu_event != nullptr && menu_event->itemref != nullptr) + // create a mini lookup table for name format based on type + nameformat[INPUT_TYPE_DIGITAL] = "%1$s"; + nameformat[INPUT_TYPE_ANALOG] = _("input-name", "%1$s Analog"); + nameformat[INPUT_TYPE_ANALOG_INC] = _("input-name", "%1$s Analog Inc"); + nameformat[INPUT_TYPE_ANALOG_DEC] = _("input-name", "%1$s Analog Dec"); + + // build the menu + std::string text, subtext; + const device_t *prev_owner = nullptr; + for (input_item_data &item : data) { - analog_item_data *data = (analog_item_data *)menu_event->itemref; - int newval = data->cur; + // generate the name of the item itself, based off the base name and the type + assert(nameformat[item.type] != nullptr); - switch (menu_event->iptkey) + if (item.owner && (item.owner != prev_owner)) { - /* if selected, reset to default value */ - case IPT_UI_SELECT: - newval = data->defvalue; - break; - - /* left decrements */ - case IPT_UI_LEFT: - newval -= machine().input().code_pressed(KEYCODE_LSHIFT) ? 10 : 1; - break; - - /* right increments */ - case IPT_UI_RIGHT: - newval += machine().input().code_pressed(KEYCODE_LSHIFT) ? 10 : 1; - break; + if (item.owner->owner()) + item_append(string_format(_("%1$s [root%2$s]"), item.owner->type().fullname(), item.owner->tag()), FLAG_UI_HEADING | FLAG_DISABLE, nullptr); + else + item_append(string_format(_("[root%1$s]"), item.owner->tag()), FLAG_UI_HEADING | FLAG_DISABLE, nullptr); + prev_owner = item.owner; } - /* clamp to range */ - if (newval < data->min) - newval = data->min; - if (newval > data->max) - newval = data->max; + text = string_format(nameformat[item.type], item.name); + if (item.is_optional) + text = "(" + text + ")"; - /* if things changed, update */ - if (newval != data->cur) + uint32_t flags = 0; + if (&item == pollingitem) { - ioport_field::user_settings settings; - - /* get the settings and set the new value */ - data->field->get_user_settings(settings); - switch (data->type) - { - case ANALOG_ITEM_KEYSPEED: settings.delta = newval; break; - case ANALOG_ITEM_CENTERSPEED: settings.centerdelta = newval; break; - case ANALOG_ITEM_REVERSE: settings.reverse = newval; break; - case ANALOG_ITEM_SENSITIVITY: settings.sensitivity = newval; break; - } - data->field->set_user_settings(settings); - - /* rebuild the menu */ - reset(reset_options::REMEMBER_POSITION); + // if we're polling this item, use some spaces with left/right arrows + subtext = " "; + flags |= FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW; + } + else + { + // otherwise, generate the sequence name and invert it if different from the default + subtext = machine().input().seq_name(item.seq); + flags |= (item.seq != *item.defseq) ? FLAG_INVERT : 0; } - } -} - - -/*------------------------------------------------- - menu_analog_populate - populate the analog - settings menu --------------------------------------------------*/ - -menu_analog::menu_analog(mame_ui_manager &mui, render_container &container) : menu(mui, container) -{ -} - -void menu_analog::populate(float &customtop, float &custombottom) -{ - std::string prev_owner; - bool first_entry = true; - - /* loop over input ports and add the items */ - for (auto &port : machine().ioport().ports()) - for (ioport_field &field : port.second->fields()) - if (field.is_analog() && field.enabled()) - { - ioport_field::user_settings settings; - int use_autocenter = false; - int type; - - /* based on the type, determine if we enable autocenter */ - switch (field.type()) - { - case IPT_POSITIONAL: - case IPT_POSITIONAL_V: - if (field.analog_wraps()) - break; - - case IPT_AD_STICK_X: - case IPT_AD_STICK_Y: - case IPT_AD_STICK_Z: - case IPT_PADDLE: - case IPT_PADDLE_V: - case IPT_PEDAL: - case IPT_PEDAL2: - case IPT_PEDAL3: - use_autocenter = true; - break; - - default: - break; - } - - /* get the user settings */ - field.get_user_settings(settings); - /* iterate over types */ - for (type = 0; type < ANALOG_ITEM_COUNT; type++) - if (type != ANALOG_ITEM_CENTERSPEED || use_autocenter) - { - analog_item_data *data; - uint32_t flags = 0; - std::string text; - std::string subtext; - if (strcmp(field.device().tag(), prev_owner.c_str()) != 0) - { - if (first_entry) - first_entry = false; - else - item_append(menu_item_type::SEPARATOR); - item_append(string_format("[root%s]", field.device().tag()), "", 0, nullptr); - prev_owner.assign(field.device().tag()); - } - - /* allocate a data item for tracking what this menu item refers to */ - data = (analog_item_data *)m_pool_alloc(sizeof(*data)); - data->field = &field; - data->type = type; - - /* determine the properties of this item */ - switch (type) - { - default: - case ANALOG_ITEM_KEYSPEED: - text = string_format("%s Digital Speed", field.name()); - subtext = string_format("%d", settings.delta); - data->min = 0; - data->max = 255; - data->cur = settings.delta; - data->defvalue = field.delta(); - break; - - case ANALOG_ITEM_CENTERSPEED: - text = string_format("%s Autocenter Speed", field.name()); - subtext = string_format("%d", settings.centerdelta); - data->min = 0; - data->max = 255; - data->cur = settings.centerdelta; - data->defvalue = field.centerdelta(); - break; - - case ANALOG_ITEM_REVERSE: - text = string_format("%s Reverse", field.name()); - subtext.assign(settings.reverse ? "On" : "Off"); - data->min = 0; - data->max = 1; - data->cur = settings.reverse; - data->defvalue = field.analog_reverse(); - break; - - case ANALOG_ITEM_SENSITIVITY: - text = string_format("%s Sensitivity", field.name()); - subtext = string_format("%d", settings.sensitivity); - data->min = 1; - data->max = 255; - data->cur = settings.sensitivity; - data->defvalue = field.sensitivity(); - break; - } - - /* put on arrows */ - if (data->cur > data->min) - flags |= FLAG_LEFT_ARROW; - if (data->cur < data->max) - flags |= FLAG_RIGHT_ARROW; - - /* append a menu item */ - item_append(std::move(text), std::move(subtext), flags, data); - } - } -} + // add the item + item_append(std::move(text), std::move(subtext), flags, &item); + } -menu_analog::~menu_analog() -{ + // pre-format messages + assignprompt = util::string_format(_("Press %1$s to set\n"), ui().get_general_input_setting(IPT_UI_SELECT)); + appendprompt = util::string_format(_("Press %1$s to append\n"), ui().get_general_input_setting(IPT_UI_SELECT)); + clearprompt = util::string_format(_("Press %1$s to clear\n"), ui().get_general_input_setting(IPT_UI_CLEAR)); + defaultprompt = util::string_format(_("Press %1$s to restore default\n"), ui().get_general_input_setting(IPT_UI_CLEAR)); } } // namespace ui diff --git a/src/frontend/mame/ui/inputmap.h b/src/frontend/mame/ui/inputmap.h index a37a297a2f8..e27e58e67fc 100644 --- a/src/frontend/mame/ui/inputmap.h +++ b/src/frontend/mame/ui/inputmap.h @@ -7,16 +7,20 @@ Internal menus for input mappings. ***************************************************************************/ - #ifndef MAME_FRONTEND_UI_INPUTMAP_H #define MAME_FRONTEND_UI_INPUTMAP_H #pragma once #include "ui/menu.h" +#include "iptseqpoll.h" + +#include <string> +#include <vector> namespace ui { + class menu_input_groups : public menu { public: @@ -24,14 +28,14 @@ public: virtual ~menu_input_groups() 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; }; + class menu_input : public menu { public: - menu_input(mame_ui_manager &mui, render_container &container); virtual ~menu_input() override; protected: @@ -43,135 +47,79 @@ protected: INPUT_TYPE_TOTAL = INPUT_TYPE_ANALOG + SEQ_TYPE_TOTAL }; - /* internal input menu item data */ + // internal input menu item data struct input_item_data { - input_item_data * next; /* pointer to next item in the list */ - const void * ref; /* reference to type description for global inputs or field for game inputs */ - input_seq_type seqtype; /* sequence type */ - input_seq seq; /* copy of the live sequence */ - const input_seq * defseq; /* pointer to the default sequence */ - const char * name; /* pointer to the base name of the item */ - const char * owner_name; /* pointer to the name of the owner of the item */ - ioport_group group; /* group type */ - uint8_t type; /* type of port */ - bool is_optional; /* true if this input is considered optional */ + const void * ref = nullptr; // reference to type description for global inputs or field for game inputs + input_seq_type seqtype = SEQ_TYPE_INVALID; // sequence type + input_seq seq; // copy of the live sequence + const input_seq * defseq = nullptr; // pointer to the default sequence + std::string name; // base name of the item + const device_t * owner = nullptr; // pointer to the owner of the item + ioport_group group = IPG_INVALID; // group type + uint8_t type = 0U; // type of port + bool is_optional = false; // true if this input is considered optional }; + using data_vector = std::vector<input_item_data>; + + menu_input(mame_ui_manager &mui, render_container &container); + + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) override; - void populate_sorted(std::vector<input_item_data *> &&itemarray); + void populate_sorted(); void toggle_none_default(input_seq &selected_seq, input_seq &original_seq, const input_seq &selected_defseq); - const void * pollingref; - input_seq_type pollingseq; - input_item_data * pollingitem; + data_vector data; + input_item_data *pollingitem; private: - input_item_data * lastitem; - bool record_next; - input_seq starting_seq; - - virtual void handle() override; - virtual void update_input(struct input_item_data *seqchangeditem) = 0; + std::unique_ptr<input_sequence_poller> seq_poll; + std::string assignprompt, appendprompt; + std::string clearprompt, defaultprompt; + std::string errormsg; + input_item_data *erroritem; + input_item_data *lastitem; + bool record_next; + osd_ticks_t modified_ticks; + input_seq starting_seq; + + virtual bool handle(event const *ev) override; + virtual void update_input(input_item_data &seqchangeditem) = 0; }; + class menu_input_general : public menu_input { public: - menu_input_general(mame_ui_manager &mui, render_container &container, int group); + menu_input_general(mame_ui_manager &mui, render_container &container, int group, std::string &&heading); virtual ~menu_input_general() override; +protected: + virtual void menu_activated() override; + private: - virtual void populate(float &customtop, float &custombottom) override; - virtual void update_input(struct input_item_data *seqchangeditem) override; + virtual void populate() override; + virtual void update_input(input_item_data &seqchangeditem) override; - int group; + const int group; }; + class menu_input_specific : public menu_input { public: menu_input_specific(mame_ui_manager &mui, render_container &container); virtual ~menu_input_specific() override; -private: - virtual void populate(float &customtop, float &custombottom) override; - virtual void update_input(struct input_item_data *seqchangeditem) override; -}; - -class menu_settings : public menu -{ -public: - menu_settings(mame_ui_manager &mui, render_container &container, uint32_t type); - virtual ~menu_settings() override; - protected: - /* DIP switch descriptor */ - struct dip_descriptor - { - dip_descriptor * next; - const char * name; - device_t * owner; - uint32_t mask; - uint32_t state; - }; - - dip_descriptor * diplist; - int dipcount; - int type; + virtual void menu_activated() override; private: - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; -}; - -class menu_settings_dip_switches : public menu_settings -{ -public: - menu_settings_dip_switches(mame_ui_manager &mui, render_container &container); - virtual ~menu_settings_dip_switches() override; - -protected: - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; - -private: - void custom_render_one(float x1, float y1, float x2, float y2, const dip_descriptor *dip, uint32_t selectedmask); -}; - -class menu_settings_driver_config : public menu_settings -{ -public: - menu_settings_driver_config(mame_ui_manager &mui, render_container &container); - virtual ~menu_settings_driver_config(); -}; - -class menu_analog : public menu -{ -public: - menu_analog(mame_ui_manager &mui, render_container &container); - virtual ~menu_analog() override; - -private: - enum { - ANALOG_ITEM_KEYSPEED = 0, - ANALOG_ITEM_CENTERSPEED, - ANALOG_ITEM_REVERSE, - ANALOG_ITEM_SENSITIVITY, - ANALOG_ITEM_COUNT - }; - - /* internal analog menu item data */ - struct analog_item_data { - ioport_field *field; - int type; - int min, max; - int cur; - int defvalue; - }; - - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; + virtual void update_input(input_item_data &seqchangeditem) override; }; } // namespace ui -#endif /* MAME_FRONTEND_UI_INPUTMAP_H */ +#endif // MAME_FRONTEND_UI_INPUTMAP_H diff --git a/src/frontend/mame/ui/inputopts.cpp b/src/frontend/mame/ui/inputopts.cpp new file mode 100644 index 00000000000..48db7698e71 --- /dev/null +++ b/src/frontend/mame/ui/inputopts.cpp @@ -0,0 +1,144 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + ui/inputopts.cpp + + Input options submenu. + +***************************************************************************/ + +#include "emu.h" +#include "ui/inputopts.h" + +#include "ui/analogipt.h" +#include "ui/inputdevices.h" +#include "ui/inputmap.h" +#include "ui/inputtoggle.h" +#include "ui/keyboard.h" + +#include "natkeyboard.h" + + +namespace ui { + +namespace { + +enum : unsigned +{ + INPUTMAP_GENERAL = 1, + INPUTMAP_MACHINE, + ANALOG, + KEYBOARD, + TOGGLES, + INPUTDEV +}; + + +void scan_inputs(running_machine &machine, bool &inputmap, bool &analog, bool &toggle) +{ + inputmap = analog = toggle = false; + for (auto &port : machine.ioport().ports()) + { + for (ioport_field &field : port.second->fields()) + { + if (field.enabled()) + { + switch (field.type_class()) + { + case INPUT_CLASS_CONTROLLER: + case INPUT_CLASS_MISC: + case INPUT_CLASS_KEYBOARD: + inputmap = true; + if (field.live().toggle) + toggle = true; + break; + default: + break; + } + if (field.is_analog()) + analog = true; + + if (inputmap && analog && toggle) + return; + } + } + } +} + +} // anonymous namespace + + +menu_input_options::menu_input_options(mame_ui_manager &mui, render_container &container) + : menu(mui, container) +{ + set_heading(_("menu-inputopts", "Input Settings")); +} + + +menu_input_options::~menu_input_options() +{ +} + + +void menu_input_options::menu_activated() +{ + reset(reset_options::REMEMBER_REF); +} + + +void menu_input_options::populate() +{ + bool inputmap, analog, toggle; + scan_inputs(machine(), inputmap, analog, toggle); + + // system-specific stuff + if (inputmap) + item_append(_("menu-inputopts", "Input Assignments (this system)"), 0, (void *)INPUTMAP_MACHINE); + if (analog) + item_append(_("menu-inputopts", "Analog Input Adjustments"), 0, (void *)ANALOG); + if (machine().natkeyboard().keyboard_count()) + item_append(_("menu-inputopts", "Keyboard Selection"), 0, (void *)KEYBOARD); + if (toggle) + item_append(_("menu-inputopts", "Toggle Inputs"), 0, (void *)TOGGLES); + if (inputmap || analog || machine().natkeyboard().keyboard_count() || toggle) + item_append(menu_item_type::SEPARATOR); + + // general stuff + item_append(_("menu-inputopts", "Input Assignments (general)"), 0, (void *)INPUTMAP_GENERAL); + item_append(_("menu-inputopts", "Input Devices"), 0, (void *)INPUTDEV); + item_append(menu_item_type::SEPARATOR); +} + + +bool menu_input_options::handle(event const *ev) +{ + if (ev && (IPT_UI_SELECT == ev->iptkey)) + { + switch (uintptr_t(ev->itemref)) + { + case INPUTMAP_GENERAL: + stack_push<menu_input_groups>(ui(), container()); + break; + case INPUTMAP_MACHINE: + stack_push<menu_input_specific>(ui(), container()); + break; + case ANALOG: + stack_push<menu_analog>(ui(), container()); + break; + case KEYBOARD: + stack_push<menu_keyboard_mode>(ui(), container()); + break; + case TOGGLES: + stack_push<menu_input_toggles>(ui(), container()); + break; + case INPUTDEV: + stack_push<menu_input_devices>(ui(), container()); + break; + } + } + + return false; +} + +} // namespace ui diff --git a/src/frontend/mame/ui/inputopts.h b/src/frontend/mame/ui/inputopts.h new file mode 100644 index 00000000000..bf6afebabf5 --- /dev/null +++ b/src/frontend/mame/ui/inputopts.h @@ -0,0 +1,37 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + ui/inputopts.h + + Input options submenu. + +***************************************************************************/ + +#ifndef MAME_FRONTEND_UI_INPUTOPTS_H +#define MAME_FRONTEND_UI_INPUTOPTS_H + +#pragma once + +#include "ui/menu.h" + + +namespace ui { + +class menu_input_options : public menu +{ +public: + menu_input_options(mame_ui_manager &mui, render_container &container); + virtual ~menu_input_options(); + +protected: + virtual void menu_activated() override; + +private: + virtual void populate() override; + virtual bool handle(event const *ev) override; +}; + +} // namespace ui + +#endif // MAME_FRONTEND_UI_INPUTOPTS_H diff --git a/src/frontend/mame/ui/inputtoggle.cpp b/src/frontend/mame/ui/inputtoggle.cpp new file mode 100644 index 00000000000..7a307c01c66 --- /dev/null +++ b/src/frontend/mame/ui/inputtoggle.cpp @@ -0,0 +1,220 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + ui/inputtoggle.cpp + + Toggle inputs menu. + +***************************************************************************/ + +#include "emu.h" +#include "ui/inputtoggle.h" + +#include <iterator> + + +namespace ui { + +menu_input_toggles::menu_input_toggles(mame_ui_manager &mui, render_container &container) + : menu(mui, container) +{ + set_heading(_("menu-inputtoggle", "Toggle Inputs")); +} + + +menu_input_toggles::~menu_input_toggles() +{ +} + + +void menu_input_toggles::menu_activated() +{ + // enabled inputs and state of inputs can change while menu is inactive + reset(reset_options::REMEMBER_REF); +} + + +void menu_input_toggles::populate() +{ + // find toggle fields + if (m_fields.empty()) + { + for (auto &port : machine().ioport().ports()) + { + for (ioport_field &field : port.second->fields()) + { + switch (field.type_class()) + { + case INPUT_CLASS_CONTROLLER: + case INPUT_CLASS_MISC: + case INPUT_CLASS_KEYBOARD: + if (field.live().toggle) + m_fields.emplace_back(field); + break; + default: + break; + } + } + } + } + + // create corresponding items for enabled fields + device_t *prev_owner = nullptr; + for (auto &field : m_fields) + { + if (field.get().enabled()) + { + // add a device heading if necessary + if (&field.get().device() != prev_owner) + { + prev_owner = &field.get().device(); + if (prev_owner->owner()) + item_append(string_format(_("%1$s [root%2$s]"), prev_owner->type().fullname(), prev_owner->tag()), FLAG_UI_HEADING | FLAG_DISABLE, nullptr); + else + item_append(string_format(_("[root%1$s]"), prev_owner->tag()), FLAG_UI_HEADING | FLAG_DISABLE, nullptr); + } + + // choose the display name for the value + char const *setting; + u32 flags = 0U; + if (!field.get().settings().empty()) + { + setting = field.get().setting_name(); + if (field.get().has_previous_setting()) + flags |= FLAG_LEFT_ARROW; + if (field.get().has_next_setting()) + flags |= FLAG_RIGHT_ARROW; + } + else if (field.get().defvalue() == field.get().live().value) + { + setting = _("Off"); + flags = FLAG_RIGHT_ARROW; + } + else + { + setting = _("On"); + flags = FLAG_LEFT_ARROW; + } + + // actually create the item + item_append(field.get().name(), setting, flags, &field); + } + } + + // display a message if there are toggle inputs enabled + if (!prev_owner) + item_append(_("menu-inputtoggle", "[no toggle inputs are enabled]"), FLAG_DISABLE, nullptr); + + item_append(menu_item_type::SEPARATOR); +} + + +bool menu_input_toggles::handle(event const *ev) +{ + if (!ev || !ev->itemref) + return false; + + auto const ref = reinterpret_cast<std::reference_wrapper<ioport_field> *>(ev->itemref); + ioport_field &field = ref->get(); + bool invalidate = false; + switch (ev->iptkey) + { + case IPT_UI_SELECT: // toggle regular items, cycle multi-value items + if (field.settings().empty()) + field.live().value ^= field.mask(); + else + field.select_next_setting(); + invalidate = true; + break; + + case IPT_UI_CLEAR: // set to default + if (field.defvalue() != field.live().value) + { + field.live().value = field.defvalue(); + invalidate = true; + } + break; + + case IPT_UI_LEFT: // toggle or select previous setting + if (field.settings().empty()) + field.live().value ^= field.mask(); + else + field.select_previous_setting(); + invalidate = true; + break; + + case IPT_UI_RIGHT: // toggle or select next setting + if (field.settings().empty()) + field.live().value ^= field.mask(); + else + field.select_next_setting(); + invalidate = true; + break; + + case IPT_UI_PREV_GROUP: // previous device if any + { + auto current = std::distance(m_fields.data(), ref); + device_t const *dev = &field.device(); + bool found_break = false; + void *candidate = nullptr; + while (0 < current) + { + if (!found_break) + { + if (m_fields[--current].get().enabled()) + { + device_t const *prev = &m_fields[current].get().device(); + if (prev != dev) + { + dev = prev; + found_break = true; + candidate = &m_fields[current]; + } + } + } + else if (&m_fields[--current].get().device() != dev) + { + set_selection(candidate); + set_top_line(selected_index() - 1); + return true; + } + else if (m_fields[current].get().enabled()) + { + candidate = &m_fields[current]; + } + if (found_break && !current) + { + set_selection(candidate); + set_top_line(selected_index() - 1); + return true; + } + } + } + break; + + case IPT_UI_NEXT_GROUP: // next device if any + { + auto current = std::distance(m_fields.data(), ref); + device_t const *const dev = &field.device(); + while (m_fields.size() > ++current) + { + if (m_fields[current].get().enabled() && (&m_fields[current].get().device() != dev)) + { + set_selection(&m_fields[current]); + set_top_line(selected_index() - 1); + return true; + } + } + } + break; + } + + // changing value can enable or disable other fields + if (invalidate) + reset(reset_options::REMEMBER_REF); + + return false; +} + +} // namespace ui diff --git a/src/frontend/mame/ui/inputtoggle.h b/src/frontend/mame/ui/inputtoggle.h new file mode 100644 index 00000000000..676be71e58d --- /dev/null +++ b/src/frontend/mame/ui/inputtoggle.h @@ -0,0 +1,42 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + ui/inputtoggle.h + + Toggle inputs menu. + +***************************************************************************/ + +#ifndef MAME_FRONTEND_UI_INPUTTOGGLE_H +#define MAME_FRONTEND_UI_INPUTTOGGLE_H + +#pragma once + +#include "ui/menu.h" + +#include <functional> +#include <vector> + + +namespace ui { + +class menu_input_toggles : public menu +{ +public: + menu_input_toggles(mame_ui_manager &mui, render_container &container); + virtual ~menu_input_toggles(); + +protected: + virtual void menu_activated() override; + +private: + virtual void populate() override; + virtual bool handle(event const *ev) override; + + std::vector<std::reference_wrapper<ioport_field> > m_fields; +}; + +} // namespace ui + +#endif // MAME_FRONTEND_UI_INPUTTOGGLE_H diff --git a/src/frontend/mame/ui/keyboard.cpp b/src/frontend/mame/ui/keyboard.cpp new file mode 100644 index 00000000000..9eaeb5c94ee --- /dev/null +++ b/src/frontend/mame/ui/keyboard.cpp @@ -0,0 +1,121 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + ui/keyboard.cpp + + Keyboard mode menu. + +***************************************************************************/ + +#include "emu.h" +#include "ui/keyboard.h" + +#include "natkeyboard.h" + + +namespace ui { + +namespace { + +constexpr uintptr_t ITEM_KBMODE = 0x00000100; +constexpr uintptr_t ITEM_KBDEV_FIRST = 0x00000200; + +} // anonymous namespace + + +menu_keyboard_mode::menu_keyboard_mode(mame_ui_manager &mui, render_container &container) : menu(mui, container) +{ + set_heading(_("menu-keyboard", "Keyboard Selection")); +} + +void menu_keyboard_mode::menu_activated() +{ + // scripts could have changed something behind our back + reset(reset_options::REMEMBER_POSITION); +} + +void menu_keyboard_mode::populate() +{ + natural_keyboard &natkbd(machine().natkeyboard()); + + if (natkbd.can_post()) + { + bool const natmode(natkbd.in_use()); + item_append( + _("menu-keyboard", "Keyboard Mode"), + natmode ? _("menu-keyboard", "Natural") : _("menu-keyboard", "Emulated"), + natmode ? FLAG_LEFT_ARROW : FLAG_RIGHT_ARROW, + reinterpret_cast<void *>(ITEM_KBMODE)); + item_append(menu_item_type::SEPARATOR); + } + + uintptr_t ref(ITEM_KBDEV_FIRST); + for (size_t i = 0; natkbd.keyboard_count() > i; ++i, ++ref) + { + device_t &kbddev(natkbd.keyboard_device(i)); + bool const enabled(natkbd.keyboard_enabled(i)); + item_append( + util::string_format( + kbddev.owner() ? _("%1$s [root%2$s]") : _("[root%2$s]"), + kbddev.type().fullname(), + kbddev.tag()), + enabled ? _("Enabled") : _("Disabled"), + enabled ? FLAG_LEFT_ARROW : FLAG_RIGHT_ARROW, + reinterpret_cast<void *>(ref)); + } + item_append(menu_item_type::SEPARATOR); +} + +menu_keyboard_mode::~menu_keyboard_mode() +{ +} + +bool menu_keyboard_mode::handle(event const *ev) +{ + if (!ev || !uintptr_t(ev->itemref)) + return false; + + natural_keyboard &natkbd(machine().natkeyboard()); + uintptr_t const ref(uintptr_t(ev->itemref)); + bool left(IPT_UI_LEFT == ev->iptkey); + bool right(IPT_UI_RIGHT == ev->iptkey); + if (ITEM_KBMODE == ref) + { + if (IPT_UI_SELECT == ev->iptkey) + { + left = natkbd.in_use(); + right = !left; + } + if ((left || right) && (natkbd.in_use() != right)) + { + natkbd.set_in_use(right); + ev->item->set_subtext(right ? _("menu-keyboard", "Natural") : _("menu-keyboard", "Emulated")); + ev->item->set_flags(right ? FLAG_LEFT_ARROW : FLAG_RIGHT_ARROW); + return true; + } + } + else if (ITEM_KBDEV_FIRST <= ref) + { + auto const kbdno(ref - ITEM_KBDEV_FIRST); + if (IPT_UI_SELECT == ev->iptkey) + { + left = natkbd.keyboard_enabled(kbdno); + right = !left; + } + if ((left || right) && (natkbd.keyboard_enabled(kbdno) != right)) + { + if (right) + natkbd.enable_keyboard(kbdno); + else + natkbd.disable_keyboard(kbdno); + ev->item->set_subtext(right ? _("Enabled") : _("Disabled")); + ev->item->set_flags(right ? FLAG_LEFT_ARROW : FLAG_RIGHT_ARROW); + return true; + } + } + + return false; +} + +} // namespace ui diff --git a/src/frontend/mame/ui/keyboard.h b/src/frontend/mame/ui/keyboard.h new file mode 100644 index 00000000000..dd51e6c9d1e --- /dev/null +++ b/src/frontend/mame/ui/keyboard.h @@ -0,0 +1,36 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + ui/keyboard.h + + Keyboard mode menu. + +***************************************************************************/ +#ifndef MAME_FRONTEND_UI_KEYBOARD_H +#define MAME_FRONTEND_UI_KEYBOARD_H + +#pragma once + +#include "ui/menu.h" + + +namespace ui { + +class menu_keyboard_mode : public menu +{ +public: + menu_keyboard_mode(mame_ui_manager &mui, render_container &container); + virtual ~menu_keyboard_mode(); + +protected: + virtual void menu_activated() override; + +private: + virtual void populate() override; + virtual bool handle(event const *ev) override; +}; + +} // namespace ui + +#endif // MAME_FRONTEND_UI_KEYBOARD_H diff --git a/src/frontend/mame/ui/mainmenu.cpp b/src/frontend/mame/ui/mainmenu.cpp index b9befd90991..84c6b3d1500 100644 --- a/src/frontend/mame/ui/mainmenu.cpp +++ b/src/frontend/mame/ui/mainmenu.cpp @@ -11,14 +11,18 @@ #include "emu.h" #include "ui/mainmenu.h" +#include "ui/about.h" +#include "ui/audiomix.h" +#include "ui/audioeffects.h" #include "ui/barcode.h" #include "ui/cheatopt.h" +#include "ui/confswitch.h" #include "ui/datmenu.h" #include "ui/filemngr.h" #include "ui/info.h" #include "ui/info_pty.h" #include "ui/inifile.h" -#include "ui/inputmap.h" +#include "ui/inputopts.h" #include "ui/miscmenu.h" #include "ui/pluginopt.h" #include "ui/selgame.h" @@ -31,130 +35,195 @@ #include "mame.h" #include "luaengine.h" -#include "machine/bcreader.h" #include "imagedev/cassette.h" +#include "machine/bcreader.h" #include "crsshair.h" +#include "dinetwork.h" #include "dipty.h" #include "emuopts.h" -#include "natkeyboard.h" namespace ui { +enum : unsigned { + INPUT_OPTIONS, + SETTINGS_DIP_SWITCHES, + SETTINGS_DRIVER_CONFIG, + BOOKKEEPING, + GAME_INFO, + WARN_INFO, + IMAGE_MENU_IMAGE_INFO, + IMAGE_MENU_FILE_MANAGER, + TAPE_CONTROL, + SLOT_DEVICES, + NETWORK_DEVICES, + AUDIO_MIXER, + AUDIO_EFFECTS, + SLIDERS, + VIDEO_TARGETS, + CROSSHAIR, + CHEAT, + PLUGINS, + BIOS_SELECTION, + BARCODE_READ, + PTY_INFO, + EXTERNAL_DATS, + FAVORITE, + ABOUT, + QUIT_GAME, + DISMISS, + SELECT_GAME +}; + /*************************************************************************** MENU HANDLERS ***************************************************************************/ /*------------------------------------------------- - menu_main constructor - populate the main menu + menu_main constructor/destructor -------------------------------------------------*/ -menu_main::menu_main(mame_ui_manager &mui, render_container &container) : menu(mui, container) +menu_main::menu_main(mame_ui_manager &mui, render_container &container) + : menu(mui, container) + , m_phase(machine_phase::PREINIT) +{ + set_needs_prev_menu_item(false); +} + +menu_main::~menu_main() { } -void menu_main::populate(float &customtop, float &custombottom) + +/*------------------------------------------------- + menu_activated - handle coming to foreground +-------------------------------------------------*/ + +void menu_main::menu_activated() { - /* add main menu items */ - item_append(_("Input (general)"), "", 0, (void *)INPUT_GROUPS); + if (machine().phase() != m_phase) + reset(reset_options::REMEMBER_REF); +} - item_append(_("Input (this Machine)"), "", 0, (void *)INPUT_SPECIFIC); - if (ui().machine_info().has_analog()) - item_append(_("Analog Controls"), "", 0, (void *)ANALOG); +/*------------------------------------------------- + populate - populate main menu items +-------------------------------------------------*/ + +void menu_main::populate() +{ + m_phase = machine().phase(); + + item_append(_("menu-main", "Input Settings"), 0, (void *)INPUT_OPTIONS); + if (ui().machine_info().has_dips()) - item_append(_("Dip Switches"), "", 0, (void *)SETTINGS_DIP_SWITCHES); + item_append(_("menu-main", "DIP Switches"), 0, (void *)SETTINGS_DIP_SWITCHES); if (ui().machine_info().has_configs()) - item_append(_("Machine Configuration"), "", 0, (void *)SETTINGS_DRIVER_CONFIG); + item_append(_("menu-main", "Machine Configuration"), 0, (void *)SETTINGS_DRIVER_CONFIG); - item_append(_("Bookkeeping Info"), "", 0, (void *)BOOKKEEPING); + item_append(_("menu-main", "Bookkeeping Info"), 0, (void *)BOOKKEEPING); - item_append(_("Machine Information"), "", 0, (void *)GAME_INFO); + item_append(_("menu-main", "System Information"), 0, (void *)GAME_INFO); - for (device_image_interface &image : image_interface_iterator(machine().root_device())) - { + if (ui().machine_info().has_warnings()) + item_append(_("menu-main", "Warning Information"), 0, (void *)WARN_INFO); + + for (device_image_interface &image : image_interface_enumerator(machine().root_device())) if (image.user_loadable()) { - item_append(_("Image Information"), "", 0, (void *)IMAGE_MENU_IMAGE_INFO); - - item_append(_("File Manager"), "", 0, (void *)IMAGE_MENU_FILE_MANAGER); + item_append(_("menu-main", "Media Image Information"), 0, (void *)IMAGE_MENU_IMAGE_INFO); + break; + } + for (device_image_interface &image : image_interface_enumerator(machine().root_device())) + if (image.user_loadable() || image.has_preset_images_selection()) + { + item_append(_("menu-main", "File Manager"), 0, (void *)IMAGE_MENU_FILE_MANAGER); break; } - } - if (cassette_device_iterator(machine().root_device()).first() != nullptr) - item_append(_("Tape Control"), "", 0, (void *)TAPE_CONTROL); + if (cassette_device_enumerator(machine().root_device()).first() != nullptr) + item_append(_("menu-main", "Tape Control"), 0, (void *)TAPE_CONTROL); - if (pty_interface_iterator(machine().root_device()).first() != nullptr) - item_append(_("Pseudo terminals"), "", 0, (void *)PTY_INFO); + if (pty_interface_enumerator(machine().root_device()).first() != nullptr) + item_append(_("menu-main", "Pseudo Terminals"), 0, (void *)PTY_INFO); if (ui().machine_info().has_bioses()) - item_append(_("BIOS Selection"), "", 0, (void *)BIOS_SELECTION); + item_append(_("menu-main", "BIOS Selection"), 0, (void *)BIOS_SELECTION); + + if (slot_interface_enumerator(machine().root_device()).first() != nullptr) + item_append(_("menu-main", "Slot Devices"), 0, (void *)SLOT_DEVICES); - if (slot_interface_iterator(machine().root_device()).first() != nullptr) - item_append(_("Slot Devices"), "", 0, (void *)SLOT_DEVICES); + if (barcode_reader_device_enumerator(machine().root_device()).first() != nullptr) + item_append(_("menu-main", "Barcode Reader"), 0, (void *)BARCODE_READ); - if (barcode_reader_device_iterator(machine().root_device()).first() != nullptr) - item_append(_("Barcode Reader"), "", 0, (void *)BARCODE_READ); + if (network_interface_enumerator(machine().root_device()).first() != nullptr) + item_append(_("menu-main", "Network Devices"), 0, (void*)NETWORK_DEVICES); - if (network_interface_iterator(machine().root_device()).first() != nullptr) - item_append(_("Network Devices"), "", 0, (void*)NETWORK_DEVICES); + item_append(_("menu-main", "Audio Mixer"), 0, (void *)AUDIO_MIXER); - if (ui().machine_info().has_keyboard() && machine().ioport().natkeyboard().can_post()) - item_append(_("Keyboard Mode"), "", 0, (void *)KEYBOARD_MODE); + item_append(_("menu-main", "Audio Effects"), 0, (void *)AUDIO_EFFECTS); - item_append(_("Slider Controls"), "", 0, (void *)SLIDERS); + item_append(_("menu-main", "Slider Controls"), 0, (void *)SLIDERS); - item_append(_("Video Options"), "", 0, (machine().render().target_by_index(1) != nullptr) ? (void *)VIDEO_TARGETS : (void *)VIDEO_OPTIONS); + item_append(_("menu-main", "Video Options"), 0, (void *)VIDEO_TARGETS); if (machine().crosshair().get_usage()) - item_append(_("Crosshair Options"), "", 0, (void *)CROSSHAIR); + item_append(_("menu-main", "Crosshair Options"), 0, (void *)CROSSHAIR); if (machine().options().cheat()) - item_append(_("Cheat"), "", 0, (void *)CHEAT); + item_append(_("menu-main", "Cheat Options"), 0, (void *)CHEAT); - if (machine().options().plugins()) - item_append(_("Plugin Options"), "", 0, (void *)PLUGINS); + if (machine_phase::RESET <= m_phase) + { + if (machine().options().plugins() && !mame_machine_manager::instance()->lua()->get_menu().empty()) + item_append(_("menu-main", "Plugin Options"), 0, (void *)PLUGINS); - if (mame_machine_manager::instance()->lua()->call_plugin_check<const char *>("data_list", "", true)) - item_append(_("External DAT View"), "", 0, (void *)EXTERNAL_DATS); + if (mame_machine_manager::instance()->lua()->call_plugin_check<const char *>("data_list", "", true)) + item_append(_("menu-main", "External DAT View"), 0, (void *)EXTERNAL_DATS); + } item_append(menu_item_type::SEPARATOR); if (!mame_machine_manager::instance()->favorite().is_favorite(machine())) - item_append(_("Add To Favorites"), "", 0, (void *)ADD_FAVORITE); + item_append(_("menu-main", "Add To Favorites"), 0, (void *)FAVORITE); else - item_append(_("Remove From Favorites"), "", 0, (void *)REMOVE_FAVORITE); + item_append(_("menu-main", "Remove From Favorites"), 0, (void *)FAVORITE); item_append(menu_item_type::SEPARATOR); -// item_append(_("Quit from Machine"), nullptr, 0, (void *)QUIT_GAME); + item_append(string_format(_("menu-main", "About %1$s"), emulator_info::get_appname()), 0, (void *)ABOUT); - item_append(_("Select New Machine"), "", 0, (void *)SELECT_GAME); -} + item_append(menu_item_type::SEPARATOR); -menu_main::~menu_main() -{ +// item_append(_("menu-main", "Quit from System"), 0, (void *)QUIT_GAME); + + if (machine_phase::INIT == m_phase) + { + item_append(_("menu-main", "Start System"), 0, (void *)DISMISS); + } + else + { + item_append(_("menu-main", "Select New System"), 0, (void *)SELECT_GAME); + item_append(_("menu-main", "Close Menu"), 0, (void *)DISMISS); + } } + /*------------------------------------------------- - menu_main - handle the main menu + handle - handle main menu events -------------------------------------------------*/ -void menu_main::handle() +bool menu_main::handle(event const *ev) { - /* process the menu */ - const event *menu_event = process(0); - if (menu_event != nullptr && menu_event->iptkey == IPT_UI_SELECT) { - switch((long long)(menu_event->itemref)) { - case INPUT_GROUPS: - menu::stack_push<menu_input_groups>(ui(), container()); - break; - - case INPUT_SPECIFIC: - menu::stack_push<menu_input_specific>(ui(), container()); + // process the menu + if (ev && (ev->iptkey == IPT_UI_SELECT)) + { + switch (uintptr_t(ev->itemref)) + { + case INPUT_OPTIONS: + menu::stack_push<menu_input_options>(ui(), container()); break; case SETTINGS_DIP_SWITCHES: @@ -162,11 +231,7 @@ void menu_main::handle() break; case SETTINGS_DRIVER_CONFIG: - menu::stack_push<menu_settings_driver_config>(ui(), container()); - break; - - case ANALOG: - menu::stack_push<menu_analog>(ui(), container()); + menu::stack_push<menu_settings_machine_config>(ui(), container()); break; case BOOKKEEPING: @@ -177,12 +242,16 @@ void menu_main::handle() menu::stack_push<menu_game_info>(ui(), container()); break; + case WARN_INFO: + menu::stack_push<menu_warn_info>(ui(), container()); + break; + case IMAGE_MENU_IMAGE_INFO: menu::stack_push<menu_image_info>(ui(), container()); break; case IMAGE_MENU_FILE_MANAGER: - menu::stack_push<menu_file_manager>(ui(), container(), nullptr); + menu::stack_push<menu_file_manager>(ui(), container(), std::string()); break; case TAPE_CONTROL: @@ -201,8 +270,12 @@ void menu_main::handle() menu::stack_push<menu_network_devices>(ui(), container()); break; - case KEYBOARD_MODE: - menu::stack_push<menu_keyboard_mode>(ui(), container()); + case AUDIO_MIXER: + menu::stack_push<menu_audio_mixer>(ui(), container()); + break; + + case AUDIO_EFFECTS: + menu::stack_push<menu_audio_effects>(ui(), container()); break; case SLIDERS: @@ -213,10 +286,6 @@ void menu_main::handle() menu::stack_push<menu_video_targets>(ui(), container()); break; - case VIDEO_OPTIONS: - menu::stack_push<menu_video_options>(ui(), container(), machine().render().first_target()); - break; - case CROSSHAIR: menu::stack_push<menu_crosshair>(ui(), container()); break; @@ -236,6 +305,10 @@ void menu_main::handle() menu::stack_push<menu_select_game>(ui(), container(), nullptr); break; + case ABOUT: + menu::stack_push<menu_about>(ui(), container()); + break; + case BIOS_SELECTION: menu::stack_push<menu_bios_selection>(ui(), container()); break; @@ -248,25 +321,32 @@ void menu_main::handle() menu::stack_push<menu_dats_view>(ui(), container()); break; - case ADD_FAVORITE: - mame_machine_manager::instance()->favorite().add_favorite(machine()); - reset(reset_options::REMEMBER_POSITION); - break; - - case REMOVE_FAVORITE: - mame_machine_manager::instance()->favorite().remove_favorite(machine()); - reset(reset_options::REMEMBER_POSITION); + case FAVORITE: + { + favorite_manager &mfav = mame_machine_manager::instance()->favorite(); + if (mfav.is_favorite(machine())) + mfav.remove_favorite(machine()); + else + mfav.add_favorite(machine()); + reset(reset_options::REMEMBER_REF); break; + } case QUIT_GAME: stack_pop(); ui().request_quit(); break; + case DISMISS: + stack_pop(); + break; + default: fatalerror("ui::menu_main::handle - unknown reference\n"); } } + + return false; } } // namespace ui diff --git a/src/frontend/mame/ui/mainmenu.h b/src/frontend/mame/ui/mainmenu.h index 33ba0d8f35b..d34da5e009e 100644 --- a/src/frontend/mame/ui/mainmenu.h +++ b/src/frontend/mame/ui/mainmenu.h @@ -24,41 +24,16 @@ public: menu_main(mame_ui_manager &mui, render_container &container); virtual ~menu_main(); +protected: + virtual void menu_activated() override; + private: - enum { - INPUT_GROUPS, - INPUT_SPECIFIC, - SETTINGS_DIP_SWITCHES, - SETTINGS_DRIVER_CONFIG, - ANALOG, - BOOKKEEPING, - GAME_INFO, - IMAGE_MENU_IMAGE_INFO, - IMAGE_MENU_FILE_MANAGER, - TAPE_CONTROL, - SLOT_DEVICES, - NETWORK_DEVICES, - KEYBOARD_MODE, - SLIDERS, - VIDEO_TARGETS, - VIDEO_OPTIONS, - CROSSHAIR, - CHEAT, - PLUGINS, - SELECT_GAME, - BIOS_SELECTION, - BARCODE_READ, - PTY_INFO, - EXTERNAL_DATS, - ADD_FAVORITE, - REMOVE_FAVORITE, - QUIT_GAME - }; + virtual void populate() override; + virtual bool handle(event const *ev) override; - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + machine_phase m_phase; }; } // namespace ui -#endif /* MAME_FRONTEND_UI_MAINMENU_H */ +#endif // MAME_FRONTEND_UI_MAINMENU_H diff --git a/src/frontend/mame/ui/menu.cpp b/src/frontend/mame/ui/menu.cpp index 69e08b5489d..6f588bcc17f 100644 --- a/src/frontend/mame/ui/menu.cpp +++ b/src/frontend/mame/ui/menu.cpp @@ -9,51 +9,39 @@ *********************************************************************/ #include "emu.h" - #include "ui/menu.h" #include "ui/ui.h" #include "ui/mainmenu.h" -#include "ui/utils.h" #include "ui/miscmenu.h" #include "cheat.h" #include "mame.h" +#include "corestr.h" #include "drivenum.h" +#include "fileio.h" #include "rendutil.h" #include "uiinput.h" -#include <algorithm> +#include "osdepend.h" + #include <cassert> #include <cmath> -#include <utility> +#include <cstdlib> +#include <limits> +#include <type_traits> namespace ui { /*************************************************************************** - CONSTANTS -***************************************************************************/ - -#define UI_MENU_POOL_SIZE 65536 - -/*************************************************************************** - GLOBAL VARIABLES -***************************************************************************/ - -std::mutex menu::s_global_state_guard; -menu::global_state_map menu::s_global_states; - -/*************************************************************************** INLINE FUNCTIONS ***************************************************************************/ -menu::global_state_ptr menu::get_global_state(running_machine &machine) +menu::global_state &menu::get_global_state(mame_ui_manager &ui) { - std::lock_guard<std::mutex> guard(s_global_state_guard); - auto const it(s_global_states.find(&machine)); - return (it != s_global_states.end()) ? it->second : global_state_ptr(); + return ui.get_session_data<menu, global_state_wrapper>(ui); } //------------------------------------------------- @@ -81,27 +69,40 @@ bool menu::exclusive_input_pressed(int &iptkey, int key, int repeat) CORE SYSTEM MANAGEMENT ***************************************************************************/ -menu::global_state::global_state(running_machine &machine, ui_options const &options) - : widgets_manager(machine) - , m_machine(machine) - , m_cleanup_callbacks() +menu::global_state::global_state(mame_ui_manager &ui) + : widgets_manager(ui.machine()) + , m_ui(ui) , m_bgrnd_bitmap() - , m_bgrnd_texture(nullptr, machine.render()) + , m_bgrnd_texture(nullptr, ui.machine().render()) , m_stack() , m_free() + , m_hide(false) + , m_current_pointer(-1) + , m_pointer_type(ui_event::pointer::UNKNOWN) + , m_pointer_buttons(0U) + , m_pointer_x(-1.0F) + , m_pointer_y(-1.0F) + , m_pointer_hit(false) { - render_manager &render(machine.render()); + render_manager &render(ui.machine().render()); // create a texture for main menu background m_bgrnd_texture.reset(render.texture_alloc(render_texture::hq_scale)); - if (options.use_background_image() && (&machine.system() == &GAME_NAME(___empty))) + if (ui.options().use_background_image() && (&ui.machine().system() == &GAME_NAME(___empty))) { m_bgrnd_bitmap = std::make_unique<bitmap_argb32>(0, 0); emu_file backgroundfile(".", OPEN_FLAG_READ); - render_load_jpeg(*m_bgrnd_bitmap, backgroundfile, nullptr, "background.jpg"); + if (!backgroundfile.open("background.jpg")) + { + render_load_jpeg(*m_bgrnd_bitmap, backgroundfile); + backgroundfile.close(); + } - if (!m_bgrnd_bitmap->valid()) - render_load_png(*m_bgrnd_bitmap, backgroundfile, nullptr, "background.png"); + if (!m_bgrnd_bitmap->valid() && !backgroundfile.open("background.png")) + { + render_load_png(*m_bgrnd_bitmap, backgroundfile); + backgroundfile.close(); + } if (m_bgrnd_bitmap->valid()) m_bgrnd_texture->set_bitmap(*m_bgrnd_bitmap, m_bgrnd_bitmap->cliprect(), TEXFORMAT_ARGB32); @@ -113,29 +114,35 @@ menu::global_state::global_state(running_machine &machine, ui_options const &opt menu::global_state::~global_state() { - // it shouldn't really be possible to get here with active menus because of reference loops - assert(!m_stack); - assert(!m_free); - stack_reset(); clear_free_list(); - - for (auto const &callback : m_cleanup_callbacks) - callback(m_machine); -} - - -void menu::global_state::add_cleanup_callback(cleanup_callback &&callback) -{ - m_cleanup_callbacks.emplace_back(std::move(callback)); } void menu::global_state::stack_push(std::unique_ptr<menu> &&menu) { + if (m_stack && m_stack->is_active()) + { + m_stack->m_active = false; + m_stack->menu_deactivated(); + } menu->m_parent = std::move(m_stack); m_stack = std::move(menu); - m_stack->reset(reset_options::SELECT_FIRST); + + ui_event uievt; + while (m_stack->machine().ui_input().pop_event(&uievt)) + { + switch (uievt.event_type) + { + case ui_event::type::POINTER_UPDATE: + case ui_event::type::POINTER_LEAVE: + case ui_event::type::POINTER_ABORT: + use_pointer(m_stack->machine().render().ui_target(), m_stack->container(), uievt); + break; + default: + break; + } + } m_stack->machine().ui_input().reset(); } @@ -144,11 +151,34 @@ void menu::global_state::stack_pop() { if (m_stack) { + if (m_stack->is_one_shot()) + m_hide = true; + if (m_stack->is_active()) + { + m_stack->m_active = false; + m_stack->menu_deactivated(); + } + m_stack->menu_dismissed(); std::unique_ptr<menu> menu(std::move(m_stack)); m_stack = std::move(menu->m_parent); menu->m_parent = std::move(m_free); m_free = std::move(menu); - m_machine.ui_input().reset(); + + ui_event uievt; + while (m_free->machine().ui_input().pop_event(&uievt)) + { + switch (uievt.event_type) + { + case ui_event::type::POINTER_UPDATE: + case ui_event::type::POINTER_LEAVE: + case ui_event::type::POINTER_ABORT: + use_pointer(m_free->machine().render().ui_target(), m_free->container(), uievt); + break; + default: + break; + } + } + m_free->machine().ui_input().reset(); } } @@ -187,40 +217,158 @@ bool menu::global_state::stack_has_special_main_menu() const } +uint32_t menu::global_state::ui_handler(render_container &container) +{ + // if we have no menus stacked up, start with the main menu + if (!m_stack) + stack_push(std::make_unique<menu_main>(m_ui, container)); + while (true) + { + // ensure topmost menu is active - need a loop because it could push another menu + while (m_stack && !m_stack->is_active()) + { + m_stack->activate_menu(); + if (m_stack && m_stack->is_active()) + { + // menu activated - draw it to ensure it's on-screen before it can process input + m_stack->check_metrics(); + m_stack->do_rebuild(); + m_stack->validate_selection(1); + m_stack->do_draw_menu(); + assert(m_stack); + assert(m_stack->is_active()); + + // display pointer if appropriate + mame_ui_manager::display_pointer pointers[1]{ { m_stack->machine().render().ui_target(), m_pointer_type, m_pointer_x, m_pointer_y } }; + if ((0 <= m_current_pointer) && (ui_event::pointer::TOUCH != m_pointer_type)) + m_ui.set_pointers(std::begin(pointers), std::end(pointers)); + else + m_ui.set_pointers(std::begin(pointers), std::begin(pointers)); -//------------------------------------------------- -// init - initialize the menu system -//------------------------------------------------- + return mame_ui_manager::HANDLER_UPDATE; + } + } -void menu::init(running_machine &machine, ui_options &mopt) -{ - // initialize the menu stack - { - std::lock_guard<std::mutex> guard(s_global_state_guard); - auto const ins(s_global_states.emplace(&machine, std::make_shared<global_state>(machine, mopt))); - assert(ins.second); // calling init twice is bad - if (ins.second) - machine.add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(&menu::exit, &machine)); // add an exit callback to free memory - else - ins.first->second->stack_reset(); + // update the menu state + m_hide = false; + bool need_update(m_stack && m_stack->do_handle()); + + // clear up anything pending being released + clear_free_list(); + + // if the menus are to be hidden, return a cancel here + if (m_ui.is_menu_active() && (m_hide || !m_stack)) + { + if (m_stack) + { + if (m_stack->is_one_shot()) + { + stack_pop(); + } + else if (m_stack->is_active()) + { + m_stack->m_active = false; + m_stack->menu_deactivated(); + } + } + + // forget about pointers while menus aren't handling events + m_current_pointer = -1; + m_pointer_type = ui_event::pointer::UNKNOWN; + m_pointer_buttons = 0U; + m_pointer_x = -1.0F; + m_pointer_y = -1.0F; + m_pointer_hit = false; + + return mame_ui_manager::HANDLER_CANCEL; + } + + // if the menu is still active, draw it, otherwise try again + if (m_stack->is_active()) + { + m_stack->do_draw_menu(); + + // display pointer if appropriate + mame_ui_manager::display_pointer pointers[1]{ { m_stack->machine().render().ui_target(), m_pointer_type, m_pointer_x, m_pointer_y } }; + if ((0 <= m_current_pointer) && (ui_event::pointer::TOUCH != m_pointer_type)) + m_ui.set_pointers(std::begin(pointers), std::end(pointers)); + else + m_ui.set_pointers(std::begin(pointers), std::begin(pointers)); + + return need_update ? mame_ui_manager::HANDLER_UPDATE : 0; + } } } -//------------------------------------------------- -// exit - clean up after ourselves -//------------------------------------------------- - -void menu::exit(running_machine &machine) +std::pair<bool, bool> menu::global_state::use_pointer(render_target &target, render_container &container, ui_event const &uievt) { - // free menus - global_state_ptr const state(get_global_state(machine)); - state->stack_reset(); - state->clear_free_list(); + if (&target != uievt.target) + return std::make_pair(false, false); + + switch (uievt.event_type) + { + case ui_event::type::POINTER_UPDATE: + // if it's our current pointer, just update it + if (uievt.pointer_id == m_current_pointer) + { + assert(uievt.pointer_type == m_pointer_type); + assert(uievt.pointer_buttons == ((m_pointer_buttons & ~uievt.pointer_released) | uievt.pointer_pressed)); + + m_pointer_buttons = uievt.pointer_buttons; + m_pointer_hit = target.map_point_container( + uievt.pointer_x, + uievt.pointer_y, + container, + m_pointer_x, + m_pointer_y); + return std::make_pair(true, false); + } + + // don't change if the current pointer has buttons pressed and this one doesn't + if ((0 > m_current_pointer) || (!m_pointer_buttons && (!m_pointer_hit || uievt.pointer_pressed))) + { + float x, y; + bool const hit(target.map_point_container(uievt.pointer_x, uievt.pointer_y, container, x, y)); + if ((0 > m_current_pointer) || uievt.pointer_pressed || (!m_pointer_hit && hit)) + { + m_current_pointer = uievt.pointer_id; + m_pointer_type = uievt.pointer_type; + m_pointer_buttons = uievt.pointer_buttons; + m_pointer_x = x; + m_pointer_y = y; + m_pointer_hit = hit; + return std::make_pair(true, true); + } + } - std::lock_guard<std::mutex> guard(s_global_state_guard); - s_global_states.erase(&machine); + // keep current pointer + return std::make_pair(false, false); + + case ui_event::type::POINTER_LEAVE: + case ui_event::type::POINTER_ABORT: + // irrelevant if it isn't our current pointer + if (uievt.pointer_id != m_current_pointer) + return std::make_pair(false, false); + + assert(uievt.pointer_type == m_pointer_type); + assert(uievt.pointer_released == m_pointer_buttons); + + // keep the coordinates where we lost the pointer + m_current_pointer = -1; + m_pointer_buttons = 0U; + m_pointer_hit = target.map_point_container( + uievt.pointer_x, + uievt.pointer_y, + container, + m_pointer_x, + m_pointer_y); + return std::make_pair(true, false); + + default: + std::abort(); + } } @@ -234,29 +382,53 @@ void menu::exit(running_machine &machine) //------------------------------------------------- menu::menu(mame_ui_manager &mui, render_container &container) - : m_visible_lines(0) - , m_visible_items(0) - , m_global_state(get_global_state(mui.machine())) - , m_special_main_menu(false) + : m_global_state(get_global_state(mui)) , m_ui(mui) , m_container(container) , m_parent() - , m_event() - , m_pool(nullptr) - , m_customtop(0.0f) - , m_custombottom(0.0f) + , m_heading() + , m_items() + , m_rebuilding(false) + , m_last_size(0, 0) + , m_last_aspect(0.0F) + , m_line_height(0.0F) + , m_gutter_width(0.0F) + , m_tb_border(0.0F) + , m_lr_border(0.0F) + , m_lr_arrow_width(0.0F) + , m_ud_arrow_width(0.0F) + , m_items_left(0.0F) + , m_items_right(0.0F) + , m_items_top(0.0F) + , m_adjust_top(0.0F) + , m_adjust_bottom(0.0F) + , m_decrease_left(0.0F) + , m_increase_left(0.0F) + , m_show_up_arrow(false) + , m_show_down_arrow(false) + , m_items_drawn(false) + , m_pointer_state(track_pointer::IDLE) + , m_pointer_down(0.0F, 0.0F) + , m_pointer_updated(0.0F, 0.0F) + , m_pointer_line(0) + , m_pointer_repeat(std::chrono::steady_clock::time_point::min()) + , m_accumulated_wheel(0) + , m_process_flags(0) + , m_selected(0) + , m_special_main_menu(false) + , m_one_shot(false) + , m_needs_prev_menu_item(true) + , m_active(false) + , m_customtop(0.0F) + , m_custombottom(0.0F) , m_resetpos(0) , m_resetref(nullptr) - , m_mouse_hit(false) - , m_mouse_button(false) - , m_mouse_x(-1.0f) - , m_mouse_y(-1.0f) { - assert(m_global_state); // not calling init is bad - reset(reset_options::SELECT_FIRST); top_line = 0; + m_visible_lines = 0; + m_visible_items = 0; } @@ -266,23 +438,19 @@ menu::menu(mame_ui_manager &mui, render_container &container) menu::~menu() { - // free the pools - while (m_pool) - { - pool *const ppool = m_pool; - m_pool = m_pool->next; - global_free_array(ppool); - } } //------------------------------------------------- -// reset - free all items in the menu, -// and all memory allocated from the memory pool +// reset - free all items in the menu //------------------------------------------------- void menu::reset(reset_options options) { + // don't accept pointer input until the menu has been redrawn + m_items_drawn = false; + m_pointer_state = track_pointer::IDLE; + // based on the reset option, set the reset info m_resetpos = 0; m_resetref = nullptr; @@ -291,44 +459,10 @@ void menu::reset(reset_options options) else if (options == reset_options::REMEMBER_REF) m_resetref = get_selection_ref(); - // reset all the pools and the item count back to 0 - for (pool *ppool = m_pool; ppool != nullptr; ppool = ppool->next) - ppool->top = (uint8_t *)(ppool + 1); + // reset the item count back to 0 m_items.clear(); m_visible_items = 0; m_selected = 0; - - // add an item to return - if (!m_parent) - { - item_append(_("Return to Machine"), "", 0, nullptr); - } - else if (m_parent->is_special_main_menu()) - { - if (machine().options().ui() == emu_options::UI_SIMPLE) - item_append(_("Exit"), "", 0, nullptr); - else - item_append(_("Exit"), "", FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW, nullptr); - } - else - { - if (machine().options().ui() != emu_options::UI_SIMPLE && stack_has_special_main_menu()) - item_append(_("Return to Previous Menu"), "", FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW, nullptr); - else - item_append(_("Return to Previous Menu"), "", 0, nullptr); - } - -} - - -//------------------------------------------------- -// is_special_main_menu - returns whether the -// menu has special needs -//------------------------------------------------- - -bool menu::is_special_main_menu() const -{ - return m_special_main_menu; } @@ -348,30 +482,13 @@ void menu::set_special_main_menu(bool special) // end of the menu //------------------------------------------------- -void menu::item_append(menu_item item) -{ - item_append(item.text, item.subtext, item.flags, item.ref, item.type); -} - -//------------------------------------------------- -// item_append - append a new item to the -// end of the menu -//------------------------------------------------- - -void menu::item_append(menu_item_type type, uint32_t flags) +int menu::item_append(menu_item_type type, uint32_t flags) { + assert(menu_item_type::SEPARATOR == type); if (type == menu_item_type::SEPARATOR) - item_append(MENU_SEPARATOR_ITEM, "", flags, nullptr, menu_item_type::SEPARATOR); -} - -//------------------------------------------------- -// item_append - append a new item to the -// end of the menu -//------------------------------------------------- - -void menu::item_append(const std::string &text, const std::string &subtext, uint32_t flags, void *ref, menu_item_type type) -{ - item_append(std::string(text), std::string(subtext), flags, ref, type); + return item_append(MENU_SEPARATOR_ITEM, flags, nullptr, menu_item_type::SEPARATOR); + else + return -1; } //------------------------------------------------- @@ -379,39 +496,34 @@ void menu::item_append(const std::string &text, const std::string &subtext, uint // end of the menu //------------------------------------------------- -void menu::item_append(std::string &&text, std::string &&subtext, uint32_t flags, void *ref, menu_item_type type) +int menu::item_append(std::string &&text, std::string &&subtext, uint32_t flags, void *ref, menu_item_type type) { - // only allow multiline as the first item - if ((flags & FLAG_MULTILINE) != 0) - assert(m_items.size() == 1); - - // only allow a single multi-line item - else if (m_items.size() >= 2) - assert((m_items[0].flags & FLAG_MULTILINE) == 0); + assert(m_rebuilding); // allocate a new item and populate it - menu_item pitem; - pitem.text = std::move(text); - pitem.subtext = std::move(subtext); - pitem.flags = flags; - pitem.ref = ref; - pitem.type = type; + menu_item pitem(type, ref, flags); + pitem.set_text(std::move(text)); + pitem.set_subtext(std::move(subtext)); // append to array auto index = m_items.size(); - if (!m_items.empty()) + if (!m_items.empty() && m_needs_prev_menu_item) { m_items.emplace(m_items.end() - 1, std::move(pitem)); --index; } else + { m_items.emplace_back(std::move(pitem)); + } // update the selection if we need to - if (m_resetpos == index || (m_resetref != nullptr && m_resetref == ref)) + if ((m_resetpos == index) || (m_resetref && (m_resetref == ref))) m_selected = index; if (m_resetpos == (m_items.size() - 1)) m_selected = m_items.size() - 1; + + return int(std::make_signed_t<decltype(index)>(index)); } @@ -420,101 +532,26 @@ void menu::item_append(std::string &&text, std::string &&subtext, uint32_t flags // item to the end of the menu //------------------------------------------------- -void menu::item_append_on_off(const std::string &text, bool state, uint32_t flags, void *ref, menu_item_type type) +int menu::item_append_on_off(const std::string &text, bool state, uint32_t flags, void *ref, menu_item_type type) { if (flags & FLAG_DISABLE) ref = nullptr; else flags |= state ? FLAG_LEFT_ARROW : FLAG_RIGHT_ARROW; - item_append(std::string(text), state ? _("On") : _("Off"), flags, ref, type); + return item_append(std::string(text), state ? _("On") : _("Off"), flags, ref, type); } //------------------------------------------------- -// repopulate - repopulate menu items +// set_custom_space - set space required for +// custom rendering above and below menu //------------------------------------------------- -void menu::repopulate(reset_options options) +void menu::set_custom_space(float top, float bottom) { - reset(options); - populate(m_customtop, m_custombottom); -} - - -//------------------------------------------------- -// process - process a menu, drawing it -// and returning any interesting events -//------------------------------------------------- - -const menu::event *menu::process(uint32_t flags, float x0, float y0) -{ - // reset the event - m_event.iptkey = IPT_INVALID; - - // first make sure our selection is valid - validate_selection(1); - - // draw the menu - if (m_items.size() > 1 && (m_items[0].flags & FLAG_MULTILINE) != 0) - draw_text_box(); - else - draw(flags); - - // process input - if (!(flags & PROCESS_NOKEYS) && !(flags & PROCESS_NOINPUT)) - { - // read events - handle_events(flags, m_event); - - // handle the keys if we don't already have an event - if (m_event.iptkey == IPT_INVALID) - handle_keys(flags, m_event.iptkey); - } - - // update the selected item in the event - if ((m_event.iptkey != IPT_INVALID) && selection_valid()) - { - m_event.itemref = get_selection_ref(); - m_event.type = m_items[m_selected].type; - return &m_event; - } - else - { - return nullptr; - } -} - - -//------------------------------------------------- -// m_pool_alloc - allocate temporary memory -// from the menu's memory pool -//------------------------------------------------- - -void *menu::m_pool_alloc(size_t size) -{ - assert(size < UI_MENU_POOL_SIZE); - - // find a pool with enough room - for (pool *ppool = m_pool; ppool != nullptr; ppool = ppool->next) - { - if (ppool->end - ppool->top >= size) - { - void *result = ppool->top; - ppool->top += size; - return result; - } - } - - // allocate a new pool - pool *ppool = (pool *)global_alloc_array_clear<uint8_t>(sizeof(*ppool) + UI_MENU_POOL_SIZE); - - // wire it up - ppool->next = m_pool; - m_pool = ppool; - ppool->top = (uint8_t *)(ppool + 1); - ppool->end = ppool->top + UI_MENU_POOL_SIZE; - return m_pool_alloc(size); + m_customtop = top; + m_custombottom = bottom; } @@ -528,7 +565,7 @@ void menu::set_selection(void *selected_itemref) m_selected = -1; for (int itemnum = 0; itemnum < m_items.size(); itemnum++) { - if (m_items[itemnum].ref == selected_itemref) + if (m_items[itemnum].ref() == selected_itemref) { m_selected = itemnum; break; @@ -543,27 +580,39 @@ void menu::set_selection(void *selected_itemref) ***************************************************************************/ //------------------------------------------------- -// draw - draw a menu +// do_draw_menu - draw a menu //------------------------------------------------- -void menu::draw(uint32_t flags) +void menu::do_draw_menu() { - // first draw the FPS counter - if (ui().show_fps_counter()) - { - ui().draw_text_full(container(), machine().video().speed_text().c_str(), 0.0f, 0.0f, 1.0f, - ui::text_layout::RIGHT, ui::text_layout::WORD, mame_ui_manager::OPAQUE_, rgb_t::white(), rgb_t::black(), nullptr, nullptr); - } + // if we're not running the emulation, draw parent menus in the background + auto const draw_parent = + [] (auto &self, menu *parent) -> bool + { + if (!parent || !(parent->is_special_main_menu() || self(self, parent->m_parent.get()))) + return false; + else + parent->draw(PROCESS_NOINPUT); + return true; + }; + if (draw_parent(draw_parent, m_parent.get())) + container().add_rect(0.0F, 0.0F, 1.0F, 1.0F, rgb_t(114, 0, 0, 0), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + + // draw the menu proper + draw(m_process_flags); +} + + +//------------------------------------------------- +// draw - draw the menu itself +//------------------------------------------------- +void menu::draw(uint32_t flags) +{ bool const customonly = (flags & PROCESS_CUSTOM_ONLY); - bool const noimage = (flags & PROCESS_NOIMAGE); - bool const noinput = (flags & PROCESS_NOINPUT); - float const line_height = ui().get_line_height(); - float const lr_arrow_width = 0.4f * line_height * machine().render().ui_aspect(); - float const ud_arrow_width = line_height * machine().render().ui_aspect(); - float const gutter_width = lr_arrow_width * 1.3f; - - if (&machine().system() == &GAME_NAME(___empty) && !noimage) + float const max_width = 1.0F - ((lr_border() + (x_aspect() * UI_LINE_WIDTH)) * 2.0F); + + if (is_special_main_menu()) draw_background(); // compute the width and height of the full menu @@ -572,186 +621,258 @@ void menu::draw(uint32_t flags) for (auto const &pitem : m_items) { // compute width of left hand side - float total_width = gutter_width + ui().get_string_width(pitem.text.c_str()) + gutter_width; + float total_width = gutter_width() + get_string_width(pitem.text()) + gutter_width(); // add in width of right hand side - if (!pitem.subtext.empty()) - total_width += 2.0f * gutter_width + ui().get_string_width(pitem.subtext.c_str()); + if (!pitem.subtext().empty()) + total_width += 2.0F * gutter_width() + get_string_width(pitem.subtext()); + else if (pitem.flags() & FLAG_UI_HEADING) + total_width += 4.0F * ud_arrow_width(); // track the maximum - if (total_width > visible_width) - visible_width = total_width; + visible_width = std::max(total_width, visible_width); // track the height as well - visible_main_menu_height += line_height; + visible_main_menu_height += line_height(); + } + + // lay out the heading if present + std::optional<text_layout> heading_layout; + if (m_heading) + { + heading_layout.emplace(create_layout(max_width - (gutter_width() * 2.0F), text_layout::text_justify::CENTER)); + heading_layout->add_text(*m_heading, ui().colors().text_color()); + + // readjust visible width if heading width exceeds that of the menu + visible_width = std::max(gutter_width() + heading_layout->actual_width() + gutter_width(), visible_width); } // account for extra space at the top and bottom - float const visible_extra_menu_height = m_customtop + m_custombottom; + float const top_extra_menu_height = m_customtop + (heading_layout ? (heading_layout->actual_height() + (tb_border() * 3.0F)) : 0.0F); + float const visible_extra_menu_height = top_extra_menu_height + m_custombottom; // add a little bit of slop for rounding - visible_width += 0.01f; - visible_main_menu_height += 0.01f; + visible_width += 0.01F; + visible_main_menu_height += 0.01F; // if we are too wide or too tall, clamp it down - if (visible_width + 2.0f * ui().box_lr_border() > 1.0f) - visible_width = 1.0f - 2.0f * ui().box_lr_border(); + visible_width = std::min(visible_width, max_width); // if the menu and extra menu won't fit, take away part of the regular menu, it will scroll - if (visible_main_menu_height + visible_extra_menu_height + 2.0f * ui().box_tb_border() > 1.0f) - visible_main_menu_height = 1.0f - 2.0f * ui().box_tb_border() - visible_extra_menu_height; + if (visible_main_menu_height + visible_extra_menu_height + 2.0F * tb_border() > 1.0F) + visible_main_menu_height = 1.0F - 2.0F * tb_border() - visible_extra_menu_height; - m_visible_lines = std::min(int(std::floor(visible_main_menu_height / line_height)), int(unsigned(m_items.size()))); - visible_main_menu_height = float(m_visible_lines) * line_height; + m_visible_lines = std::min(int(std::floor(visible_main_menu_height / line_height())), int(unsigned(m_items.size()))); + visible_main_menu_height = float(m_visible_lines) * line_height(); // compute top/left of inner menu area by centering - float const visible_left = (1.0f - visible_width) * 0.5f; - float const visible_top = ((1.0f - visible_main_menu_height - visible_extra_menu_height) * 0.5f) + m_customtop; + float const visible_left = (1.0F - visible_width) * 0.5F; + m_items_top = ((1.0F - visible_main_menu_height - visible_extra_menu_height) * 0.5F) + top_extra_menu_height; + if (m_last_size.second != 0) + m_items_top = std::round(m_items_top * float(m_last_size.second)) / float(m_last_size.second); // first add us a box - float const x1 = visible_left - ui().box_lr_border(); - float const y1 = visible_top - ui().box_tb_border(); - float const x2 = visible_left + visible_width + ui().box_lr_border(); - float const y2 = visible_top + visible_main_menu_height + ui().box_tb_border(); + float const x1 = visible_left - lr_border(); + float const y1 = m_items_top - tb_border(); + float const x2 = visible_left + visible_width + lr_border(); + float const y2 = m_items_top + visible_main_menu_height + tb_border(); if (!customonly) - ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color()); + { + if (heading_layout) + { + ui().draw_outlined_box( + container(), + x1, y1 - top_extra_menu_height, + x2, y1 - m_customtop - tb_border(), + UI_GREEN_COLOR); + heading_layout->emit(container(), (1.0F - heading_layout->width()) * 0.5F, y1 - top_extra_menu_height + tb_border()); + } + ui().draw_outlined_box( + container(), + x1, y1, + x2, y2, + ui().colors().background_color()); + } + + if ((m_selected >= (top_line + m_visible_lines)) || (m_selected < (top_line + 1))) + top_line = m_selected - (m_visible_lines / 2); if (top_line < 0 || is_first_selected()) top_line = 0; - if (m_selected >= (top_line + m_visible_lines)) - top_line = m_selected - (m_visible_lines / 2); - if ((top_line > (m_items.size() - m_visible_lines)) || is_last_selected()) + else if ((top_line > (m_items.size() - m_visible_lines)) || is_last_selected()) top_line = m_items.size() - m_visible_lines; + else if (m_selected >= (top_line + m_visible_lines - 2)) + top_line = m_selected - m_visible_lines + ((m_selected == (m_items.size() - 1)) ? 1: 2); // if scrolling, show arrows - bool const show_top_arrow((m_items.size() > m_visible_lines) && !first_item_visible()); - bool const show_bottom_arrow((m_items.size() > m_visible_lines) && !last_item_visible()); + m_show_up_arrow = (m_items.size() > m_visible_lines) && !first_item_visible(); + m_show_down_arrow = (m_items.size() > m_visible_lines) && !last_item_visible(); // set the number of visible lines, minus 1 for top arrow and 1 for bottom arrow - m_visible_items = m_visible_lines - (show_top_arrow ? 1 : 0) - (show_bottom_arrow ? 1 : 0); + m_visible_items = m_visible_lines - (m_show_up_arrow ? 1 : 0) - (m_show_down_arrow ? 1 : 0); // determine effective positions taking into account the hilighting arrows - float const effective_width = visible_width - 2.0f * gutter_width; - float const effective_left = visible_left + gutter_width; - - // locate mouse - if (!customonly && !noinput) - map_mouse(); - else - ignore_mouse(); + float const effective_width = visible_width - 2.0F * gutter_width(); + float const effective_left = visible_left + gutter_width(); // loop over visible lines - m_hover = m_items.size() + 1; bool selected_subitem_too_big = false; - float const line_x0 = x1 + 0.5f * UI_LINE_WIDTH; - float const line_x1 = x2 - 0.5f * UI_LINE_WIDTH; - if (!customonly) + m_items_left = x1 + 0.5F * UI_LINE_WIDTH; + m_items_right = x2 - 0.5F * UI_LINE_WIDTH; + if (customonly) { + m_items_drawn = false; + switch (m_pointer_state) + { + case track_pointer::IDLE: + case track_pointer::IGNORED: + case track_pointer::COMPLETED: + case track_pointer::CUSTOM: + break; + case track_pointer::TRACK_LINE: + case track_pointer::SCROLL: + case track_pointer::ADJUST: + m_pointer_state = track_pointer::COMPLETED; + } + } + else + { + m_adjust_top = 1.0F; + m_adjust_bottom = 0.0F; + m_decrease_left = -1.0F; + m_increase_left = -1.0F; + m_items_drawn = true; for (int linenum = 0; linenum < m_visible_lines; linenum++) { auto const itemnum = top_line + linenum; menu_item const &pitem = m_items[itemnum]; - char const *const itemtext = pitem.text.c_str(); + std::string_view const itemtext = pitem.text(); rgb_t fgcolor = ui().colors().text_color(); rgb_t bgcolor = ui().colors().text_bg_color(); rgb_t fgcolor2 = ui().colors().subitem_color(); rgb_t fgcolor3 = ui().colors().clone_color(); - float const line_y0 = visible_top + (float)linenum * line_height; - float const line_y1 = line_y0 + line_height; + float const line_y0 = m_items_top + (float(linenum) * line_height()); + float const line_y1 = line_y0 + 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)) - m_hover = itemnum; + // work out what we're dealing with + bool const uparrow = !linenum && m_show_up_arrow; + bool const downarrow = (linenum == (m_visible_lines - 1)) && m_show_down_arrow; - // if we're selected, draw with a different background + // highlight if necessary if (is_selected(itemnum)) { + // if we're selected, draw with a different background fgcolor = fgcolor2 = fgcolor3 = ui().colors().selected_color(); bgcolor = ui().colors().selected_bg_color(); } - - // else if the mouse is over this item, draw with a different background - else if (itemnum == m_hover) + else if (uparrow || downarrow || is_selectable(pitem)) { - fgcolor = fgcolor2 = fgcolor3 = ui().colors().mouseover_color(); - bgcolor = ui().colors().mouseover_bg_color(); + bool pointerline(linenum == m_pointer_line); + if ((track_pointer::ADJUST == m_pointer_state) && pointerline) + { + // use the hover background if an adjust gesture is attempted on an item that isn't selected + fgcolor = fgcolor2 = fgcolor3 = ui().colors().mouseover_color(); + bgcolor = ui().colors().mouseover_bg_color(); + } + else if (have_pointer() && pointer_in_rect(m_items_left, line_y0, m_items_right, line_y1)) + { + if ((track_pointer::TRACK_LINE == m_pointer_state) && pointerline) + { + // use the selected background for an item being selected + fgcolor = fgcolor2 = fgcolor3 = ui().colors().selected_color(); + bgcolor = ui().colors().selected_bg_color(); + } + else if (track_pointer::IDLE == m_pointer_state) + { + // else if the pointer is over this item, draw with a different background + fgcolor = fgcolor2 = fgcolor3 = ui().colors().mouseover_color(); + bgcolor = ui().colors().mouseover_bg_color(); + } + } + else if ((track_pointer::TRACK_LINE == m_pointer_state) && pointerline) + { + // use the hover background if the pointer moved out of the tracked item + fgcolor = fgcolor2 = fgcolor3 = ui().colors().mouseover_color(); + bgcolor = ui().colors().mouseover_bg_color(); + } } // if we have some background hilighting to do, add a quad behind everything else if (bgcolor != ui().colors().text_bg_color()) - highlight(line_x0, line_y0, line_x1, line_y1, bgcolor); + highlight(m_items_left, line_y0, m_items_right, line_y1, bgcolor); - if (linenum == 0 && show_top_arrow) + if (uparrow || downarrow) { - // if we're on the top line, display the up arrow + // if we're on the top or bottom line, display the up or down arrow draw_arrow( - 0.5f * (x1 + x2) - 0.5f * ud_arrow_width, - line_y0 + 0.25f * line_height, - 0.5f * (x1 + x2) + 0.5f * ud_arrow_width, - line_y0 + 0.75f * line_height, - fgcolor, - ROT0); - if (m_hover == itemnum) - m_hover = HOVER_ARROW_UP; + 0.5F * (x1 + x2 - ud_arrow_width()), + line_y0 + (0.25F * line_height()), + 0.5F * (x1 + x2 + ud_arrow_width()), + line_y0 + (0.75F * line_height()), + fgcolor, + downarrow ? (ROT0 ^ ORIENTATION_FLIP_Y) : ROT0); } - else if (linenum == m_visible_lines - 1 && show_bottom_arrow) - { - // if we're on the bottom line, display the down arrow - draw_arrow( - 0.5f * (x1 + x2) - 0.5f * ud_arrow_width, - line_y0 + 0.25f * line_height, - 0.5f * (x1 + x2) + 0.5f * ud_arrow_width, - line_y0 + 0.75f * line_height, - fgcolor, - ROT0 ^ ORIENTATION_FLIP_Y); - if (m_hover == itemnum) - m_hover = HOVER_ARROW_DOWN; - } - 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_y0 + 0.5f * line_height, visible_left + visible_width, line_y0 + 0.5f * line_height, UI_LINE_WIDTH, ui().colors().border_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + container().add_line(visible_left, line_y0 + 0.5F * line_height(), visible_left + visible_width, line_y0 + 0.5F * line_height(), UI_LINE_WIDTH, ui().colors().border_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } - else if (pitem.subtext.empty()) + else if (pitem.subtext().empty()) { // if we don't have a subitem, just draw the string centered - if (pitem.flags & FLAG_UI_HEADING) + if (pitem.flags() & FLAG_UI_HEADING) { - float heading_width = ui().get_string_width(itemtext); - container().add_line(visible_left, line_y0 + 0.5f * line_height, visible_left + ((visible_width - heading_width) / 2) - ui().box_lr_border(), line_y0 + 0.5f * line_height, UI_LINE_WIDTH, ui().colors().border_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); - container().add_line(visible_left + visible_width - ((visible_width - heading_width) / 2) + ui().box_lr_border(), line_y0 + 0.5f * line_height, visible_left + visible_width, line_y0 + 0.5f * line_height, UI_LINE_WIDTH, ui().colors().border_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + float heading_width = get_string_width(itemtext); + container().add_line(visible_left, line_y0 + 0.5F * line_height(), visible_left + ((visible_width - heading_width) / 2) - lr_border(), line_y0 + 0.5F * line_height(), UI_LINE_WIDTH, ui().colors().border_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + container().add_line(visible_left + visible_width - ((visible_width - heading_width) / 2) + lr_border(), line_y0 + 0.5F * line_height(), visible_left + visible_width, line_y0 + 0.5F * line_height(), UI_LINE_WIDTH, ui().colors().border_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } - ui().draw_text_full(container(), itemtext, effective_left, line_y0, effective_width, - ui::text_layout::CENTER, ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, fgcolor, bgcolor, nullptr, nullptr); + ui().draw_text_full( + container(), + itemtext, + effective_left, line_y0, effective_width, + text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, + mame_ui_manager::NORMAL, fgcolor, bgcolor, + nullptr, nullptr, + line_height()); } else { // otherwise, draw the item on the left and the subitem text on the right - bool const subitem_invert(pitem.flags & FLAG_INVERT); - char const *subitem_text(pitem.subtext.c_str()); + bool const subitem_invert(pitem.flags() & FLAG_INVERT); float item_width, subitem_width; // draw the left-side text - ui().draw_text_full(container(), itemtext, effective_left, line_y0, effective_width, - ui::text_layout::LEFT, ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, fgcolor, bgcolor, &item_width, nullptr); - - if (pitem.flags & FLAG_COLOR_BOX) + ui().draw_text_full( + container(), + itemtext, + effective_left, line_y0, effective_width, + text_layout::text_justify::LEFT, text_layout::word_wrapping::TRUNCATE, + mame_ui_manager::NORMAL, fgcolor, bgcolor, + &item_width, nullptr, + line_height()); + + if (pitem.flags() & FLAG_COLOR_BOX) { - rgb_t color = rgb_t((uint32_t)strtoul(subitem_text, nullptr, 16)); + rgb_t color = rgb_t((uint32_t)strtoul(pitem.subtext().c_str(), nullptr, 16)); // give 2 spaces worth of padding - subitem_width = ui().get_string_width("FF00FF00"); + subitem_width = get_string_width("FF00FF00"); - ui().draw_outlined_box(container(), effective_left + effective_width - subitem_width, line_y0, - effective_left + effective_width, line_y1, color); + ui().draw_outlined_box( + container(), + effective_left + effective_width - subitem_width, line_y0 + (UI_LINE_WIDTH * 2.0F), + effective_left + effective_width, line_y1 - (UI_LINE_WIDTH * 2.0F), + color); } else { + std::string_view subitem_text(pitem.subtext()); + // give 2 spaces worth of padding - item_width += 2.0f * gutter_width; + item_width += 2.0F * gutter_width(); // if the subitem doesn't fit here, display dots - if (ui().get_string_width(subitem_text) > effective_width - item_width) + if (get_string_width(subitem_text) > effective_width - item_width) { subitem_text = "..."; if (is_selected(itemnum)) @@ -759,40 +880,43 @@ void menu::draw(uint32_t flags) } // customize subitem text color - if (!core_stricmp(subitem_text, _("On"))) + if (!core_stricmp(pitem.subtext(), _("On"))) fgcolor2 = rgb_t(0x00,0xff,0x00); - if (!core_stricmp(subitem_text, _("Off"))) + if (!core_stricmp(pitem.subtext(), _("Off"))) fgcolor2 = rgb_t(0xff,0x00,0x00); - if (!core_stricmp(subitem_text, _("Auto"))) + if (!core_stricmp(pitem.subtext(), _("Auto"))) fgcolor2 = rgb_t(0xff,0xff,0x00); // draw the subitem right-justified - ui().draw_text_full(container(), subitem_text, effective_left + item_width, line_y0, effective_width - item_width, - ui::text_layout::RIGHT, ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, subitem_invert ? fgcolor3 : fgcolor2, bgcolor, &subitem_width, nullptr); + ui().draw_text_full( + container(), + subitem_text, + effective_left + item_width, line_y0, effective_width - item_width, + text_layout::text_justify::RIGHT, text_layout::word_wrapping::TRUNCATE, + mame_ui_manager::NORMAL, subitem_invert ? fgcolor3 : fgcolor2, bgcolor, + &subitem_width, nullptr, + line_height()); } // apply arrows - if (is_selected(itemnum) && (pitem.flags & FLAG_LEFT_ARROW)) - { - draw_arrow( - effective_left + effective_width - subitem_width - gutter_width, - line_y0 + 0.1f * line_height, - effective_left + effective_width - subitem_width - gutter_width + lr_arrow_width, - line_y0 + 0.9f * line_height, - fgcolor, - ROT90 ^ ORIENTATION_FLIP_X); - } - if (is_selected(itemnum) && (pitem.flags & FLAG_RIGHT_ARROW)) + if (is_selected(itemnum) && (pitem.flags() & (FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW))) { - draw_arrow( - effective_left + effective_width + gutter_width - lr_arrow_width, - line_y0 + 0.1f * line_height, - effective_left + effective_width + gutter_width, - line_y0 + 0.9f * line_height, - fgcolor, - ROT90); + m_adjust_top = line_y0 + (0.1F * line_height()); + m_adjust_bottom = line_y0 + (0.9F * line_height()); + if (pitem.flags() & FLAG_LEFT_ARROW) + { + m_decrease_left = effective_left + effective_width - subitem_width - gutter_width(); + float const r = m_decrease_left + lr_arrow_width(); + draw_arrow(m_decrease_left, m_adjust_top, r, m_adjust_bottom, fgcolor, ROT90 ^ ORIENTATION_FLIP_X); + } + if (pitem.flags() & FLAG_RIGHT_ARROW) + { + float const r = effective_left + effective_width + gutter_width(); + m_increase_left = r - lr_arrow_width(); + draw_arrow(m_increase_left, m_adjust_top, r, m_adjust_bottom, fgcolor, ROT90); + } } } } @@ -802,134 +926,63 @@ void menu::draw(uint32_t flags) if (selected_subitem_too_big) { menu_item const &pitem = selected_item(); - bool const subitem_invert(pitem.flags & FLAG_INVERT); + bool const subitem_invert(pitem.flags() & FLAG_INVERT); auto const linenum = m_selected - top_line; - float const line_y = visible_top + (float)linenum * line_height; - float target_width, target_height; + float const line_y = m_items_top + float(linenum) * line_height(); // compute the multi-line target width/height - ui().draw_text_full(container(), pitem.subtext.c_str(), 0, 0, visible_width * 0.75f, - ui::text_layout::RIGHT, ui::text_layout::WORD, mame_ui_manager::NONE, rgb_t::white(), rgb_t::black(), &target_width, &target_height); + auto const [target_width, target_height] = get_text_dimensions( + pitem.subtext(), + 0, 0, visible_width * 0.75F, + text_layout::text_justify::RIGHT, text_layout::word_wrapping::WORD); // determine the target location - float const target_x = visible_left + visible_width - target_width - ui().box_lr_border(); - float target_y = line_y + line_height + ui().box_tb_border(); - if (target_y + target_height + ui().box_tb_border() > visible_main_menu_height) - target_y = line_y - target_height - ui().box_tb_border(); + float const target_x = visible_left + visible_width - target_width - lr_border(); + float target_y = line_y + line_height() + tb_border(); + if (target_y + target_height + tb_border() > visible_main_menu_height) + target_y = line_y - target_height - tb_border(); // add a box around that - ui().draw_outlined_box(container(), target_x - ui().box_lr_border(), - target_y - ui().box_tb_border(), - target_x + target_width + ui().box_lr_border(), - target_y + target_height + ui().box_tb_border(), + ui().draw_outlined_box( + container(), + target_x - lr_border(), target_y - tb_border(), + target_x + target_width + lr_border(), target_y + target_height + tb_border(), subitem_invert ? ui().colors().selected_bg_color() : ui().colors().background_color()); - ui().draw_text_full(container(), pitem.subtext.c_str(), target_x, target_y, target_width, - ui::text_layout::RIGHT, ui::text_layout::WORD, mame_ui_manager::NORMAL, ui().colors().selected_color(), ui().colors().selected_bg_color(), nullptr, nullptr); + ui().draw_text_full( + container(), + pitem.subtext(), + target_x, target_y, target_width, + text_layout::text_justify::RIGHT, text_layout::word_wrapping::WORD, + mame_ui_manager::NORMAL, ui().colors().selected_color(), ui().colors().selected_bg_color(), + nullptr, nullptr); } // if there is something special to add, do it by calling the virtual method - custom_render(get_selection_ref(), m_customtop, m_custombottom, x1, y1, x2, y2); + custom_render(flags, get_selection_ref(), m_customtop, m_custombottom, x1, y1, x2, y2); } -void menu::custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) +void menu::recompute_metrics(uint32_t width, uint32_t height, float aspect) { -} + float const ui_line_height = ui().get_line_height(); -//------------------------------------------------- -// draw_text_box - draw a multiline -// word-wrapped text box with a menu item at the -// bottom -//------------------------------------------------- - -void menu::draw_text_box() -{ - const char *text = m_items[0].text.c_str(); - const char *backtext = m_items[1].text.c_str(); - float line_height = ui().get_line_height(); - float lr_arrow_width = 0.4f * line_height * machine().render().ui_aspect(); - float gutter_width = lr_arrow_width; - float target_width, target_height, prior_width; - float target_x, target_y; - - // compute the multi-line target width/height - ui().draw_text_full(container(), text, 0, 0, 1.0f - 2.0f * ui().box_lr_border() - 2.0f * gutter_width, - ui::text_layout::LEFT, ui::text_layout::WORD, mame_ui_manager::NONE, rgb_t::white(), rgb_t::black(), &target_width, &target_height); - target_height += 2.0f * line_height; - if (target_height > 1.0f - 2.0f * ui().box_tb_border()) - target_height = floorf((1.0f - 2.0f * ui().box_tb_border()) / line_height) * line_height; - - // maximum against "return to prior menu" text - prior_width = ui().get_string_width(backtext) + 2.0f * gutter_width; - target_width = std::max(target_width, prior_width); - - // determine the target location - target_x = 0.5f - 0.5f * target_width; - target_y = 0.5f - 0.5f * target_height; - - // make sure we stay on-screen - if (target_x < ui().box_lr_border() + gutter_width) - target_x = ui().box_lr_border() + gutter_width; - if (target_x + target_width + gutter_width + ui().box_lr_border() > 1.0f) - target_x = 1.0f - ui().box_lr_border() - gutter_width - target_width; - if (target_y < ui().box_tb_border()) - target_y = ui().box_tb_border(); - if (target_y + target_height + ui().box_tb_border() > 1.0f) - target_y = 1.0f - ui().box_tb_border() - target_height; - - // add a box around that - ui().draw_outlined_box(container(), target_x - ui().box_lr_border() - gutter_width, - target_y - ui().box_tb_border(), - target_x + target_width + gutter_width + ui().box_lr_border(), - target_y + target_height + ui().box_tb_border(), - (m_items[0].flags & FLAG_REDTEXT) ? UI_RED_COLOR : ui().colors().background_color()); - ui().draw_text_full(container(), text, target_x, target_y, target_width, - ui::text_layout::LEFT, ui::text_layout::WORD, mame_ui_manager::NORMAL, ui().colors().text_color(), ui().colors().text_bg_color(), nullptr, nullptr); - - // draw the "return to prior menu" text with a hilight behind it - highlight( - target_x + 0.5f * UI_LINE_WIDTH, - target_y + target_height - line_height, - target_x + target_width - 0.5f * UI_LINE_WIDTH, - target_y + target_height, - ui().colors().selected_bg_color()); - ui().draw_text_full(container(), backtext, target_x, target_y + target_height - line_height, target_width, - ui::text_layout::CENTER, ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, ui().colors().selected_color(), ui().colors().selected_bg_color(), nullptr, nullptr); - - // artificially set the hover to the last item so a double-click exits - m_hover = m_items.size() - 1; -} + // force whole pixels for line height, gutters and borders + m_line_height = std::floor(ui_line_height * float(height)) / float(height); + m_gutter_width = std::floor(0.5F * ui_line_height * aspect * float(width)) / float(width); + m_tb_border = std::floor(ui().box_tb_border() * float(height)) / float(height); + m_lr_border = std::floor(ui().box_lr_border() * aspect * float(width)) / float(width); + m_lr_arrow_width = 0.4F * m_line_height * aspect; + m_ud_arrow_width = m_line_height * aspect; -//------------------------------------------------- -// map_mouse - map mouse pointer location to menu -// coordinates -//------------------------------------------------- + // don't accept pointer input until the menu has been redrawn + m_items_drawn = false; + m_pointer_state = track_pointer::IDLE; -void menu::map_mouse() -{ - ignore_mouse(); - int32_t mouse_target_x, mouse_target_y; - render_target *const mouse_target = machine().ui_input().find_mouse(&mouse_target_x, &mouse_target_y, &m_mouse_button); - if (mouse_target) - { - if (mouse_target->map_point_container(mouse_target_x, mouse_target_y, container(), m_mouse_x, m_mouse_y)) - m_mouse_hit = true; - } } - -//------------------------------------------------- -// ignore_mouse - set members to ignore mouse -// input -//------------------------------------------------- - -void menu::ignore_mouse() +void menu::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { - m_mouse_hit = false; - m_mouse_button = false; - m_mouse_x = -1.0f; - m_mouse_y = -1.0f; } @@ -938,8 +991,9 @@ void menu::ignore_mouse() // input events for a menu //------------------------------------------------- -void menu::handle_events(uint32_t flags, event &ev) +bool menu::handle_events(uint32_t flags, event &ev) { + bool need_update = false; bool stop = false; ui_event local_menu_event; @@ -948,112 +1002,638 @@ void menu::handle_events(uint32_t flags, event &ev) { switch (local_menu_event.event_type) { - // if we are hovering over a valid item, select it with a single click - case ui_event::MOUSE_DOWN: - if (custom_mouse_down()) - return; + // 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; - if ((flags & PROCESS_ONLYCHAR) == 0) + // 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) { - if (m_hover >= 0 && m_hover < m_items.size()) - m_selected = m_hover; - else if (m_hover == HOVER_ARROW_UP) - { - if ((flags & FLAG_UI_DATS) != 0) - { - top_line -= m_visible_items - (last_item_visible() ? 1 : 0); - return; - } - m_selected -= m_visible_items; - if (m_selected < 0) - m_selected = 0; - top_line -= m_visible_items - (last_item_visible() ? 1 : 0); - } - else if (m_hover == HOVER_ARROW_DOWN) - { - if ((flags & FLAG_UI_DATS) != 0) - { - top_line += m_visible_lines - 2; - return; - } - m_selected += m_visible_lines - 2 + is_first_selected(); - if (m_selected > m_items.size() - 1) - m_selected = m_items.size() - 1; - top_line += m_visible_lines - 2; - } + ev.iptkey = key; + stop = true; } - break; + } + break; - // if we are hovering over a valid item, fake a UI_SELECT with a double-click - case ui_event::MOUSE_DOUBLE_CLICK: - if (!(flags & PROCESS_ONLYCHAR) && m_hover >= 0 && m_hover < m_items.size()) + // 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) { - m_selected = m_hover; - ev.iptkey = IPT_UI_SELECT; - if (is_last_selected()) - { - ev.iptkey = IPT_UI_CANCEL; - stack_pop(); - } + ev.iptkey = key; stop = true; } - break; + } + break; - // caught scroll event - case ui_event::MOUSE_WHEEL: - if (!(flags & PROCESS_ONLYCHAR)) + // caught scroll event + case ui_event::type::MOUSE_WHEEL: + if ((track_pointer::IDLE == m_pointer_state) || (track_pointer::IGNORED == m_pointer_state)) + { + // the value is scaled to 120 units per "click" + m_accumulated_wheel += local_menu_event.zdelta * local_menu_event.num_lines; + int const lines((m_accumulated_wheel + ((0 < local_menu_event.zdelta) ? 36 : -36)) / 120); + if (!lines) + break; + m_accumulated_wheel -= lines * 120; + + if (!custom_mouse_scroll(-lines) && !(flags & (PROCESS_ONLYCHAR | PROCESS_CUSTOM_NAV))) { - if (local_menu_event.zdelta > 0) + if (lines > 0) { - if ((flags & FLAG_UI_DATS) != 0) - { - top_line -= local_menu_event.num_lines; - return; - } if (is_first_selected()) + { select_last_item(); + } else { - m_selected -= local_menu_event.num_lines; + m_selected -= lines; validate_selection(-1); } top_line -= (m_selected <= top_line && top_line != 0); if (m_selected <= top_line && m_visible_items != m_visible_lines) - top_line -= local_menu_event.num_lines; + top_line -= lines; } else { - if ((flags & FLAG_UI_DATS)) - { - top_line += local_menu_event.num_lines; - return; - } if (is_last_selected()) + { select_first_item(); + } else { - m_selected += local_menu_event.num_lines; + m_selected -= lines; validate_selection(1); } top_line += (m_selected >= top_line + m_visible_items + (top_line != 0)); if (m_selected >= (top_line + m_visible_items + (top_line != 0))) - top_line += local_menu_event.num_lines; + top_line -= lines; } } - break; + } + break; - // translate CHAR events into specials - case ui_event::IME_CHAR: + // translate CHAR events into specials + case ui_event::type::IME_CHAR: + if ((track_pointer::IDLE == m_pointer_state) || (track_pointer::IGNORED == m_pointer_state)) + { ev.iptkey = IPT_SPECIAL; ev.unichar = local_menu_event.ch; stop = true; - break; + } + break; - // ignore everything else - default: - break; + // ignore everything else + default: + break; + } + } + + // deal with repeating scroll arrows + if ((track_pointer::TRACK_LINE == m_pointer_state) && ((!m_pointer_line && m_show_up_arrow) || ((m_pointer_line == (m_visible_lines - 1)) && m_show_down_arrow))) + { + float const linetop(m_items_top + (float(m_pointer_line) * line_height())); + float const linebottom(m_items_top + (float(m_pointer_line + 1) * line_height())); + if (pointer_in_rect(m_items_left, linetop, m_items_right, linebottom)) + { + if (std::chrono::steady_clock::now() >= m_pointer_repeat) + { + if (!m_pointer_line) + { + // scroll up + assert(0 < top_line); + --top_line; + if (!top_line) + m_pointer_state = track_pointer::COMPLETED; + } + else + { + // scroll down + assert(m_items.size() > (top_line + m_visible_lines)); + ++top_line; + if (m_items.size() == (top_line + m_visible_lines)) + m_pointer_state = track_pointer::COMPLETED; + } + force_visible_selection(); + need_update = true; + m_pointer_repeat += std::chrono::milliseconds(100); + } + } + } + + return need_update; +} + + +//------------------------------------------------- +// handle_pointer_update - handle a regular +// pointer update +//------------------------------------------------- + +std::pair<int, bool> menu::handle_pointer_update(uint32_t flags, ui_event const &uievt) +{ + // decide whether to make this our current pointer + render_target &target(machine().render().ui_target()); + auto const [ours, changed] = m_global_state.use_pointer(target, container(), uievt); + if (!ours) + { + return std::make_pair(IPT_INVALID, false); + } + else if (changed) + { + // if the active pointer changed, ignore if any buttons were already down + if (uievt.pointer_buttons != uievt.pointer_pressed) + { + m_pointer_state = track_pointer::IGNORED; + return std::make_pair(IPT_INVALID, false); + } + else + { + m_pointer_state = track_pointer::IDLE; + } + } + else if ((track_pointer::IGNORED == m_pointer_state) || (track_pointer::COMPLETED == m_pointer_state)) + { + // stop ignoring the pointer if all buttons were released + if (uievt.pointer_buttons == uievt.pointer_pressed) + m_pointer_state = track_pointer::IDLE; + else + return std::make_pair(IPT_INVALID, false); + } + + // give derived class a chance to handle it + if ((track_pointer::IDLE == m_pointer_state) || (track_pointer::CUSTOM == m_pointer_state)) + { + bool const wascustom(track_pointer::CUSTOM == m_pointer_state); + auto const [key, take, redraw] = custom_pointer_updated(changed, uievt); + if (take) + { + m_pointer_state = track_pointer::CUSTOM; + return std::make_pair(key, redraw); + } + else if (wascustom) + { + if (uievt.pointer_buttons) + { + m_pointer_state = track_pointer::COMPLETED; + return std::make_pair(key, redraw); + } + else + { + m_pointer_state = track_pointer::IDLE; + } + } + + if (IPT_INVALID != key) + return std::make_pair(key, redraw); + } + + // ignore altogether if menu hasn't been drawn or flags say so + if (!m_items_drawn || (flags & (PROCESS_CUSTOM_ONLY | PROCESS_ONLYCHAR))) + { + if (uievt.pointer_pressed) + { + if (track_pointer::IDLE == m_pointer_state) + m_pointer_state = track_pointer::IGNORED; + } + else if (!uievt.pointer_buttons) + { + if ((track_pointer::IGNORED == m_pointer_state) || (track_pointer::COMPLETED == m_pointer_state)) + m_pointer_state = track_pointer::IDLE; + } + return std::make_pair(IPT_INVALID, false); + } + + switch (m_pointer_state) + { + case track_pointer::IDLE: + // ignore anything other than left click for now + if ((uievt.pointer_pressed & 0x01) && !(uievt.pointer_buttons & ~u32(0x1))) + return handle_primary_down(flags, uievt); + else if (uievt.pointer_pressed) + m_pointer_state = track_pointer::IGNORED; + break; + + case track_pointer::IGNORED: + case track_pointer::COMPLETED: + case track_pointer::CUSTOM: + std::abort(); // won't get here - handled earlier + + case track_pointer::TRACK_LINE: + { + auto const result(update_line_click(uievt)); + + // treat anything else being pressed as cancelling the click sequence + if (uievt.pointer_buttons & ~u32(0x01)) + m_pointer_state = track_pointer::COMPLETED; + else if (!uievt.pointer_buttons) + m_pointer_state = track_pointer::IDLE; + + return result; + } + + case track_pointer::SCROLL: + { + bool const redraw(update_drag_scroll(uievt)); + + // treat anything else being pressed as cancelling the drag + if (uievt.pointer_buttons & ~u32(0x01)) + m_pointer_state = track_pointer::COMPLETED; + else if (!uievt.pointer_buttons) + m_pointer_state = track_pointer::IDLE; + + return std::make_pair(IPT_INVALID, redraw); + } + + case track_pointer::ADJUST: + { + auto const result(update_drag_adjust(uievt)); + + // treat anything else being pressed as cancelling the drag + if (uievt.pointer_buttons & ~u32(0x01)) + m_pointer_state = track_pointer::COMPLETED; + else if (!uievt.pointer_buttons) + m_pointer_state = track_pointer::IDLE; + + return result; + } + } + + return std::make_pair(IPT_INVALID, false); +} + + +//------------------------------------------------- +// handle_pointer_leave - handle a pointer +// leaving the window +//------------------------------------------------- + +std::pair<int, bool> menu::handle_pointer_leave(uint32_t flags, ui_event const &uievt) +{ + // ignore pointer input in windows other than the one that displays the UI + render_target &target(machine().render().ui_target()); + auto const [ours, changed] = m_global_state.use_pointer(target, container(), uievt); + assert(!changed); + if (!ours) + return std::make_pair(IPT_INVALID, false); + + int key(IPT_INVALID); + bool redraw(false); + switch (m_pointer_state) + { + case track_pointer::IDLE: + case track_pointer::CUSTOM: + std::tie(key, std::ignore, redraw) = custom_pointer_updated(changed, uievt); + break; + + case track_pointer::IGNORED: + case track_pointer::COMPLETED: + break; // nothing to do + + case track_pointer::TRACK_LINE: + std::tie(key, redraw) = update_line_click(uievt); + break; + + case track_pointer::SCROLL: + redraw = update_drag_scroll(uievt); + break; + + case track_pointer::ADJUST: + std::tie(key, redraw) = update_drag_adjust(uievt); + break; + } + + m_pointer_state = track_pointer::IDLE; + return std::make_pair(key, redraw); +} + + +//------------------------------------------------- +// handle_pointer_abort - handle a pointer +// leaving in an abnormal way +//------------------------------------------------- + +std::pair<int, bool> menu::handle_pointer_abort(uint32_t flags, ui_event const &uievt) +{ + // ignore pointer input in windows other than the one that displays the UI + render_target &target(machine().render().ui_target()); + auto const [ours, changed] = m_global_state.use_pointer(target, container(), uievt); + assert(!changed); + if (!ours) + return std::make_pair(IPT_INVALID, false); + + int key(IPT_INVALID); + bool redraw(false); + if (track_pointer::CUSTOM == m_pointer_state) + std::tie(key, std::ignore, redraw) = custom_pointer_updated(false, uievt); + else if (track_pointer::TRACK_LINE == m_pointer_state) + redraw = true; + m_pointer_state = track_pointer::IDLE; + return std::make_pair(key, redraw); +} + + +//------------------------------------------------- +// handle_primary_down - handle the primary +// action for a pointer device +//------------------------------------------------- + +std::pair<int, bool> menu::handle_primary_down(uint32_t flags, ui_event const &uievt) +{ + // we handle touch differently to mouse or pen + bool const is_touch(ui_event::pointer::TOUCH == uievt.pointer_type); + auto const [x, y] = pointer_location(); // FIXME: need starting location for multi-click actions + + // check increase/decrease arrows first + // FIXME: should repeat if appropriate + if (!is_touch && (y >= m_adjust_top) && (y < m_adjust_bottom)) + { + if ((x >= m_decrease_left) && (x < (m_decrease_left + lr_arrow_width()))) + { + m_pointer_state = track_pointer::COMPLETED; + return std::make_pair(IPT_UI_LEFT, false); + } + else if ((x >= m_increase_left) && (x < (m_increase_left + lr_arrow_width()))) + { + m_pointer_state = track_pointer::COMPLETED; + return std::make_pair(IPT_UI_RIGHT, false); + } + } + + // work out if we’re pointing at an item + if ((x < m_items_left) || (x >= m_items_right) || (y < m_items_top) || (y >= (m_items_top + (float(m_visible_lines) * line_height())))) + { + m_pointer_state = track_pointer::IGNORED; + return std::make_pair(IPT_INVALID, false); + } + auto const lineno(int((y - m_items_top) / line_height())); + assert(lineno >= 0); + assert(lineno < m_visible_lines); + + // map to an action + if (!lineno && m_show_up_arrow) + { + // scroll up + assert(0 < top_line); + --top_line; + force_visible_selection(); + if (top_line) + { + m_pointer_state = track_pointer::TRACK_LINE; + m_pointer_down = std::make_pair(x, y); + m_pointer_updated = m_pointer_down; + m_pointer_line = lineno; + m_pointer_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(300); + } + else + { + m_pointer_state = track_pointer::COMPLETED; + } + return std::make_pair(IPT_INVALID, true); + } + else if ((lineno == (m_visible_lines - 1)) && m_show_down_arrow) + { + // scroll down + assert(m_items.size() > (top_line + m_visible_lines)); + ++top_line; + force_visible_selection(); + if (m_items.size() > (top_line + m_visible_lines)) + { + m_pointer_state = track_pointer::TRACK_LINE; + m_pointer_down = std::make_pair(x, y); + m_pointer_updated = m_pointer_down; + m_pointer_line = lineno; + m_pointer_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(300); + } + else + { + m_pointer_state = track_pointer::COMPLETED; } + return std::make_pair(IPT_INVALID, true); } + else + { + m_pointer_state = track_pointer::TRACK_LINE; + m_pointer_down = std::make_pair(x, y); + m_pointer_updated = m_pointer_down; + m_pointer_line = lineno; + + int const itemno(lineno + top_line); + assert(itemno >= 0); + assert(itemno < m_items.size()); + return std::make_pair(IPT_INVALID, is_selectable(m_items[itemno])); + } + + // nothing to do + m_pointer_state = track_pointer::IGNORED; + return std::make_pair(IPT_INVALID, false); +} + + +//------------------------------------------------- +// update_line_click - track pointer after +// clicking a menu line +//------------------------------------------------- + +std::pair<int, bool> menu::update_line_click(ui_event const &uievt) +{ + assert(track_pointer::TRACK_LINE == m_pointer_state); + assert((uievt.pointer_buttons | uievt.pointer_released) & 0x01); + + // arrows should scroll while held + if ((!m_pointer_line && m_show_up_arrow) || ((m_pointer_line == (m_visible_lines - 1)) && m_show_down_arrow)) + { + // check for re-entry + bool redraw(false); + auto const [x, y] = pointer_location(); + float const linetop(m_items_top + (float(m_pointer_line) * line_height())); + float const linebottom(m_items_top + (float(m_pointer_line + 1) * line_height())); + bool const reentered(reentered_rect(m_pointer_updated.first, m_pointer_updated.second, x, y, m_items_left, linetop, m_items_right, linebottom)); + if (reentered) + { + auto const now(std::chrono::steady_clock::now()); + if (now >= m_pointer_repeat) + { + if (!m_pointer_line) + { + // scroll up + assert(0 < top_line); + --top_line; + if (!top_line) + m_pointer_state = track_pointer::COMPLETED; + } + else + { + // scroll down + assert(m_items.size() > (top_line + m_visible_lines)); + ++top_line; + if (m_items.size() == (top_line + m_visible_lines)) + m_pointer_state = track_pointer::COMPLETED; + } + force_visible_selection(); + redraw = true; + m_pointer_repeat = now + std::chrono::milliseconds(100); + } + } + + // keep the pointer location where we updated + m_pointer_updated = std::make_pair(x, y); + return std::make_pair(IPT_INVALID, redraw); + } + + // check for conversion of a tap to a finger drag + auto const drag_result(check_touch_drag(uievt)); + if (track_pointer::TRACK_LINE != m_pointer_state) + return drag_result; + + // only take action if the primary button was released + if (!(uievt.pointer_released & 0x01)) + return std::make_pair(IPT_INVALID, false); + + // nothing to do if the item isn't selectable + int const itemno = m_pointer_line + top_line; + assert(itemno >= 0); + assert(itemno < m_items.size()); + if (!is_selectable(m_items[itemno])) + return std::make_pair(IPT_INVALID, false); + + // treat multi-click actions as not moving, otherwise check that pointer is still over the line + if (0 >= uievt.pointer_clicks) + { + auto const [x, y] = pointer_location(); + if ((x < m_items_left) || (x >= m_items_right) || (int((y - m_items_top) / line_height()) != m_pointer_line)) + return std::make_pair(IPT_INVALID, true); + } + + // anything other than a double-click just selects the item + m_selected = itemno; + if (2 != uievt.pointer_clicks) + return std::make_pair(IPT_INVALID, true); + + // activate regular items by simulating UI Select + if (!is_last_selected() || !m_needs_prev_menu_item) + return std::make_pair(IPT_UI_SELECT, true); + + // handle the magic final item that dismisses the menu + stack_pop(); + if (is_special_main_menu()) + machine().schedule_exit(); + return std::make_pair(IPT_UI_BACK, true); +} + + +//------------------------------------------------- +// update_drag_scroll - update menu position in +// response to a touch drag +//------------------------------------------------- + +bool menu::update_drag_scroll(ui_event const &uievt) +{ + assert(track_pointer::SCROLL == m_pointer_state); + assert((uievt.pointer_buttons | uievt.pointer_released) & 0x01); + + // get target location + int const newtop(drag_scroll( + pointer_location().second, m_pointer_down.second, m_pointer_updated.second, -line_height(), + m_pointer_line, 0, int(m_items.size() - m_visible_lines))); + if (newtop == top_line) + return false; + + // scroll and move the selection if necessary to keep it in the visible range + top_line = newtop; + force_visible_selection(); + return true; +} + + +//------------------------------------------------- +// update_drag_adjust - adjust value on +// horizontal drag +//------------------------------------------------- + +std::pair<int, bool> menu::update_drag_adjust(ui_event const &uievt) +{ + assert(track_pointer::ADJUST == m_pointer_state); + assert((uievt.pointer_buttons | uievt.pointer_released) & 0x01); + + // this is ugly because adjustment is implemented by faking keystrokes - can't give a count/distance + + // set thresholds depending on the direction for hysteresis + int const target(drag_scroll( + pointer_location().first, m_pointer_updated.first, m_pointer_updated.first, line_height() * x_aspect(), + 0, std::numeric_limits<int>::min(), std::numeric_limits<int>::max())); + + // ensure the item under the pointer is selected and adjustable + if ((top_line + m_pointer_line) == m_selected) + { + if (0 < target) + { + if (m_items[m_selected].flags() & FLAG_RIGHT_ARROW) + return std::make_pair(IPT_UI_RIGHT, true); + } + else if (0 > target) + { + if (m_items[m_selected].flags() & FLAG_LEFT_ARROW) + return std::make_pair(IPT_UI_LEFT, true); + } + } + + // looks like it wasn't to be + return std::make_pair(IPT_INVALID, false); +} + + +//------------------------------------------------- +// check_touch_drag - check for conversion of a +// touch to a scroll or adjust slide +//------------------------------------------------- + +std::pair<int, bool> menu::check_touch_drag(ui_event const &uievt) +{ + // we handle touch differently to mouse or pen + if (ui_event::pointer::TOUCH != uievt.pointer_type) + return std::make_pair(IPT_INVALID, false); + + // check distances + auto const [x, y] = pointer_location(); + auto const [h, v] = check_drag_conversion(x, y, m_pointer_down.first, m_pointer_down.second, line_height()); + if (h) + { + // only the selected line can be adjusted + if ((top_line + m_pointer_line) == m_selected) + { + m_pointer_state = track_pointer::ADJUST; + return update_drag_adjust(uievt); + } + else + { + m_pointer_state = track_pointer::COMPLETED; + } + } + else if (v) + { + m_pointer_state = track_pointer::SCROLL; + m_pointer_line = top_line; + return std::make_pair(IPT_INVALID, update_drag_scroll(uievt)); + } + + // no update needed + return std::make_pair(IPT_INVALID, false); } @@ -1062,64 +1642,81 @@ void menu::handle_events(uint32_t flags, event &ev) // keys for a menu //------------------------------------------------- -void menu::handle_keys(uint32_t flags, int &iptkey) +bool menu::handle_keys(uint32_t flags, int &iptkey) { - bool ignorepause = stack_has_special_main_menu(); - int code; - - // bail if no items + // bail if no items (happens if event handling triggered an item reset) if (m_items.empty()) - return; + return false; + + bool const ignorepause = (flags & PROCESS_IGNOREPAUSE) || stack_has_special_main_menu(); // if we hit select, return true or pop the stack, depending on the item if (exclusive_input_pressed(iptkey, IPT_UI_SELECT, 0)) { - if (is_last_selected()) + if (is_last_selected() && m_needs_prev_menu_item) { - iptkey = IPT_UI_CANCEL; + iptkey = IPT_INVALID; stack_pop(); + if (is_special_main_menu()) + machine().schedule_exit(); } - return; + return false; + } + + // UI configure hides the menus + if (!(flags & PROCESS_NOKEYS) && exclusive_input_pressed(iptkey, IPT_UI_MENU, 0) && !m_global_state.stack_has_special_main_menu()) + { + if (is_one_shot()) + stack_pop(); + else + m_global_state.hide_menu(); + return true; } // bail out - if ((flags & PROCESS_ONLYCHAR)) - return; + if (flags & PROCESS_ONLYCHAR) + return false; - // hitting cancel also pops the stack - if (exclusive_input_pressed(iptkey, IPT_UI_CANCEL, 0)) + // hitting back also pops the stack + if (exclusive_input_pressed(iptkey, IPT_UI_BACK, 0)) { - if (!menu_has_search_active()) + if (!custom_ui_back()) + { + iptkey = IPT_INVALID; stack_pop(); - return; + if (is_special_main_menu()) + machine().schedule_exit(); + } + return false; } // validate the current selection validate_selection(1); // swallow left/right keys if they are not appropriate - bool ignoreleft = ((selected_item().flags & FLAG_LEFT_ARROW) == 0); - bool ignoreright = ((selected_item().flags & FLAG_RIGHT_ARROW) == 0); - - if ((m_items[0].flags & FLAG_UI_DATS)) - ignoreleft = ignoreright = false; + bool const ignoreleft = !(flags & PROCESS_LR_ALWAYS) && !(selected_item().flags() & FLAG_LEFT_ARROW); + bool const ignoreright = !(flags & PROCESS_LR_ALWAYS) && !(selected_item().flags() & FLAG_RIGHT_ARROW); - // accept left/right keys as-is with repeat + // accept left/right/prev/next keys as-is with repeat if appropriate if (!ignoreleft && exclusive_input_pressed(iptkey, IPT_UI_LEFT, (flags & PROCESS_LR_REPEAT) ? 6 : 0)) - return; + return false; if (!ignoreright && exclusive_input_pressed(iptkey, IPT_UI_RIGHT, (flags & PROCESS_LR_REPEAT) ? 6 : 0)) - return; + return false; + + // keep track of whether we changed anything + bool updated(false); // up backs up by one item if (exclusive_input_pressed(iptkey, IPT_UI_UP, 6)) { - if ((m_items[0].flags & FLAG_UI_DATS)) + if (flags & PROCESS_CUSTOM_NAV) { - top_line--; - return; + return updated; } - if (is_first_selected()) + else if (is_first_selected()) + { select_last_item(); + } else { --m_selected; @@ -1128,18 +1725,20 @@ void menu::handle_keys(uint32_t flags, int &iptkey) top_line -= (m_selected <= top_line && top_line != 0); if (m_selected <= top_line && m_visible_items != m_visible_lines) top_line--; + updated = true; } // down advances by one item if (exclusive_input_pressed(iptkey, IPT_UI_DOWN, 6)) { - if ((m_items[0].flags & FLAG_UI_DATS)) + if (flags & PROCESS_CUSTOM_NAV) { - top_line++; - return; + return updated; } - if (is_last_selected()) + else if (is_last_selected()) + { select_first_item(); + } else { ++m_selected; @@ -1148,36 +1747,53 @@ void menu::handle_keys(uint32_t flags, int &iptkey) top_line += (m_selected >= top_line + m_visible_items + (top_line != 0)); if (m_selected >= (top_line + m_visible_items + (top_line != 0))) top_line++; + updated = true; } // page up backs up by m_visible_items if (exclusive_input_pressed(iptkey, IPT_UI_PAGE_UP, 6)) { + if (flags & PROCESS_CUSTOM_NAV) + return updated; m_selected -= m_visible_items; top_line -= m_visible_items - (last_item_visible() ? 1 : 0); if (m_selected < 0) m_selected = 0; validate_selection(1); + updated = true; } // page down advances by m_visible_items if (exclusive_input_pressed(iptkey, IPT_UI_PAGE_DOWN, 6)) { + if (flags & PROCESS_CUSTOM_NAV) + return updated; m_selected += m_visible_lines - 2 + is_first_selected(); top_line += m_visible_lines - 2; if (m_selected > m_items.size() - 1) m_selected = m_items.size() - 1; validate_selection(-1); + updated = true; } // home goes to the start if (exclusive_input_pressed(iptkey, IPT_UI_HOME, 0)) + { + if (flags & PROCESS_CUSTOM_NAV) + return updated; select_first_item(); + updated = true; + } // end goes to the last if (exclusive_input_pressed(iptkey, IPT_UI_END, 0)) + { + if (flags & PROCESS_CUSTOM_NAV) + return updated; select_last_item(); + updated = true; + } // pause enables/disables pause if (!ignorepause && exclusive_input_pressed(iptkey, IPT_UI_PAUSE, 0)) @@ -1188,21 +1804,63 @@ void menu::handle_keys(uint32_t flags, int &iptkey) machine().pause(); } - // handle a toggle cheats request - if (machine().ui_input().pressed_repeat(IPT_UI_TOGGLE_CHEAT, 0)) - mame_machine_manager::instance()->cheat().set_enable(!mame_machine_manager::instance()->cheat().enabled()); - // see if any other UI keys are pressed if (iptkey == IPT_INVALID) { - for (code = IPT_UI_FIRST + 1; code < IPT_UI_LAST; code++) + for (int code = IPT_UI_FIRST + 1; code < IPT_UI_LAST; code++) { - if (code == IPT_UI_CONFIGURE || (code == IPT_UI_LEFT && ignoreleft) || (code == IPT_UI_RIGHT && ignoreright) || (code == IPT_UI_PAUSE && ignorepause)) - continue; + switch (code) + { + case IPT_UI_LEFT: + if (ignoreleft) + continue; + break; + case IPT_UI_RIGHT: + if (ignoreright) + continue; + break; + case IPT_UI_PAUSE: + if (ignorepause) + continue; + break; + } if (exclusive_input_pressed(iptkey, code, 0)) break; } } + return updated; +} + + +//------------------------------------------------- +// default handler implementations +//------------------------------------------------- + +bool menu::custom_ui_back() +{ + return false; +} + +std::tuple<int, bool, bool> menu::custom_pointer_updated(bool changed, ui_event const &uievt) +{ + return std::make_tuple(IPT_INVALID, false, false); +} + +bool menu::custom_mouse_scroll(int lines) +{ + return false; +} + +void menu::menu_activated() +{ +} + +void menu::menu_deactivated() +{ +} + +void menu::menu_dismissed() +{ } @@ -1250,16 +1908,182 @@ void menu::validate_selection(int scandir) } +//------------------------------------------------- +// activate_menu - handle becoming top of the +// menu stack +//------------------------------------------------- + +void menu::activate_menu() +{ + m_items_drawn = false; + m_pointer_state = track_pointer::IDLE; + m_accumulated_wheel = 0; + m_active = true; + menu_activated(); +} + + +//------------------------------------------------- +// check_metrics - recompute metrics if target +// geometry has changed +//------------------------------------------------- + +bool menu::check_metrics() +{ + render_manager &render(machine().render()); + render_target &target(render.ui_target()); + std::pair<uint32_t, uint32_t> const uisize(target.width(), target.height()); + float const aspect = render.ui_aspect(&container()); + if ((uisize == m_last_size) && (std::fabs(1.0F - (aspect / m_last_aspect)) < 1e-6F)) + return false; + + m_last_size = uisize; + m_last_aspect = aspect; + recompute_metrics(uisize.first, uisize.second, aspect); + return true; +} + + +//------------------------------------------------- +// do_rebuild - get the subclass to populate +// the menu items +//------------------------------------------------- + +bool menu::do_rebuild() +{ + if (!m_items.empty()) + return false; + + m_rebuilding = true; + try + { + // add an item to return - this is a really hacky way of doing this + if (m_needs_prev_menu_item) + item_append(_("Return to Previous Menu"), 0, nullptr); + + // let implementation add other items + populate(); + } + catch (...) + { + m_items.clear(); + m_rebuilding = false; + throw; + } + m_rebuilding = false; + return true; +} + + +//------------------------------------------------- +// force_visible_selection - if the selected item +// is not visible, move the selection it it's +// within the visible portion of the menu +//------------------------------------------------- + +void menu::force_visible_selection() +{ + int const first(top_line ? (top_line + 1) : 0); + int const last(top_line + m_visible_lines - ((m_items.size() > (top_line + m_visible_lines)) ? 1 : 0)); + if (first > m_selected) + { + m_selected = first; + while (!is_selectable(m_items[m_selected])) + ++m_selected; + assert(last > m_selected); + } + else if (last <= m_selected) + { + m_selected = last - 1; + while (!is_selectable(m_items[m_selected])) + --m_selected; + assert(first <= m_selected); + } +} + + /*************************************************************************** MENU STACK MANAGEMENT ***************************************************************************/ -void menu::do_handle() +bool menu::do_handle() { - if (m_items.size() < 2) - populate(m_customtop, m_custombottom); - handle(); + bool need_update = false; + + // let OSD do its thing + machine().osd().check_osd_inputs(); + + // recompute metrics if necessary + if (check_metrics()) + need_update = true; + + // get the implementation to rebuild the list of items if necessary + if (do_rebuild()) + need_update = true; + validate_selection(1); + + // reset the event + std::optional<event> result; + result.emplace(); + result->itemref = nullptr; + result->item = nullptr; + result->iptkey = IPT_INVALID; + + // process input + uint32_t flags(m_process_flags); + if (!(flags & (PROCESS_NOKEYS | PROCESS_NOINPUT))) + { + // read events + if (handle_events(flags, *result)) + need_update = true; + + switch (m_pointer_state) + { + case track_pointer::IDLE: + case track_pointer::IGNORED: + // handle keys if we don't already have an event and we aren't tracking a pointer action + if ((IPT_INVALID == result->iptkey) && handle_keys(flags, result->iptkey)) + need_update = true; + break; + default: + // ignore keys pressed while tracking a pointer action + for (int code = IPT_UI_FIRST + 1; IPT_UI_LAST > code; ++code) + machine().ui_input().pressed(code); + break; + } + } + + // deal with stack push/pop and rebuild + if (!is_active()) + return false; + if (do_rebuild()) + { + validate_selection(1); + need_update = true; + } + + // update the selected item in the event and let the implementation handle it + if ((result->iptkey != IPT_INVALID) && selection_valid()) + { + result->itemref = get_selection_ref(); + result->item = &m_items[m_selected]; + } + else + { + result.reset(); + } + need_update = handle(result ? &*result : nullptr) || need_update; + + // the implementation had another chance to push/pop or rebuild + if (!is_active()) + return false; + if (do_rebuild()) + { + validate_selection(1); + return true; + } + return need_update; } @@ -1272,26 +2096,10 @@ void menu::do_handle() // and calls the menu handler //------------------------------------------------- -uint32_t menu::ui_handler(render_container &container, mame_ui_manager &mui) +delegate<uint32_t (render_container &)> menu::get_ui_handler(mame_ui_manager &mui) { - global_state_ptr const state(get_global_state(mui.machine())); - - // if we have no menus stacked up, start with the main menu - if (!state->topmost_menu<menu>()) - state->stack_push(std::unique_ptr<menu>(global_alloc_clear<menu_main>(mui, container))); - - // update the menu state - if (state->topmost_menu<menu>()) - state->topmost_menu<menu>()->do_handle(); - - // clear up anything pending to be released - state->clear_free_list(); - - // if the menus are to be hidden, return a cancel here - if (mui.is_menu_active() && ((mui.machine().ui_input().pressed(IPT_UI_CONFIGURE) && !state->stack_has_special_main_menu()) || !state->topmost_menu<menu>())) - return UI_HANDLER_CANCEL; - - return 0; + global_state &state(get_global_state(mui)); + return delegate<uint32_t (render_container &)>(&global_state::ui_handler, &state); } /*************************************************************************** @@ -1299,12 +2107,22 @@ uint32_t menu::ui_handler(render_container &container, mame_ui_manager &mui) ***************************************************************************/ //------------------------------------------------- +// create_layout +//------------------------------------------------- + +text_layout menu::create_layout(float width, text_layout::text_justify justify, text_layout::word_wrapping wrap) +{ + return text_layout(*ui().get_font(), line_height() * x_aspect(), line_height(), width, justify, wrap); +} + + +//------------------------------------------------- // highlight //------------------------------------------------- void menu::highlight(float x0, float y0, float x1, float y1, rgb_t bgcolor) { - container().add_quad(x0, y0, x1, y1, bgcolor, m_global_state->hilight_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1) | PRIMFLAG_PACKABLE); + container().add_quad(x0, y0, x1, y1, bgcolor, m_global_state.hilight_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1) | PRIMFLAG_PACKABLE); } @@ -1314,7 +2132,7 @@ void menu::highlight(float x0, float y0, float x1, float y1, rgb_t bgcolor) void menu::draw_arrow(float x0, float y0, float x1, float y1, rgb_t fgcolor, uint32_t orientation) { - container().add_quad(x0, y0, x1, y1, fgcolor, m_global_state->arrow_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXORIENT(orientation) | PRIMFLAG_PACKABLE); + container().add_quad(x0, y0, x1, y1, fgcolor, m_global_state.arrow_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXORIENT(orientation) | PRIMFLAG_PACKABLE); } @@ -1323,10 +2141,10 @@ void menu::draw_arrow(float x0, float y0, float x1, float y1, rgb_t fgcolor, uin // or footer text //------------------------------------------------- -void menu::extra_text_draw_box(float origx1, float origx2, float origy, float yspan, const char *text, int direction) +void menu::extra_text_draw_box(float origx1, float origx2, float origy, float yspan, std::string_view text, int direction) { // get the size of the text - auto layout = ui().create_layout(container()); + auto layout = create_layout(); layout.add_text(text); // position this extra text @@ -1337,8 +2155,8 @@ void menu::extra_text_draw_box(float origx1, float origx2, float origy, float ys ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color()); // take off the borders - x1 += ui().box_lr_border(); - y1 += ui().box_tb_border(); + x1 += lr_border(); + y1 += tb_border(); // draw the text within it layout.emit(container(), x1, y1); @@ -1348,8 +2166,8 @@ void menu::extra_text_draw_box(float origx1, float origx2, float origy, float ys void menu::draw_background() { // draw background image if available - if (ui().options().use_background_image() && m_global_state->bgrnd_bitmap() && m_global_state->bgrnd_bitmap()->valid()) - container().add_quad(0.0f, 0.0f, 1.0f, 1.0f, rgb_t::white(), m_global_state->bgrnd_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + if (ui().options().use_background_image() && m_global_state.bgrnd_bitmap() && m_global_state.bgrnd_bitmap()->valid()) + container().add_quad(0.0F, 0.0F, 1.0F, 1.0F, rgb_t::white(), m_global_state.bgrnd_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } @@ -1361,14 +2179,14 @@ void menu::draw_background() void menu::extra_text_position(float origx1, float origx2, float origy, float yspan, text_layout &layout, int direction, float &x1, float &y1, float &x2, float &y2) { - float width = layout.actual_width() + (2 * ui().box_lr_border()); + float width = layout.actual_width() + (2 * lr_border()); float maxwidth = std::max(width, origx2 - origx1); // compute our bounds - x1 = 0.5f - 0.5f * maxwidth; + x1 = 0.5F - (0.5F * maxwidth); x2 = x1 + maxwidth; y1 = origy + (yspan * direction); - y2 = origy + (ui().box_tb_border() * direction); + y2 = origy + (tb_border() * direction); if (y1 > y2) std::swap(y1, y2); @@ -1380,14 +2198,11 @@ void menu::extra_text_position(float origx1, float origx2, float origy, float ys // and footer text //------------------------------------------------- -void menu::extra_text_render(float top, float bottom, float origx1, float origy1, float origx2, float origy2, const char *header, const char *footer) +void menu::extra_text_render(float top, float bottom, float origx1, float origy1, float origx2, float origy2, std::string_view header, std::string_view footer) { - header = (header && *header) ? header : nullptr; - footer = (footer && *footer) ? footer : nullptr; - - if (header != nullptr) + if (!header.empty()) extra_text_draw_box(origx1, origx2, origy1, top, header, -1); - if (footer != nullptr) + if (!footer.empty()) extra_text_draw_box(origx1, origx2, origy2, bottom, footer, +1); } diff --git a/src/frontend/mame/ui/menu.h b/src/frontend/mame/ui/menu.h index 8df66a92882..b59913df1a7 100644 --- a/src/frontend/mame/ui/menu.h +++ b/src/frontend/mame/ui/menu.h @@ -7,7 +7,6 @@ Internal MAME menus for the user interface. ***************************************************************************/ - #ifndef MAME_FRONTEND_UI_MENU_H #define MAME_FRONTEND_UI_MENU_H @@ -20,10 +19,27 @@ #include "language.h" #include "render.h" +#include "interface/uievents.h" + +#include <algorithm> +#include <chrono> +#include <cmath> #include <functional> #include <map> #include <memory> #include <mutex> +#include <optional> +#include <string_view> +#include <tuple> +#include <utility> +#include <vector> + + +/*************************************************************************** + FORWARD DECLARATIONS +***************************************************************************/ + +struct ui_event; namespace ui { @@ -36,80 +52,86 @@ class menu { public: // flags for menu items - enum : unsigned + enum : uint32_t { FLAG_LEFT_ARROW = 1U << 0, FLAG_RIGHT_ARROW = 1U << 1, FLAG_INVERT = 1U << 2, - FLAG_MULTILINE = 1U << 3, - FLAG_REDTEXT = 1U << 4, - FLAG_DISABLE = 1U << 5, - FLAG_UI_DATS = 1U << 6, - FLAG_UI_HEADING = 1U << 7, - FLAG_COLOR_BOX = 1U << 8 + FLAG_DISABLE = 1U << 4, + FLAG_UI_HEADING = 1U << 5, + FLAG_COLOR_BOX = 1U << 6 }; virtual ~menu(); + // setting menu heading + template <typename... T> + void set_heading(T &&... args) + { + if (!m_heading) + m_heading.emplace(std::forward<T>(args)...); + else + m_heading->assign(std::forward<T>(args)...); + } + // append a new item to the end of the menu - void item_append(const std::string &text, const std::string &subtext, uint32_t flags, void *ref, menu_item_type type = menu_item_type::UNKNOWN); - void item_append(std::string &&text, std::string &&subtext, uint32_t flags, void *ref, menu_item_type type = menu_item_type::UNKNOWN); - void item_append(menu_item item); - void item_append(menu_item_type type, uint32_t flags = 0); - void item_append_on_off(const std::string &text, bool state, uint32_t flags, void *ref, menu_item_type type = menu_item_type::UNKNOWN); + int item_append(const std::string &text, uint32_t flags, void *ref, menu_item_type type = menu_item_type::UNKNOWN) { return item_append(std::string(text), std::string(), flags, ref, type); } + int item_append(const std::string &text, const std::string &subtext, uint32_t flags, void *ref, menu_item_type type = menu_item_type::UNKNOWN) { return item_append(std::string(text), std::string(subtext), flags, ref, type); } + int item_append(std::string &&text, uint32_t flags, void *ref, menu_item_type type = menu_item_type::UNKNOWN) { return item_append(text, std::string(), flags, ref, type); } + int item_append(std::string &&text, std::string &&subtext, uint32_t flags, void *ref, menu_item_type type = menu_item_type::UNKNOWN); + int item_append(menu_item item) { return item_append(item.text(), item.subtext(), item.flags(), item.ref(), item.type()); } + int item_append(menu_item_type type, uint32_t flags = 0); + int item_append_on_off(const std::string &text, bool state, uint32_t flags, void *ref, menu_item_type type = menu_item_type::UNKNOWN); - // Global initialization - static void init(running_machine &machine, ui_options &mopt); + // set space required for drawing extra content + void set_custom_space(float top, float bottom); // reset the menus, clearing everything - static void stack_reset(running_machine &machine) { get_global_state(machine)->stack_reset(); } + static void stack_reset(mame_ui_manager &ui) { get_global_state(ui).stack_reset(); } // push a new menu onto the stack template <typename T, typename... Params> static void stack_push(Params &&... args) { - stack_push(std::unique_ptr<menu>(global_alloc_clear<T>(std::forward<Params>(args)...))); + stack_push(std::make_unique<T>(std::forward<Params>(args)...)); } template <typename T, typename... Params> static void stack_push_special_main(Params &&... args) { - std::unique_ptr<menu> ptr(global_alloc_clear<T>(std::forward<Params>(args)...)); + std::unique_ptr<menu> ptr(std::make_unique<T>(std::forward<Params>(args)...)); ptr->set_special_main_menu(true); stack_push(std::move(ptr)); } // pop a menu from the stack - static void stack_pop(running_machine &machine) { get_global_state(machine)->stack_pop(); } + static void stack_pop(mame_ui_manager &ui) { get_global_state(ui).stack_pop(); } // test if one of the menus in the stack requires hide disable - static bool stack_has_special_main_menu(running_machine &machine) { return get_global_state(machine)->stack_has_special_main_menu(); } + static bool stack_has_special_main_menu(mame_ui_manager &ui) { return get_global_state(ui).stack_has_special_main_menu(); } // master handler - static uint32_t ui_handler(render_container &container, mame_ui_manager &mui); + static delegate<uint32_t (render_container &)> get_ui_handler(mame_ui_manager &mui); // Used by sliders void validate_selection(int scandir); - void do_handle(); - -private: - virtual void draw(uint32_t flags); - void draw_text_box(); + bool do_handle(); protected: - using cleanup_callback = std::function<void(running_machine &)>; using bitmap_ptr = widgets_manager::bitmap_ptr; using texture_ptr = widgets_manager::texture_ptr; - // flags to pass to process + // flags to pass to set_process_flags enum { - PROCESS_NOKEYS = 1, - PROCESS_LR_REPEAT = 2, - PROCESS_CUSTOM_ONLY = 4, - PROCESS_ONLYCHAR = 8, - PROCESS_NOINPUT = 16, - PROCESS_NOIMAGE = 32 + PROCESS_NOKEYS = 1 << 0, + PROCESS_LR_ALWAYS = 1 << 1, + PROCESS_LR_REPEAT = 1 << 2, + PROCESS_CUSTOM_NAV = 1 << 3, + PROCESS_CUSTOM_ONLY = 1 << 4, + PROCESS_ONLYCHAR = 1 << 5, + PROCESS_NOINPUT = 1 << 6, + PROCESS_IGNOREPAUSE = 1 << 7 }; // options for reset @@ -123,11 +145,10 @@ protected: // menu-related events struct event { - void *itemref; // reference for the selected item - menu_item_type type; // item type (eventually will go away when itemref is proper ui_menu_item class rather than void*) - int iptkey; // one of the IPT_* values from inptport.h - char32_t unichar; // unicode character if iptkey == IPT_SPECIAL - render_bounds mouse; // mouse position if iptkey == IPT_CUSTOM + void *itemref; // reference for the selected item or nullptr + menu_item *item; // selected item or nullptr + int iptkey; // one of the IPT_* values from inpttype.h + char32_t unichar; // unicode character if iptkey == IPT_SPECIAL }; menu(mame_ui_manager &mui, render_container &container); @@ -136,33 +157,27 @@ protected: running_machine &machine() const { return m_ui.machine(); } render_container &container() const { return m_container; } - // allocate temporary memory from the menu's memory pool - void *m_pool_alloc(size_t size); + bool is_special_main_menu() const { return m_special_main_menu; } + bool is_one_shot() const { return m_one_shot; } + bool is_active() const { return m_active; } + void set_one_shot(bool oneshot) { m_one_shot = oneshot; } + void set_needs_prev_menu_item(bool needs) { m_needs_prev_menu_item = needs; } void reset(reset_options options); void reset_parent(reset_options options) { m_parent->reset(options); } - template <typename T> T *topmost_menu() const { return m_global_state->topmost_menu<T>(); } - template <typename T> static T *topmost_menu(running_machine &machine) { return get_global_state(machine)->topmost_menu<T>(); } - void stack_pop() { m_global_state->stack_pop(); } - void stack_reset() { m_global_state->stack_reset(); } - bool stack_has_special_main_menu() const { return m_global_state->stack_has_special_main_menu(); } - - void add_cleanup_callback(cleanup_callback &&callback) { m_global_state->add_cleanup_callback(std::move(callback)); } - - // repopulate the menu items - void repopulate(reset_options options); - - // process a menu, drawing it and returning any interesting events - const event *process(uint32_t flags, float x0 = 0.0f, float y0 = 0.0f); - void process_parent() { m_parent->process(PROCESS_NOINPUT); } + template <typename T> T *topmost_menu() const { return m_global_state.topmost_menu<T>(); } + template <typename T> static T *topmost_menu(mame_ui_manager &ui) { return get_global_state(ui).topmost_menu<T>(); } + void stack_pop() { m_global_state.stack_pop(); } + void stack_reset() { m_global_state.stack_reset(); } + bool stack_has_special_main_menu() const { return m_global_state.stack_has_special_main_menu(); } menu_item &item(int index) { return m_items[index]; } menu_item const &item(int index) const { return m_items[index]; } int item_count() const { return m_items.size(); } // retrieves the ref of the currently selected menu item or nullptr - void *get_selection_ref() const { return selection_valid() ? m_items[m_selected].ref : nullptr; } + void *get_selection_ref() const { return selection_valid() ? m_items[m_selected].ref() : nullptr; } menu_item &selected_item() { return m_items[m_selected]; } menu_item const &selected_item() const { return m_items[m_selected]; } @@ -178,11 +193,8 @@ protected: void select_first_item(); void select_last_item(); - int hover() const { return m_hover; } - void set_hover(int index) { m_hover = index; } - void clear_hover() { m_hover = m_items.size() + 1; } - // scroll position control + void set_top_line(int index) { top_line = (0 < index) ? (index - 1) : index; } void centre_selection() { top_line = m_selected - (m_visible_lines / 2); } // test if the given key is pressed and we haven't already reported a key @@ -192,15 +204,74 @@ protected: float get_customtop() const { return m_customtop; } float get_custombottom() const { return m_custombottom; } + std::pair<uint32_t, uint32_t> target_size() const { return m_last_size; } + float x_aspect() const { return m_last_aspect; } + float line_height() const { return m_line_height; } + float gutter_width() const { return m_gutter_width; } + float tb_border() const { return m_tb_border; } + float lr_border() const { return m_lr_border; } + float lr_arrow_width() const { return m_lr_arrow_width; } + float ud_arrow_width() const { return m_ud_arrow_width; } + + float get_string_width(std::string_view s) { return ui().get_string_width(s, line_height()); } + text_layout create_layout(float width = 1.0, text_layout::text_justify justify = text_layout::text_justify::LEFT, text_layout::word_wrapping wrap = text_layout::word_wrapping::WORD); + + void draw_text_normal( + std::string_view text, + float x, float y, float width, + text_layout::text_justify justify, text_layout::word_wrapping wrap, + rgb_t color) + { + ui().draw_text_full( + container(), + text, + x, y, width, justify, wrap, + mame_ui_manager::NORMAL, color, ui().colors().text_bg_color(), + nullptr, nullptr, + line_height()); + } + + float get_text_width( + std::string_view text, + float x, float y, float width, + text_layout::text_justify justify, text_layout::word_wrapping wrap) + { + float result; + ui().draw_text_full( + container(), + text, + x, y, width, justify, wrap, + mame_ui_manager::NONE, rgb_t::white(), rgb_t::black(), + &result, nullptr, + line_height()); + return result; + } + + std::pair<float, float> get_text_dimensions( + std::string_view text, + float x, float y, float width, + text_layout::text_justify justify, text_layout::word_wrapping wrap) + { + std::pair<float, float> result; + ui().draw_text_full( + container(), + text, + x, y, width, justify, wrap, + mame_ui_manager::NONE, rgb_t::white(), rgb_t::black(), + &result.first, &result.second, + line_height()); + return result; + } + // highlight void highlight(float x0, float y0, float x1, float y1, rgb_t bgcolor); - render_texture *hilight_main_texture() { return m_global_state->hilight_main_texture(); } + render_texture *hilight_main_texture() { return m_global_state.hilight_main_texture(); } // draw arrow void draw_arrow(float x0, float y0, float x1, float y1, rgb_t fgcolor, uint32_t orientation); // draw header and footer text - void extra_text_render(float top, float bottom, float origx1, float origy1, float origx2, float origy2, const char *header, const char *footer); + void extra_text_render(float top, float bottom, float origx1, float origy1, float origx2, float origy2, std::string_view header, std::string_view footer); void extra_text_position(float origx1, float origx2, float origy, float yspan, text_layout &layout, int direction, float &x1, float &y1, float &x2, float &y2); @@ -213,80 +284,90 @@ protected: rgb_t fgcolor, rgb_t bgcolor, float text_size) { // size up the text - float maxwidth(origx2 - origx1); + float const origwidth(origx2 - origx1 - (2.0f * lr_border())); + float maxwidth(origwidth); for (Iter it = begin; it != end; ++it) { - float width; - ui().draw_text_full( - container(), get_c_str(*it), - 0.0f, 0.0f, 1.0f, justify, wrap, - mame_ui_manager::NONE, rgb_t::black(), rgb_t::white(), - &width, nullptr, text_size); - width += 2.0f * ui().box_lr_border(); - maxwidth = (std::max)(maxwidth, width); + std::string_view const &line(*it); + if (!line.empty()) + { + text_layout layout(*ui().get_font(), text_size * x_aspect(), text_size, 1.0, justify, wrap); + layout.add_text(line, rgb_t::white(), rgb_t::black()); + maxwidth = (std::max)(layout.actual_width(), maxwidth); + } } - if (scale && ((origx2 - origx1) < maxwidth)) + if (scale && (origwidth < maxwidth)) { - text_size *= ((origx2 - origx1) / maxwidth); - maxwidth = origx2 - origx1; + text_size *= origwidth / maxwidth; + maxwidth = origwidth; } // draw containing box - float x1(0.5f * (1.0f - maxwidth)); - float x2(x1 + maxwidth); - ui().draw_outlined_box(container(), x1, y1, x2, y2, bgcolor); + float const boxleft(0.5f - (maxwidth * 0.5f) - lr_border()); + float boxright(0.5f + (maxwidth * 0.5f) + lr_border()); + ui().draw_outlined_box(container(), boxleft, y1, boxright, y2, bgcolor); // inset box and draw content - x1 += ui().box_lr_border(); - x2 -= ui().box_lr_border(); - y1 += ui().box_tb_border(); - y2 -= ui().box_tb_border(); + float const textleft(0.5f - (maxwidth * 0.5f)); + y1 += tb_border(); for (Iter it = begin; it != end; ++it) { ui().draw_text_full( - container(), get_c_str(*it), - x1, y1, x2 - x1, justify, wrap, + container(), std::string_view(*it), + textleft, y1, maxwidth, justify, wrap, mame_ui_manager::NORMAL, fgcolor, ui().colors().text_bg_color(), nullptr, nullptr, text_size); - y1 += ui().get_line_height(); + y1 += text_size; } // in case you want another box of similar width return maxwidth; } + template <typename Iter> + float draw_text_box( + Iter begin, Iter end, + float origx1, float origx2, float y1, float y2, + ui::text_layout::text_justify justify, ui::text_layout::word_wrapping wrap, bool scale, + rgb_t fgcolor, rgb_t bgcolor) + { + return draw_text_box(begin, end, origx1, origx2, y1, y2, justify, wrap, scale, fgcolor, bgcolor, line_height()); + } + void draw_background(); // draw additional menu content - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2); - - // map mouse to menu coordinates - void map_mouse(); - - // clear the mouse position - void ignore_mouse(); + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect); + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2); - bool is_mouse_hit() const { return m_mouse_hit; } // is mouse pointer inside menu's render container? - float get_mouse_x() const { return m_mouse_x; } // mouse x location in menu coordinates - float get_mouse_y() const { return m_mouse_y; } // mouse y location in menu coordinates + // access to pointer state + bool have_pointer() const noexcept { return m_global_state.have_pointer(); } + bool pointer_idle() const noexcept { return (track_pointer::IDLE == m_pointer_state) && have_pointer(); } + bool pointer_in_rect(float x0, float y0, float x1, float y1) const noexcept { return m_global_state.pointer_in_rect(x0, y0, x1, y1); } + std::pair<float, float> pointer_location() const noexcept { return m_global_state.pointer_location(); } + osd::ui_event_handler::pointer pointer_type() const noexcept { return m_global_state.pointer_type(); } - // mouse hit test - checks whether mouse_x is in [x0, x1) and mouse_y is in [y0, y1) - bool mouse_in_rect(float x0, float y0, float x1, float y1) const - { - return m_mouse_hit && (m_mouse_x >= x0) && (m_mouse_x < x1) && (m_mouse_y >= y0) && (m_mouse_y < y1); - } + // derived classes that override handle_events need to call these for pointer events + std::pair<int, bool> handle_pointer_update(uint32_t flags, ui_event const &uievt); + std::pair<int, bool> handle_pointer_leave(uint32_t flags, ui_event const &uievt); + std::pair<int, bool> handle_pointer_abort(uint32_t flags, ui_event const &uievt); // overridable event handling - virtual void handle_events(uint32_t flags, event &ev); - virtual void handle_keys(uint32_t flags, int &iptkey); - virtual bool custom_mouse_down() { return false; } - - // test if search is active - virtual bool menu_has_search_active() { return false; } + void set_process_flags(uint32_t flags) { m_process_flags = flags; } + virtual bool handle_events(uint32_t flags, event &ev); + virtual bool handle_keys(uint32_t flags, int &iptkey); + virtual bool custom_ui_back(); + virtual std::tuple<int, bool, bool> custom_pointer_updated(bool changed, ui_event const &uievt); + virtual bool custom_mouse_scroll(int lines); + + // event notifications + virtual void menu_activated(); + virtual void menu_deactivated(); + virtual void menu_dismissed(); static bool is_selectable(menu_item const &item) { - return ((item.flags & (menu::FLAG_MULTILINE | menu::FLAG_DISABLE)) == 0 && item.type != menu_item_type::SEPARATOR); + return (!(item.flags() & menu::FLAG_DISABLE) && (item.type() != menu_item_type::SEPARATOR)); } // get arrows status @@ -296,17 +377,55 @@ protected: return ((actual > min) ? FLAG_LEFT_ARROW : 0) | ((actual < max) ? FLAG_RIGHT_ARROW : 0); } + static bool reentered_rect(float x0, float y0, float x1, float y1, float l, float t, float r, float b) + { + return + ((x0 < l) || (x0 >= r) || (y0 < t) || (y0 >= b)) && + ((x1 >= l) && (x1 < r) && (y1 >= t) && (y1 < b)); + } + + std::pair<bool, bool> check_drag_conversion(float x, float y, float x_base, float y_base, float threshold) const + { + float const dx(std::abs((x - x_base) / x_aspect())); + float const dy(std::abs(y - y_base)); + if ((dx > dy) && (dx >= threshold)) + return std::make_pair(true, false); + else if ((dy >= dx) && (dy > threshold)) + return std::make_pair(false, true); + else + return std::make_pair(false, false); + } + + template <typename T> + static T drag_scroll(float location, float base, float &last, float unit, T start, T min, T max) + { + // set thresholds depending on the direction for hysteresis and clamp to valid range + T const target((location - (std::abs(unit) * ((location > last) ? 0.3F : -0.3F)) - base) / unit); + last = base + (float(target) * unit); + return std::clamp(start + target, min, max); + } + private: + // pointer tracking state + enum class track_pointer + { + IDLE, + IGNORED, + COMPLETED, + CUSTOM, + TRACK_LINE, + SCROLL, + ADJUST + }; + class global_state : public widgets_manager { public: - global_state(running_machine &machine, ui_options const &options); + global_state(mame_ui_manager &ui); global_state(global_state const &) = delete; global_state(global_state &&) = delete; ~global_state(); - void add_cleanup_callback(cleanup_callback &&callback); - bitmap_argb32 *bgrnd_bitmap() { return m_bgrnd_bitmap.get(); } render_texture *bgrnd_texture() { return m_bgrnd_texture.get(); } @@ -319,86 +438,178 @@ private: void clear_free_list(); bool stack_has_special_main_menu() const; + void hide_menu() { m_hide = true; } + + uint32_t ui_handler(render_container &container); + + bool have_pointer() const noexcept + { + return 0 <= m_current_pointer; + } + bool pointer_in_rect(float x0, float y0, float x1, float y1) const noexcept + { + return (m_pointer_x >= x0) && (m_pointer_x < x1) && (m_pointer_y >= y0) && (m_pointer_y < y1); + } + std::pair<float, float> pointer_location() const noexcept + { + return std::make_pair(m_pointer_x, m_pointer_y); + } + osd::ui_event_handler::pointer pointer_type() const noexcept + { + return m_pointer_type; + } + + std::pair<bool, bool> use_pointer(render_target &target, render_container &container, ui_event const &event); + + protected: + mame_ui_manager &m_ui; + private: - using cleanup_callback_vector = std::vector<cleanup_callback>; + bitmap_ptr m_bgrnd_bitmap; + texture_ptr m_bgrnd_texture; - running_machine &m_machine; - cleanup_callback_vector m_cleanup_callbacks; + std::unique_ptr<menu> m_stack; + std::unique_ptr<menu> m_free; - bitmap_ptr m_bgrnd_bitmap; - texture_ptr m_bgrnd_texture; + bool m_hide; - std::unique_ptr<menu> m_stack; - std::unique_ptr<menu> m_free; + s32 m_current_pointer; // current active pointer ID or -1 if none + osd::ui_event_handler::pointer m_pointer_type; // current pointer type + u32 m_pointer_buttons; // depressed buttons for current pointer + float m_pointer_x; + float m_pointer_y; + bool m_pointer_hit; }; - using global_state_ptr = std::shared_ptr<global_state>; - using global_state_map = std::map<running_machine *, global_state_ptr>; - struct pool + // this is to satisfy the std::any requirement that objects be copyable + class global_state_wrapper : public global_state { - pool *next; // chain to next one - uint8_t *top; // top of the pool - uint8_t *end; // end of the pool + public: + global_state_wrapper(mame_ui_manager &ui) : global_state(ui) { } + global_state_wrapper(global_state_wrapper const &that) : global_state(that.m_ui) { } }; + // process a menu, returning any interesting events + std::pair<int, bool> handle_primary_down(uint32_t flags, ui_event const &uievt); + std::pair<int, bool> update_line_click(ui_event const &uievt); + bool update_drag_scroll(ui_event const &uievt); + std::pair<int, bool> update_drag_adjust(ui_event const &uievt); + std::pair<int, bool> check_touch_drag(ui_event const &uievt); + + // drawing the menu + void do_draw_menu(); + virtual void draw(uint32_t flags); + // request the specific handling of the game selection main menu - bool is_special_main_menu() const; void set_special_main_menu(bool disable); - // To be reimplemented in the menu subclass - virtual void populate(float &customtop, float &custombottom) = 0; - - // To be reimplemented in the menu subclass - virtual void handle() = 0; + // to be implemented in derived classes + virtual void populate() = 0; - // push a new menu onto the stack - static void stack_push(std::unique_ptr<menu> &&menu) { get_global_state(menu->machine())->stack_push(std::move(menu)); } + // to be implemented in derived classes + virtual bool handle(event const *ev) = 0; - void extra_text_draw_box(float origx1, float origx2, float origy, float yspan, const char *text, int direction); + void extra_text_draw_box(float origx1, float origx2, float origy, float yspan, std::string_view text, int direction); + void activate_menu(); + bool check_metrics(); + bool do_rebuild(); bool first_item_visible() const { return top_line <= 0; } bool last_item_visible() const { return (top_line + m_visible_lines) >= m_items.size(); } + void force_visible_selection(); - static void exit(running_machine &machine); - static global_state_ptr get_global_state(running_machine &machine); - - static char const *get_c_str(std::string const &str) { return str.c_str(); } - static char const *get_c_str(char const *str) { return str; } + // push a new menu onto the stack + static void stack_push(std::unique_ptr<menu> &&menu) { menu->m_global_state.stack_push(std::move(menu)); } - int m_selected; // which item is selected - int m_hover; // which item is being hovered over - std::vector<menu_item> m_items; // array of items + static global_state &get_global_state(mame_ui_manager &ui); -protected: // TODO: remove need to expose these +protected: // TODO: remove need to expose these - only used here and in selmenu.cpp int top_line; // main box top line - int skip_main_items; int m_visible_lines; // main box visible lines int m_visible_items; // number of visible items private: - global_state_ptr const m_global_state; - bool m_special_main_menu; - mame_ui_manager &m_ui; // UI we are attached to - render_container &m_container; // render_container we render to - std::unique_ptr<menu> m_parent; // pointer to parent menu - event m_event; // the UI event that occurred - pool *m_pool; // list of memory pools - - float m_customtop; // amount of extra height to add at the top - float m_custombottom; // amount of extra height to add at the bottom - - int m_resetpos; // reset position - void *m_resetref; // reset reference - - bool m_mouse_hit; - bool m_mouse_button; - float m_mouse_x; - float m_mouse_y; - - static std::mutex s_global_state_guard; - static global_state_map s_global_states; + global_state &m_global_state; // reference to global state for session + mame_ui_manager &m_ui; // UI we are attached to + render_container &m_container; // render_container we render to + std::unique_ptr<menu> m_parent; // pointer to parent menu in the stack + + std::optional<std::string> m_heading; // menu heading + std::vector<menu_item> m_items; // array of items + bool m_rebuilding; // ensure items are only added during rebuild + + std::pair<uint32_t, uint32_t> m_last_size; // pixel size of UI container when metrics were computed + float m_last_aspect; // aspect ratio of UI container when metrics were computed + float m_line_height; + float m_gutter_width; + float m_tb_border; + float m_lr_border; + float m_lr_arrow_width; + float m_ud_arrow_width; + + float m_items_left; // left of the area where the items are drawn + float m_items_right; // right of the area where the items are drawn + float m_items_top; // top of the area where the items are drawn + float m_adjust_top; // top of the "increase"/"decrease" arrows + float m_adjust_bottom; // bottom of the "increase"/"decrease" arrows + float m_decrease_left; // left of the "decrease" arrow + float m_increase_left; // left of the "increase" arrow + bool m_show_up_arrow; // are we showing the "scroll up" arrow? + bool m_show_down_arrow; // are we showing the "scroll down" arrow? + bool m_items_drawn; // have we drawn the items at least once? + + track_pointer m_pointer_state; // tracking state for currently active pointer + std::pair<float, float> m_pointer_down; // start location of tracked pointer action + std::pair<float, float> m_pointer_updated; // location where pointer tracking was updated + int m_pointer_line; // the line we're tracking pointer motion in + std::chrono::steady_clock::time_point m_pointer_repeat; + int m_accumulated_wheel; // accumulated scroll wheel/gesture movement + + uint32_t m_process_flags; // event processing options + int m_selected; // which item is selected + bool m_special_main_menu; // true if no real emulation running under the menu + bool m_one_shot; // true for menus outside the normal stack + bool m_needs_prev_menu_item; // true to automatically create item to dismiss menu + bool m_active; // whether the menu is currently visible and topmost + + float m_customtop; // amount of extra height to add at the top + float m_custombottom; // amount of extra height to add at the bottom + + int m_resetpos; // item index to select after repopulating + void *m_resetref; // item reference value to select after repopulating +}; + + +template <typename Base = menu> +class autopause_menu : public Base +{ +protected: + using Base::Base; + + virtual void menu_activated() override + { + m_was_paused = this->machine().paused(); + if (m_was_paused) + m_unpaused = false; + else if (!m_unpaused) + this->machine().pause(); + Base::menu_activated(); + } + + virtual void menu_deactivated() override + { + m_unpaused = !this->machine().paused(); + if (!m_was_paused && !m_unpaused) + this->machine().resume(); + Base::menu_deactivated(); + } + +private: + bool m_was_paused = false; + bool m_unpaused = false; }; + } // namespace ui #endif // MAME_FRONTEND_UI_MENU_H diff --git a/src/frontend/mame/ui/menuitem.h b/src/frontend/mame/ui/menuitem.h index 0ba4629c93b..d92e4153ee4 100644 --- a/src/frontend/mame/ui/menuitem.h +++ b/src/frontend/mame/ui/menuitem.h @@ -9,13 +9,19 @@ ***************************************************************************/ -#pragma once - #ifndef MAME_FRONTEND_UI_MENUITEM_H #define MAME_FRONTEND_UI_MENUITEM_H +#pragma once + + +#include <cstdint> +#include <string> +#include <utility> + namespace ui { + // special menu item for separators #define MENU_SEPARATOR_ITEM "---" @@ -30,17 +36,32 @@ enum class menu_item_type class menu_item { public: - menu_item() = default; menu_item(menu_item const &) = default; menu_item(menu_item &&) = default; menu_item &operator=(menu_item const &) = default; menu_item &operator=(menu_item &&) = default; - std::string text; - std::string subtext; - uint32_t flags; - void *ref; - menu_item_type type; // item type (eventually will go away when itemref is proper ui_menu_item class rather than void*) + menu_item(menu_item_type t = menu_item_type::UNKNOWN, void *r = nullptr, uint32_t f = 0) : m_ref(r), m_flags(f), m_type(t) + { } + + std::string const &text() const noexcept { return m_text; } + std::string const &subtext() const noexcept { return m_subtext; } + void *ref() const noexcept { return m_ref; } + uint32_t flags() const noexcept { return m_flags; } + unsigned generation() const noexcept { return m_generation; } + menu_item_type type() const noexcept { return m_type; } + + template <typename... T> void set_text(T &&... args) { m_text.assign(std::forward<T>(args)...); ++m_generation; } + template <typename... T> void set_subtext(T &&... args) { m_subtext.assign(std::forward<T>(args)...); ++m_generation; } + void set_flags(uint32_t f) noexcept { m_flags = f; ++m_generation; } + +private: + std::string m_text; + std::string m_subtext; + void *m_ref; + uint32_t m_flags; + unsigned m_generation = 0; + menu_item_type m_type; }; } // namespace ui diff --git a/src/frontend/mame/ui/midiinout.cpp b/src/frontend/mame/ui/midiinout.cpp new file mode 100644 index 00000000000..558d3f0fb0f --- /dev/null +++ b/src/frontend/mame/ui/midiinout.cpp @@ -0,0 +1,103 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert +/********************************************************************* + + ui/midiinout.cpp + + Midi channel selection + +*********************************************************************/ + +#include "emu.h" +#include "ui/midiinout.h" + +#include "ui/ui.h" + +#include "osdepend.h" + +namespace ui { + +menu_midi_inout::menu_midi_inout(mame_ui_manager &mui, render_container &container, bool is_input, std::string *channel) + : menu(mui, container) + , m_channel(channel) + , m_is_input(is_input) +{ + set_heading(m_is_input ? _("MIDI input channel") : _("MIDI output channel")); +} + +menu_midi_inout::~menu_midi_inout() +{ +} + +bool menu_midi_inout::handle(event const *ev) +{ + if(!ev) + return false; + + if(ev->iptkey == IPT_UI_SELECT) { + *m_channel = m_port_names[uintptr_t(ev->itemref)]; + stack_pop(); + return true; + } + + return false; +} + + +//------------------------------------------------- +// menu_midi_inout_populate - populate the midi_inout +// menu +//------------------------------------------------- + +void menu_midi_inout::populate() +{ + auto ports = machine().osd().list_midi_ports(); + for(auto &p : ports) + if((m_is_input && p.input) || (!m_is_input && p.output)) { + item_append(p.name, "", 0, (void *)(m_port_names.size())); + m_port_names.push_back(p.name); + } +} + + +//------------------------------------------------- +// recompute_metrics - recompute metrics +//------------------------------------------------- + +void menu_midi_inout::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + menu::recompute_metrics(width, height, aspect); +} + + +//------------------------------------------------- +// menu_midi_inout_custom_render - perform our special +// rendering +//------------------------------------------------- + +void menu_midi_inout::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x1, float y1, float x2, float y2) +{ +} + + +//------------------------------------------------- +// menu_activated - handle menu gaining focus +//------------------------------------------------- + +void menu_midi_inout::menu_activated() +{ + // scripts or the other form of the menu could have changed something in the mean time + reset(reset_options::REMEMBER_POSITION); +} + + +//------------------------------------------------- +// menu_deactivated - handle menu losing focus +//------------------------------------------------- + +void menu_midi_inout::menu_deactivated() +{ +} + +} // namespace ui + diff --git a/src/frontend/mame/ui/midiinout.h b/src/frontend/mame/ui/midiinout.h new file mode 100644 index 00000000000..1cc7f360d35 --- /dev/null +++ b/src/frontend/mame/ui/midiinout.h @@ -0,0 +1,44 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert +/*************************************************************************** + + ui/midiinout.h + + Midi channel selection + +***************************************************************************/ + +#ifndef MAME_FRONTEND_UI_MIDIINOUT_H +#define MAME_FRONTEND_UI_MIDIINOUT_H + +#pragma once + +#include "ui/menu.h" + + +namespace ui { + +class menu_midi_inout : public menu +{ +public: + menu_midi_inout(mame_ui_manager &mui, render_container &container, bool is_input, std::string *channel); + virtual ~menu_midi_inout() override; + +protected: + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void menu_activated() override; + virtual void menu_deactivated() override; + +private: + std::vector<std::string> m_port_names; + std::string *m_channel; + bool m_is_input; + + virtual void populate() override; + virtual bool handle(event const *ev) override; +}; + +} // namespace ui + +#endif // MAME_FRONTEND_UI_MIDIINOUT_H diff --git a/src/frontend/mame/ui/miscmenu.cpp b/src/frontend/mame/ui/miscmenu.cpp index 397eb5801ff..377c34ed2d8 100644 --- a/src/frontend/mame/ui/miscmenu.cpp +++ b/src/frontend/mame/ui/miscmenu.cpp @@ -9,94 +9,78 @@ *********************************************************************/ #include "emu.h" +#include "ui/miscmenu.h" + +#include "ui/inifile.h" +#include "ui/selector.h" +#include "ui/submenu.h" +#include "ui/ui.h" +#include "ui/utils.h" + +#include "infoxml.h" #include "mame.h" -#include "osdnet.h" + #include "mameopts.h" #include "pluginopts.h" +#include "dinetwork.h" #include "drivenum.h" -#include "natkeyboard.h" +#include "fileio.h" #include "romload.h" - #include "uiinput.h" -#include "ui/ui.h" -#include "ui/menu.h" -#include "ui/miscmenu.h" -#include "../info.h" -#include "ui/inifile.h" -#include "ui/submenu.h" +#include "osdepend.h" + +#include "path.h" + +#include <algorithm> +#include <cstring> #include <fstream> +#include <iterator> +#include <locale> + namespace ui { + /*************************************************************************** MENU HANDLERS ***************************************************************************/ /*------------------------------------------------- - menu_keyboard_mode - menu that --------------------------------------------------*/ - -menu_keyboard_mode::menu_keyboard_mode(mame_ui_manager &mui, render_container &container) : menu(mui, container) -{ -} - -void menu_keyboard_mode::populate(float &customtop, float &custombottom) -{ - bool natural = machine().ioport().natkeyboard().in_use(); - item_append(_("Keyboard Mode:"), natural ? _("Natural") : _("Emulated"), natural ? FLAG_LEFT_ARROW : FLAG_RIGHT_ARROW, nullptr); -} - -menu_keyboard_mode::~menu_keyboard_mode() -{ -} - -void menu_keyboard_mode::handle() -{ - bool natural = machine().ioport().natkeyboard().in_use(); - - /* process the menu */ - const event *menu_event = process(0); - - if (menu_event != nullptr) - { - if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) - { - machine().ioport().natkeyboard().set_in_use(!natural); - reset(reset_options::REMEMBER_REF); - } - } -} - - -/*------------------------------------------------- menu_bios_selection - populates the main bios selection menu -------------------------------------------------*/ menu_bios_selection::menu_bios_selection(mame_ui_manager &mui, render_container &container) : menu(mui, container) { + set_heading(_("BIOS Selection")); } -void menu_bios_selection::populate(float &customtop, float &custombottom) +void menu_bios_selection::populate() { - /* cycle through all devices for this system */ - for (device_t &device : device_iterator(machine().root_device())) + // cycle through all devices for this system + for (device_t &device : device_enumerator(machine().root_device())) { - tiny_rom_entry const *rom(device.rom_region()); - if (rom && !ROMENTRY_ISEND(rom)) + device_t const *const parent(device.owner()); + device_slot_interface const *const slot(dynamic_cast<device_slot_interface const *>(parent)); + if (!parent || (slot && (slot->get_card_device() == &device))) { - const char *val = "default"; - for ( ; !ROMENTRY_ISEND(rom); rom++) + tiny_rom_entry const *rom(device.rom_region()); + if (rom && !ROMENTRY_ISEND(rom)) { - if (ROMENTRY_ISSYSTEM_BIOS(rom) && ROM_GETBIOSFLAGS(rom) == device.system_bios()) - val = rom->hashdata; + char const *val = nullptr; + for ( ; !ROMENTRY_ISEND(rom) && !val; rom++) + { + if (ROMENTRY_ISSYSTEM_BIOS(rom) && ROM_GETBIOSFLAGS(rom) == device.system_bios()) + val = rom->hashdata; + } + if (val) + item_append(!parent ? _("System") : (device.tag() + 1), val, FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW, (void *)&device); } - item_append(!device.owner() ? "driver" : (device.tag() + 1), val, FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW, (void *)&device); } } item_append(menu_item_type::SEPARATOR); - item_append(_("Reset"), "", 0, (void *)1); + item_append(_("Reset"), 0, (void *)1); } menu_bios_selection::~menu_bios_selection() @@ -107,40 +91,71 @@ menu_bios_selection::~menu_bios_selection() menu_bios_selection - menu that -------------------------------------------------*/ -void menu_bios_selection::handle() +bool menu_bios_selection::handle(event const *ev) { - /* process the menu */ - const event *menu_event = process(0); + if (!ev || !ev->itemref) + return false; + + if ((uintptr_t)ev->itemref == 1 && ev->iptkey == IPT_UI_SELECT) + { + machine().schedule_hard_reset(); + return false; + } + + device_t *const dev = (device_t *)ev->itemref; + int bios_val = 0; - if (menu_event != nullptr && menu_event->itemref != nullptr) + switch (ev->iptkey) { - if ((uintptr_t)menu_event->itemref == 1 && menu_event->iptkey == IPT_UI_SELECT) - machine().schedule_hard_reset(); - else if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) + // reset to default + case IPT_UI_CLEAR: + bios_val = dev->default_bios(); + break; + + // previous/next BIOS setting + case IPT_UI_SELECT: + case IPT_UI_LEFT: + case IPT_UI_RIGHT: { - device_t *dev = (device_t *)menu_event->itemref; int const cnt = ([bioses = romload::entries(dev->rom_region()).get_system_bioses()] () { return std::distance(bioses.begin(), bioses.end()); })(); - int val = dev->system_bios() + ((menu_event->iptkey == IPT_UI_LEFT) ? -1 : +1); - if (val < 1) - val = cnt; - if (val > cnt) - val = 1; - dev->set_system_bios(val); - if (strcmp(dev->tag(),":")==0) { - machine().options().set_value("bios", val-1, OPTION_PRIORITY_CMDLINE); - } else { - const char *slot_option_name = dev->owner()->tag() + 1; - machine().options().slot_option(slot_option_name).set_bios(string_format("%d", val - 1)); - } - reset(reset_options::REMEMBER_REF); + bios_val = dev->system_bios() + ((ev->iptkey == IPT_UI_LEFT) ? -1 : +1); + + // wrap + if (bios_val < 1) + bios_val = cnt; + if (bios_val > cnt) + bios_val = 1; + } + break; + + default: + break; + } + + if (bios_val > 0) + { + dev->set_system_bios(bios_val); + if (!strcmp(dev->tag(), ":")) + { + machine().options().set_value("bios", bios_val - 1, OPTION_PRIORITY_CMDLINE); } + else + { + const char *slot_option_name = dev->owner()->tag() + 1; + machine().options().slot_option(slot_option_name).set_bios(string_format("%d", bios_val - 1)); + } + reset(reset_options::REMEMBER_REF); } + + // triggers an item reset for any change + return false; } menu_network_devices::menu_network_devices(mame_ui_manager &mui, render_container &container) : menu(mui, container) { + set_heading(_("Network Devices")); } menu_network_devices::~menu_network_devices() @@ -152,44 +167,73 @@ menu_network_devices::~menu_network_devices() network device menu -------------------------------------------------*/ -void menu_network_devices::populate(float &customtop, float &custombottom) +void menu_network_devices::populate() { /* cycle through all devices for this system */ - for (device_network_interface &network : network_interface_iterator(machine().root_device())) + for (device_network_interface &network : network_interface_enumerator(machine().root_device())) { int curr = network.get_interface(); - const char *title = nullptr; - for(auto &entry : get_netdev_list()) + std::string_view title; + for (auto &entry : machine().osd().list_network_devices()) { - if(entry->id==curr) { - title = entry->description; + if (entry.id == curr) + { + title = entry.description; break; } } - item_append(network.device().tag(), (title) ? title : "------", FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW, (void *)&network); + item_append(network.device().tag(), std::string(!title.empty() ? title : "------"), FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW, (void *)&network); } + + item_append(menu_item_type::SEPARATOR); } /*------------------------------------------------- menu_network_devices - menu that -------------------------------------------------*/ -void menu_network_devices::handle() +bool menu_network_devices::handle(event const *ev) { - /* process the menu */ - const event *menu_event = process(0); - - if (menu_event != nullptr && menu_event->itemref != nullptr) + if (!ev || !ev->itemref) + { + return false; + } + else if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT) { - if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) { - device_network_interface *network = (device_network_interface *)menu_event->itemref; - int curr = network->get_interface(); - if (menu_event->iptkey == IPT_UI_LEFT) curr--; else curr++; - if (curr==-2) curr = netdev_count() - 1; - network->set_interface(curr); - reset(reset_options::REMEMBER_REF); + device_network_interface *const network = (device_network_interface *)ev->itemref; + auto const interfaces = machine().osd().list_network_devices(); + int curr = network->get_interface(); + auto const found = std::find_if( + std::begin(interfaces), + std::end(interfaces), + [curr] (osd::network_device_info const &info) { return info.id == curr; }); + auto index = std::distance(interfaces.begin(), found); + if (ev->iptkey == IPT_UI_LEFT) + --index; + else if (std::end(interfaces) == found) + index = 0; + else if (std::size(interfaces) <= ++index) + index = -1; + network->set_interface((0 <= index) ? interfaces[index].id : -1); + + curr = network->get_interface(); + std::string_view title; + for (auto &entry : interfaces) + { + if (entry.id == curr) + { + title = entry.description; + break; + } } + + ev->item->set_subtext(!title.empty() ? title : "------"); + return true; + } + else + { + return false; } } @@ -199,165 +243,185 @@ void menu_network_devices::handle() information menu -------------------------------------------------*/ -void menu_bookkeeping::handle() +menu_bookkeeping::menu_bookkeeping(mame_ui_manager &mui, render_container &container) : menu_textbox(mui, container) { - attotime curtime; - - /* if the time has rolled over another second, regenerate */ - curtime = machine().time(); - if (prevtime.seconds() != curtime.seconds()) - { - prevtime = curtime; - repopulate(reset_options::SELECT_FIRST); - } - - /* process the menu */ - process(0); + set_process_flags(PROCESS_CUSTOM_NAV); } - -/*------------------------------------------------- - menu_bookkeeping - handle the bookkeeping - information menu --------------------------------------------------*/ -menu_bookkeeping::menu_bookkeeping(mame_ui_manager &mui, render_container &container) : menu(mui, container) +menu_bookkeeping::~menu_bookkeeping() { } -menu_bookkeeping::~menu_bookkeeping() +void menu_bookkeeping::menu_activated() { + // stuff can change while the menu is hidden + reset_layout(); } -void menu_bookkeeping::populate(float &customtop, float &custombottom) +void menu_bookkeeping::populate_text(std::optional<text_layout> &layout, float &width, int &lines) { - int tickets = machine().bookkeeping().get_dispensed_tickets(); - std::ostringstream tempstring; - int ctrnum; + if (!layout || (layout->width() != width)) + { + rgb_t const color = ui().colors().text_color(); + layout.emplace(create_layout(width)); - /* show total time first */ - if (prevtime.seconds() >= (60 * 60)) - util::stream_format(tempstring, _("Uptime: %1$d:%2$02d:%3$02d\n\n"), prevtime.seconds() / (60 * 60), (prevtime.seconds() / 60) % 60, prevtime.seconds() % 60); - else - util::stream_format(tempstring, _("Uptime: %1$d:%2$02d\n\n"), (prevtime.seconds() / 60) % 60, prevtime.seconds() % 60); + // show total time first + prevtime = machine().time(); + if (prevtime.seconds() >= (60 * 60)) + layout->add_text(util::string_format(_("Uptime: %1$d:%2$02d:%3$02d\n\n"), prevtime.seconds() / (60 * 60), (prevtime.seconds() / 60) % 60, prevtime.seconds() % 60), color); + else + layout->add_text(util::string_format(_("Uptime: %1$d:%2$02d\n\n"), (prevtime.seconds() / 60) % 60, prevtime.seconds() % 60), color); - /* show tickets at the top */ - if (tickets > 0) - util::stream_format(tempstring, _("Tickets dispensed: %1$d\n\n"), tickets); + // show tickets at the top + int const tickets = machine().bookkeeping().get_dispensed_tickets(); + if (tickets > 0) + layout->add_text(util::string_format(_("Tickets dispensed: %1$d\n\n"), tickets), color); - /* loop over coin counters */ - for (ctrnum = 0; ctrnum < bookkeeping_manager::COIN_COUNTERS; ctrnum++) - { - int count = machine().bookkeeping().coin_counter_get_count(ctrnum); - - /* display the coin counter number */ - /* display how many coins */ - /* display whether or not we are locked out */ - util::stream_format(tempstring, - (count == 0) ? _("Coin %1$c: NA%3$s\n") : _("Coin %1$c: %2$d%3$s\n"), - ctrnum + 'A', - count, - machine().bookkeeping().coin_lockout_get_state(ctrnum) ? _(" (locked)") : ""); + // loop over coin counters + for (int ctrnum = 0; ctrnum < bookkeeping_manager::COIN_COUNTERS; ctrnum++) + { + int const count = machine().bookkeeping().coin_counter_get_count(ctrnum); + bool const locked = machine().bookkeeping().coin_lockout_get_state(ctrnum); + + // display the coin counter number + // display how many coins + // display whether or not we are locked out + layout->add_text( + util::string_format( + (count == 0) ? _("Coin %1$c: NA%3$s\n") : _("Coin %1$c: %2$d%3$s\n"), + ctrnum + 'A', + count, + locked ? _(" (locked)") : ""), + color); + } + + lines = layout->lines(); } + width = layout->actual_width(); +} + +void menu_bookkeeping::populate() +{ +} - /* append the single item */ - item_append(tempstring.str(), "", FLAG_MULTILINE, nullptr); +bool menu_bookkeeping::handle(event const *ev) +{ + // if the time has rolled over another second, regenerate + // TODO: what about other bookkeeping events happening with the menu open? + attotime const curtime = machine().time(); + if (curtime.seconds() != prevtime.seconds()) + { + reset_layout(); + return true; + } + else + { + return menu_textbox::handle(ev); + } } + /*------------------------------------------------- menu_crosshair - handle the crosshair settings menu -------------------------------------------------*/ -void menu_crosshair::handle() +bool menu_crosshair::handle(event const *ev) { - /* process the menu */ - const event *menu_event = process(PROCESS_LR_REPEAT); - - /* handle events */ - if (menu_event != nullptr && menu_event->itemref != nullptr) + // handle events + if (ev && ev->itemref) { - crosshair_item_data *data = (crosshair_item_data *)menu_event->itemref; - bool changed = false; - //int set_def = false; - int newval = data->cur; + crosshair_item_data &data(*reinterpret_cast<crosshair_item_data *>(ev->itemref)); + bool changed(false); + int newval(data.cur); - /* retreive the user settings */ - render_crosshair &crosshair = machine().crosshair().get_crosshair(data->player); - - switch (menu_event->iptkey) + switch (ev->iptkey) { - /* if selected, reset to default value */ - case IPT_UI_SELECT: - newval = data->defvalue; - //set_def = true; - break; + // if selected, reset to default value + case IPT_UI_SELECT: + newval = data.defvalue; + break; - /* left decrements */ - case IPT_UI_LEFT: - newval -= machine().input().code_pressed(KEYCODE_LSHIFT) ? 10 : 1; - break; + // left decrements + case IPT_UI_LEFT: + newval -= machine().input().code_pressed(KEYCODE_LSHIFT) ? 10 : 1; + break; - /* right increments */ - case IPT_UI_RIGHT: - newval += machine().input().code_pressed(KEYCODE_LSHIFT) ? 10 : 1; - break; + // right increments + case IPT_UI_RIGHT: + newval += machine().input().code_pressed(KEYCODE_LSHIFT) ? 10 : 1; + break; } - /* clamp to range */ - if (newval < data->min) - newval = data->min; - if (newval > data->max) - newval = data->max; + // clamp to range + if (newval < data.min) + newval = data.min; + if (newval > data.max) + newval = data.max; - /* if things changed, update */ - if (newval != data->cur) + // if things changed, update + if (newval != data.cur) { - switch (data->type) + switch (data.type) { - /* visibility state */ - case CROSSHAIR_ITEM_VIS: - crosshair.set_mode(newval); - // set visibility as specified by mode - auto mode starts with visibility off - crosshair.set_visible(newval == CROSSHAIR_VISIBILITY_ON); - changed = true; - break; - - /* auto time */ - case CROSSHAIR_ITEM_AUTO_TIME: - machine().crosshair().set_auto_time(newval); - changed = true; - break; + // visibility state + case CROSSHAIR_ITEM_VIS: + data.crosshair->set_mode(newval); + // set visibility as specified by mode - auto mode starts with visibility off + data.crosshair->set_visible(newval == CROSSHAIR_VISIBILITY_ON); + changed = true; + break; + + // auto time + case CROSSHAIR_ITEM_AUTO_TIME: + machine().crosshair().set_auto_time(newval); + changed = true; + break; } } - /* crosshair graphic name */ - if (data->type == CROSSHAIR_ITEM_PIC) + // crosshair graphic name + if (data.type == CROSSHAIR_ITEM_PIC) { - switch (menu_event->iptkey) + switch (ev->iptkey) { - case IPT_UI_SELECT: - crosshair.set_default_bitmap(); - changed = true; - break; - - case IPT_UI_LEFT: - crosshair.set_bitmap_name(data->last_name); - changed = true; - break; - - case IPT_UI_RIGHT: - crosshair.set_bitmap_name(data->next_name); - changed = true; - break; + case IPT_UI_SELECT: + { + std::vector<std::string> sel; + sel.reserve(m_pics.size() + 1); + sel.push_back(_("menu-crosshair", "[built-in]")); + std::copy(m_pics.begin(), m_pics.end(), std::back_inserter(sel)); + menu::stack_push<menu_selector>( + ui(), container(), std::string(ev->item->text()), std::move(sel), data.cur, + [this, &data] (int selection) + { + if (!selection) + data.crosshair->set_default_bitmap(); + else + data.crosshair->set_bitmap_name(m_pics[selection - 1].c_str()); + reset(reset_options::REMEMBER_REF); + }); + } + break; + + case IPT_UI_LEFT: + data.crosshair->set_bitmap_name(data.last_name.c_str()); + changed = true; + break; + + case IPT_UI_RIGHT: + data.crosshair->set_bitmap_name(data.next_name.c_str()); + changed = true; + break; } } if (changed) - { - /* rebuild the menu */ - reset(reset_options::REMEMBER_POSITION); - } + reset(reset_options::REMEMBER_REF); // rebuild the menu } + + // triggers an item reset for any changes + return false; } @@ -368,178 +432,197 @@ void menu_crosshair::handle() menu_crosshair::menu_crosshair(mame_ui_manager &mui, render_container &container) : menu(mui, container) { + set_process_flags(PROCESS_LR_REPEAT); + set_heading(_("menu-crosshair", "Crosshair Options")); } -void menu_crosshair::populate(float &customtop, float &custombottom) +void menu_crosshair::populate() { - crosshair_item_data *data; - char temp_text[16]; - int player; - uint8_t use_auto = false; - uint32_t flags = 0; - - /* loop over player and add the manual items */ - for (player = 0; player < MAX_PLAYERS; player++) + if (m_data.empty()) { - /* get the user settings */ - render_crosshair &crosshair = machine().crosshair().get_crosshair(player); - - /* add menu items for usable crosshairs */ - if (crosshair.is_used()) + // loop over player and add the manual items + for (int player = 0; player < MAX_PLAYERS; player++) { - /* Make sure to keep these matched to the CROSSHAIR_VISIBILITY_xxx types */ - static const char *const vis_text[] = { "Off", "On", "Auto" }; - - /* track if we need the auto time menu */ - if (crosshair.mode() == CROSSHAIR_VISIBILITY_AUTO) use_auto = true; - - /* CROSSHAIR_ITEM_VIS - allocate a data item and fill it */ - data = (crosshair_item_data *)m_pool_alloc(sizeof(*data)); - data->type = CROSSHAIR_ITEM_VIS; - data->player = player; - data->min = CROSSHAIR_VISIBILITY_OFF; - data->max = CROSSHAIR_VISIBILITY_AUTO; - data->defvalue = CROSSHAIR_VISIBILITY_DEFAULT; - data->cur = crosshair.mode(); - - /* put on arrows */ - if (data->cur > data->min) - flags |= FLAG_LEFT_ARROW; - if (data->cur < data->max) - flags |= FLAG_RIGHT_ARROW; - - /* add CROSSHAIR_ITEM_VIS menu */ - sprintf(temp_text, "P%d Visibility", player + 1); - item_append(temp_text, vis_text[crosshair.mode()], flags, data); - - /* CROSSHAIR_ITEM_PIC - allocate a data item and fill it */ - data = (crosshair_item_data *)m_pool_alloc(sizeof(*data)); - data->type = CROSSHAIR_ITEM_PIC; - data->player = player; - data->last_name[0] = 0; - /* other data item not used by this menu */ - - /* search for crosshair graphics */ - - /* open a path to the crosshairs */ - file_enumerator path(machine().options().crosshair_path()); - const osd::directory::entry *dir; - /* reset search flags */ - bool using_default = false; - bool finished = false; - bool found = false; - - /* if we are using the default, then we just need to find the first in the list */ - if (*crosshair.bitmap_name() == '\0') - using_default = true; - - /* look for the current name, then remember the name before */ - /* and find the next name */ - while (((dir = path.next()) != nullptr) && !finished) + // get the user settings + render_crosshair &crosshair(machine().crosshair().get_crosshair(player)); + + // add menu items for usable crosshairs + if (crosshair.is_used()) { - int length = strlen(dir->name); + // CROSSHAIR_ITEM_VIS - allocate a data item and fill it + crosshair_item_data &visdata(m_data.emplace_back()); + visdata.crosshair = &crosshair; + visdata.type = CROSSHAIR_ITEM_VIS; + visdata.player = player; + visdata.min = CROSSHAIR_VISIBILITY_OFF; + visdata.max = CROSSHAIR_VISIBILITY_AUTO; + visdata.defvalue = CROSSHAIR_VISIBILITY_DEFAULT; + + // CROSSHAIR_ITEM_PIC - allocate a data item and fill it + crosshair_item_data &picdata(m_data.emplace_back()); + picdata.crosshair = &crosshair; + picdata.type = CROSSHAIR_ITEM_PIC; + picdata.player = player; + // other data item not used by this menu + } + } + + // CROSSHAIR_ITEM_AUTO_TIME - allocate a data item and fill it + crosshair_item_data &timedata(m_data.emplace_back()); + timedata.type = CROSSHAIR_ITEM_AUTO_TIME; + timedata.min = CROSSHAIR_VISIBILITY_AUTOTIME_MIN; + timedata.max = CROSSHAIR_VISIBILITY_AUTOTIME_MAX; + timedata.defvalue = CROSSHAIR_VISIBILITY_AUTOTIME_DEFAULT; + } - /* look for files ending in .png with a name not larger then 9 chars*/ - if ((length > 4) && (length <= CROSSHAIR_PIC_NAME_LENGTH + 4) && core_filename_ends_with(dir->name, ".png")) + if (m_pics.empty()) + { + // open a path to the crosshairs + file_enumerator path(machine().options().crosshair_path()); + for (osd::directory::entry const *dir = path.next(); dir; dir = path.next()) + { + // look for files ending in .png + size_t const length(std::strlen(dir->name)); + if ((length > 4) && core_filename_ends_with(dir->name, ".png")) + m_pics.emplace_back(dir->name, length - 4); + } + std::locale const lcl; + std::collate<wchar_t> const &coll = std::use_facet<std::collate<wchar_t> >(lcl); + std::stable_sort( + m_pics.begin(), + m_pics.end(), + [&coll] (auto const &x, auto const &y) { - /* remove .png from length */ - length -= 4; + std::wstring const wx = wstring_from_utf8(x); + std::wstring const wy = wstring_from_utf8(y); + return 0 > coll.compare(wx.data(), wx.data() + wx.size(), wy.data(), wy.data() + wy.size()); + } + ); + } + + // Make sure to keep these matched to the CROSSHAIR_VISIBILITY_xxx types + static char const *const vis_text[] = { + N_p("menu-crosshair", "Never"), + N_p("menu-crosshair", "Always"), + N_p("menu-crosshair", "When moved") }; + + bool use_auto = false; + for (crosshair_item_data &data : m_data) + { + switch (data.type) + { + case CROSSHAIR_ITEM_VIS: + { + // track if we need the auto time menu + if (data.crosshair->mode() == CROSSHAIR_VISIBILITY_AUTO) + use_auto = true; + + data.cur = data.crosshair->mode(); + + // put on arrows + uint32_t flags(0U); + if (data.cur > data.min) + flags |= FLAG_LEFT_ARROW; + if (data.cur < data.max) + flags |= FLAG_RIGHT_ARROW; + + // add CROSSHAIR_ITEM_VIS menu */ + item_append( + util::string_format(_("menu-crosshair", "P%1$d Visibility"), data.player + 1), + _("menu-crosshair", vis_text[data.crosshair->mode()]), + flags, + &data); + } + break; + case CROSSHAIR_ITEM_PIC: + // search for crosshair graphics + { + // reset search flags + bool const using_default(*data.crosshair->bitmap_name() == '\0'); + bool finished(false); + bool found(false); + data.cur = using_default ? 0U : 1U; + data.last_name.clear(); + data.next_name.clear(); + + // look for the current name, then remember the name before and find the next name + for (auto it = m_pics.begin(); it != m_pics.end() && !finished; ++it) + { + // if we are using the default, then we just need to find the first in the list if (found || using_default) { - /* get the next name */ - strncpy(data->next_name, dir->name, length); - data->next_name[length] = 0; + // get the next name + data.next_name = *it; finished = true; } - else if (!strncmp(dir->name, crosshair.bitmap_name(), length)) + else if (data.crosshair->bitmap_name() == *it) { - /* we found the current name */ - /* so loop once more to find the next name */ + // we found the current name so loop once more to find the next name found = true; } else - /* remember last name */ - /* we will do it here in case files get added to the directory */ { - strncpy(data->last_name, dir->name, length); - data->last_name[length] = 0; + // remember last name - we will do it here in case files get added to the directory + ++data.cur; + data.last_name = *it; } } + + // if name not found then next item is DEFAULT + if (!found && !using_default) + { + data.cur = 0U; + data.next_name.clear(); + finished = true; + } + + // set up the selection flags + uint32_t flags(0U); + if (finished) + flags |= FLAG_RIGHT_ARROW; + if (found) + flags |= FLAG_LEFT_ARROW; + + // add CROSSHAIR_ITEM_PIC menu + item_append( + util::string_format(_("menu-crosshair", "P%1$d Crosshair"), data.player + 1), + using_default ? _("menu-crosshair", "[built-in]") : data.crosshair->bitmap_name(), + flags, + &data); + item_append(menu_item_type::SEPARATOR); } - /* if name not found then next item is DEFAULT */ - if (!found && !using_default) + break; + + case CROSSHAIR_ITEM_AUTO_TIME: + if (use_auto) { - data->next_name[0] = 0; - finished = true; + data.cur = machine().crosshair().auto_time(); + + // put on arrows in visible menu + uint32_t flags(0U); + if (data.cur > data.min) + flags |= FLAG_LEFT_ARROW; + if (data.cur < data.max) + flags |= FLAG_RIGHT_ARROW; + + // add CROSSHAIR_ITEM_AUTO_TIME menu + item_append( + _("menu-crosshair", "Auto-Hide Delay"), + util::string_format(_("menu-crosshair", "%1$d s"), data.cur), + flags, + &data); + item_append(menu_item_type::SEPARATOR); } - /* setup the selection flags */ - flags = 0; - if (finished) - flags |= FLAG_RIGHT_ARROW; - if (found) - flags |= FLAG_LEFT_ARROW; - - /* add CROSSHAIR_ITEM_PIC menu */ - sprintf(temp_text, "P%d Crosshair", player + 1); - item_append(temp_text, using_default ? "DEFAULT" : crosshair.bitmap_name(), flags, data); + break; } } - if (use_auto) - { - /* CROSSHAIR_ITEM_AUTO_TIME - allocate a data item and fill it */ - data = (crosshair_item_data *)m_pool_alloc(sizeof(*data)); - data->type = CROSSHAIR_ITEM_AUTO_TIME; - data->min = CROSSHAIR_VISIBILITY_AUTOTIME_MIN; - data->max = CROSSHAIR_VISIBILITY_AUTOTIME_MAX; - data->defvalue = CROSSHAIR_VISIBILITY_AUTOTIME_DEFAULT; - data->cur = machine().crosshair().auto_time(); - - /* put on arrows in visible menu */ - if (data->cur > data->min) - flags |= FLAG_LEFT_ARROW; - if (data->cur < data->max) - flags |= FLAG_RIGHT_ARROW; - - /* add CROSSHAIR_ITEM_AUTO_TIME menu */ - sprintf(temp_text, "%d", machine().crosshair().auto_time()); - item_append(_("Visible Delay"), temp_text, flags, data); - } -// else -// /* leave a blank filler line when not in auto time so size does not rescale */ -// item_append("", "", nullptr, nullptr); } menu_crosshair::~menu_crosshair() { } -/*------------------------------------------------- - menu_quit_game - handle the "menu" for - quitting the game --------------------------------------------------*/ - -menu_quit_game::menu_quit_game(mame_ui_manager &mui, render_container &container) : menu(mui, container) -{ -} - -menu_quit_game::~menu_quit_game() -{ -} - -void menu_quit_game::populate(float &customtop, float &custombottom) -{ -} - -void menu_quit_game::handle() -{ - /* request a reset */ - machine().schedule_exit(); - - /* reset the menu stack */ - stack_reset(); -} - //------------------------------------------------- // ctor / dtor //------------------------------------------------- @@ -547,6 +630,7 @@ void menu_quit_game::handle() menu_export::menu_export(mame_ui_manager &mui, render_container &container, std::vector<const game_driver *> &&drvlist) : menu(mui, container), m_list(std::move(drvlist)) { + set_heading(_("Export Displayed List to File")); } menu_export::~menu_export() @@ -554,29 +638,27 @@ menu_export::~menu_export() } //------------------------------------------------- -// handlethe options menu +// handle the export menu //------------------------------------------------- -void menu_export::handle() +bool menu_export::handle(event const *ev) { // process the menu - process_parent(); - const event *menu_event = process(PROCESS_NOIMAGE); - if (menu_event != nullptr && menu_event->itemref != nullptr) + if (ev && ev->itemref) { - switch (uintptr_t(menu_event->itemref)) + switch (uintptr_t(ev->itemref)) { case 1: case 3: - if (menu_event->iptkey == IPT_UI_SELECT) + if (ev->iptkey == IPT_UI_SELECT) { std::string filename("exported"); emu_file infile(ui().options().ui_path(), OPEN_FLAG_READ); - if (infile.open(filename.c_str(), ".xml") == osd_file::error::NONE) + if (!infile.open(filename + ".xml")) for (int seq = 0; ; ++seq) { - std::string seqtext = string_format("%s_%04d", filename, seq); - if (infile.open(seqtext.c_str(), ".xml") != osd_file::error::NONE) + const std::string seqtext = string_format("%s_%04d", filename, seq); + if (infile.open(seqtext + ".xml")) { filename = seqtext; break; @@ -585,9 +667,9 @@ void menu_export::handle() // attempt to open the output file emu_file file(ui().options().ui_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); - if (file.open(filename.c_str(), ".xml") == osd_file::error::NONE) + if (!file.open(filename + ".xml")) { - std::string fullpath(file.fullpath()); + const std::string fullpath(file.fullpath()); file.close(); std::ofstream pfile(fullpath); @@ -598,30 +680,30 @@ void menu_export::handle() auto iter = std::find_if( driver_list.begin(), driver_list.end(), - [shortname](const game_driver *driver) { return !strcmp(shortname, driver->name); }); + [shortname] (const game_driver *driver) { return !strcmp(shortname, driver->name); }); return iter != driver_list.end(); }; // do we want to show devices? - bool include_devices = uintptr_t(menu_event->itemref) == 1; + bool include_devices = uintptr_t(ev->itemref) == 1; // and do the dirty work info_xml_creator creator(machine().options()); creator.output(pfile, filter, include_devices); - machine().popmessage(_("%s.xml saved under ui folder."), filename.c_str()); + machine().popmessage(_("%s.xml saved in UI settings folder."), filename); } } break; case 2: - if (menu_event->iptkey == IPT_UI_SELECT) + if (ev->iptkey == IPT_UI_SELECT) { std::string filename("exported"); emu_file infile(ui().options().ui_path(), OPEN_FLAG_READ); - if (infile.open(filename.c_str(), ".txt") == osd_file::error::NONE) + if (!infile.open(filename + ".txt")) for (int seq = 0; ; ++seq) { - std::string seqtext = string_format("%s_%04d", filename, seq); - if (infile.open(seqtext.c_str(), ".txt") != osd_file::error::NONE) + const std::string seqtext = string_format("%s_%04d", filename, seq); + if (infile.open(seqtext + ".txt")) { filename = seqtext; break; @@ -630,7 +712,7 @@ void menu_export::handle() // attempt to open the output file emu_file file(ui().options().ui_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); - if (file.open(filename.c_str(), ".txt") == osd_file::error::NONE) + if (!file.open(filename + ".txt")) { // print the header std::ostringstream buffer; @@ -643,9 +725,9 @@ void menu_export::handle() // iterate through drivers and output the info while (drvlist.next()) util::stream_format(buffer, "%-18s\"%s\"\n", drvlist.driver().name, drvlist.driver().type.fullname()); - file.puts(buffer.str().c_str()); + file.puts(buffer.str()); file.close(); - machine().popmessage(_("%s.txt saved under ui folder."), filename.c_str()); + machine().popmessage(_("%s.txt saved in UI settings folder."), filename); } } break; @@ -653,18 +735,20 @@ void menu_export::handle() break; } } + + return false; } //------------------------------------------------- // populate //------------------------------------------------- -void menu_export::populate(float &customtop, float &custombottom) +void menu_export::populate() { // add options items - item_append(_("Export list in XML format (like -listxml)"), "", 0, (void *)(uintptr_t)1); - item_append(_("Export list in XML format (like -listxml, but exclude devices)"), "", 0, (void *)(uintptr_t)3); - item_append(_("Export list in TXT format (like -listfull)"), "", 0, (void *)(uintptr_t)2); + item_append(_("Export list in XML format (like -listxml)"), 0, (void *)(uintptr_t)1); + item_append(_("Export list in XML format (like -listxml, but exclude devices)"), 0, (void *)(uintptr_t)3); + item_append(_("Export list in TXT format (like -listfull)"), 0, (void *)(uintptr_t)2); item_append(menu_item_type::SEPARATOR); } @@ -675,23 +759,21 @@ void menu_export::populate(float &customtop, float &custombottom) menu_machine_configure::menu_machine_configure( mame_ui_manager &mui, render_container &container, - game_driver const &drv, - std::function<void (bool, bool)> &&handler, - float x0, float y0) + ui_system_info const &info, + std::function<void (bool, bool)> &&handler) : menu(mui, container) , m_handler(std::move(handler)) - , m_drv(drv) - , m_x0(x0) - , m_y0(y0) + , m_sys(info) , m_curbios(0) - , m_was_favorite(mame_machine_manager::instance()->favorite().is_favorite_system(drv)) + , m_was_favorite(mame_machine_manager::instance()->favorite().is_favorite_system(*info.driver)) , m_want_favorite(m_was_favorite) { // parse the INI file std::ostringstream error; osd_setup_osd_specific_emu_options(m_opts); - mame_options::parse_standard_inis(m_opts, error, &m_drv); + mame_options::parse_standard_inis(m_opts, error, m_sys.driver); setup_bios(); + set_heading(util::string_format(_("System Settings:\n%1$s"), m_sys.description)); } menu_machine_configure::~menu_machine_configure() @@ -699,9 +781,9 @@ menu_machine_configure::~menu_machine_configure() if (m_was_favorite != m_want_favorite) { if (m_want_favorite) - mame_machine_manager::instance()->favorite().add_favorite_system(m_drv); + mame_machine_manager::instance()->favorite().add_favorite_system(*m_sys.driver); else - mame_machine_manager::instance()->favorite().remove_favorite_system(m_drv); + mame_machine_manager::instance()->favorite().remove_favorite_system(*m_sys.driver); } if (m_handler) @@ -709,30 +791,28 @@ menu_machine_configure::~menu_machine_configure() } //------------------------------------------------- -// handlethe options menu +// handle the machine options menu //------------------------------------------------- -void menu_machine_configure::handle() +bool menu_machine_configure::handle(event const *ev) { // process the menu - process_parent(); - const event *menu_event = process(PROCESS_NOIMAGE, m_x0, m_y0); - if (menu_event != nullptr && menu_event->itemref != nullptr) + if (ev && ev->itemref) { - if (menu_event->iptkey == IPT_UI_SELECT) + if (ev->iptkey == IPT_UI_SELECT) { - switch ((uintptr_t)menu_event->itemref) + switch ((uintptr_t)ev->itemref) { case SAVE: { - std::string filename(m_drv.name); + const std::string filename(m_sys.driver->name); emu_file file(machine().options().ini_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE); - osd_file::error filerr = file.open(filename.c_str(), ".ini"); - if (filerr == osd_file::error::NONE) + std::error_condition const filerr = file.open(filename + ".ini"); + if (!filerr) { std::string inistring = m_opts.output_ini(); - file.puts(inistring.c_str()); - ui().popup_time(2, "%s", _("\n Configuration saved \n\n")); + file.puts(inistring); + ui().popup_time(2, "%s", _("\n Settings saved \n\n")); } } break; @@ -744,99 +824,90 @@ void menu_machine_configure::handle() m_want_favorite = false; reset(reset_options::REMEMBER_POSITION); break; - case CONTROLLER: - if (menu_event->iptkey == IPT_UI_SELECT) - menu::stack_push<submenu>(ui(), container(), submenu::control_options, &m_drv, &m_opts); - break; case VIDEO: - if (menu_event->iptkey == IPT_UI_SELECT) - menu::stack_push<submenu>(ui(), container(), submenu::video_options, &m_drv, &m_opts); + if (ev->iptkey == IPT_UI_SELECT) + menu::stack_push<submenu>(ui(), container(), submenu::video_options(), m_sys.driver, &m_opts); + break; + case CONTROLLER: + if (ev->iptkey == IPT_UI_SELECT) + menu::stack_push<submenu>(ui(), container(), submenu::control_options(), m_sys.driver, &m_opts); break; case ADVANCED: - if (menu_event->iptkey == IPT_UI_SELECT) - menu::stack_push<submenu>(ui(), container(), submenu::advanced_options, &m_drv, &m_opts); + if (ev->iptkey == IPT_UI_SELECT) + menu::stack_push<submenu>(ui(), container(), submenu::advanced_options(), m_sys.driver, &m_opts); break; default: break; } } - else if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) + else if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT) { - (menu_event->iptkey == IPT_UI_LEFT) ? --m_curbios : ++m_curbios; + (ev->iptkey == IPT_UI_LEFT) ? --m_curbios : ++m_curbios; m_opts.set_value(OPTION_BIOS, m_bios[m_curbios].second, OPTION_PRIORITY_CMDLINE); reset(reset_options::REMEMBER_POSITION); } } + + // triggers an item reset for any changes + return false; } //------------------------------------------------- // populate //------------------------------------------------- -void menu_machine_configure::populate(float &customtop, float &custombottom) +void menu_machine_configure::populate() { // add options items - item_append(_("BIOS"), "", FLAG_DISABLE | FLAG_UI_HEADING, nullptr); + item_append(_("BIOS"), FLAG_DISABLE | FLAG_UI_HEADING, nullptr); if (!m_bios.empty()) { uint32_t arrows = get_arrow_flags(std::size_t(0), m_bios.size() - 1, m_curbios); - item_append(_("Driver"), m_bios[m_curbios].first, arrows, (void *)(uintptr_t)BIOS); + item_append(_("System"), m_bios[m_curbios].first, arrows, (void *)(uintptr_t)BIOS); } else - item_append(_("This machine has no BIOS."), "", FLAG_DISABLE, nullptr); + item_append(_("[this system has no BIOS settings]"), FLAG_DISABLE, nullptr); item_append(menu_item_type::SEPARATOR); - item_append(_(submenu::advanced_options[0].description), "", 0, (void *)(uintptr_t)ADVANCED); - item_append(_(submenu::video_options[0].description), "", 0, (void *)(uintptr_t)VIDEO); - item_append(_(submenu::control_options[0].description), "", 0, (void *)(uintptr_t)CONTROLLER); + item_append(_(submenu::advanced_options()[0].description), 0, (void *)(uintptr_t)ADVANCED); + item_append(_(submenu::video_options()[0].description), 0, (void *)(uintptr_t)VIDEO); + item_append(_(submenu::control_options()[0].description), 0, (void *)(uintptr_t)CONTROLLER); item_append(menu_item_type::SEPARATOR); if (!m_want_favorite) - item_append(_("Add To Favorites"), "", 0, (void *)ADDFAV); + item_append(_("Add To Favorites"), 0, (void *)ADDFAV); else - item_append(_("Remove From Favorites"), "", 0, (void *)DELFAV); + item_append(_("Remove From Favorites"), 0, (void *)DELFAV); item_append(menu_item_type::SEPARATOR); - item_append(_("Save machine configuration"), "", 0, (void *)(uintptr_t)SAVE); - item_append(menu_item_type::SEPARATOR); - customtop = 2.0f * ui().get_line_height() + 3.0f * ui().box_tb_border(); + item_append(_("Save System Settings"), 0, (void *)(uintptr_t)SAVE); } //------------------------------------------------- // perform our special rendering //------------------------------------------------- -void menu_machine_configure::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) -{ - char const *const text[] = { _("Configure machine:"), m_drv.type.fullname() }; - 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); -} - void menu_machine_configure::setup_bios() { - if (!m_drv.rom) + if (!m_sys.driver->rom) return; std::string specbios(m_opts.bios()); char const *default_name(nullptr); - for (tiny_rom_entry const *rom = m_drv.rom; !ROMENTRY_ISEND(rom); ++rom) + for (tiny_rom_entry const *rom = m_sys.driver->rom; !ROMENTRY_ISEND(rom); ++rom) { if (ROMENTRY_ISDEFAULT_BIOS(rom)) default_name = rom->name; } std::size_t bios_count = 0; - for (romload::system_bios const &bios : romload::entries(m_drv.rom).get_system_bioses()) + for (romload::system_bios const &bios : romload::entries(m_sys.driver->rom).get_system_bioses()) { std::string name(bios.get_description()); u32 const bios_flags(bios.get_value()); std::string const bios_number(std::to_string(bios_flags - 1)); - // check biosnumber and name + // check BIOS number and name if ((bios_number == specbios) || (specbios == bios.get_name())) m_curbios = bios_count; @@ -859,77 +930,68 @@ void menu_machine_configure::setup_bios() menu_plugins_configure::menu_plugins_configure(mame_ui_manager &mui, render_container &container) : menu(mui, container) { + set_heading(_("Plugins")); } menu_plugins_configure::~menu_plugins_configure() { emu_file file_plugin(machine().options().ini_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); - if (file_plugin.open("plugin.ini") != osd_file::error::NONE) + if (file_plugin.open("plugin.ini")) // Can't throw in a destructor, so let's ignore silently for // now. We shouldn't write files in a destructor in any case. // // throw emu_fatalerror("Unable to create file plugin.ini\n"); return; // generate the updated INI - file_plugin.puts(mame_machine_manager::instance()->plugins().output_ini().c_str()); + file_plugin.puts(mame_machine_manager::instance()->plugins().output_ini()); } //------------------------------------------------- -// handlethe options menu +// handle the plugins menu //------------------------------------------------- -void menu_plugins_configure::handle() +bool menu_plugins_configure::handle(event const *ev) { - // process the menu - bool changed = false; - plugin_options& plugins = mame_machine_manager::instance()->plugins(); - process_parent(); - const event *menu_event = process(PROCESS_NOIMAGE); - if (menu_event != nullptr && menu_event->itemref != nullptr) + if (!ev || !ev->itemref) + return false; + + if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT || ev->iptkey == IPT_UI_SELECT) { - if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT || menu_event->iptkey == IPT_UI_SELECT) + plugin_options &plugins = mame_machine_manager::instance()->plugins(); + plugin_options::plugin *p = plugins.find((const char*)ev->itemref); + if (p) { - plugin *p = plugins.find((const char*)menu_event->itemref); - if (p) - { - p->m_start = !p->m_start; - changed = true; - } + p->m_start = !p->m_start; + ev->item->set_subtext(p->m_start ? _("On") : _("Off")); + ev->item->set_flags(p->m_start ? FLAG_LEFT_ARROW : FLAG_RIGHT_ARROW); + return true; } } - if (changed) - reset(reset_options::REMEMBER_REF); + + return false; } //------------------------------------------------- // populate //------------------------------------------------- -void menu_plugins_configure::populate(float &customtop, float &custombottom) +void menu_plugins_configure::populate() { - plugin_options& plugins = mame_machine_manager::instance()->plugins(); + plugin_options const &plugin_opts = mame_machine_manager::instance()->plugins(); - for (auto &curentry : plugins.plugins()) + bool first(true); + for (const plugin_options::plugin &p : plugin_opts.plugins()) { - bool enabled = curentry.m_start; - item_append_on_off(curentry.m_description, enabled, 0, (void *)(uintptr_t)curentry.m_name.c_str()); + if (p.m_type != "library") + { + first = false; + bool const enabled = p.m_start; + item_append_on_off(p.m_description, enabled, 0, (void *)(uintptr_t)p.m_name.c_str()); + } } + if (first) + item_append(_("No plugins found"), FLAG_DISABLE, nullptr); item_append(menu_item_type::SEPARATOR); - customtop = ui().get_line_height() + (3.0f * ui().box_tb_border()); -} - -//------------------------------------------------- -// perform our special rendering -//------------------------------------------------- - -void menu_plugins_configure::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) -{ - char const *const toptext[] = { _("Plugins") }; - draw_text_box( - std::begin(toptext), std::end(toptext), - 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); } } // namespace ui diff --git a/src/frontend/mame/ui/miscmenu.h b/src/frontend/mame/ui/miscmenu.h index 4df94c60a78..6d365ddba67 100644 --- a/src/frontend/mame/ui/miscmenu.h +++ b/src/frontend/mame/ui/miscmenu.h @@ -7,30 +7,24 @@ Internal MAME menus for the user interface. ***************************************************************************/ - #ifndef MAME_FRONTEND_UI_MISCMENU_H #define MAME_FRONTEND_UI_MISCMENU_H #pragma once +#include "ui/textbox.h" + #include "crsshair.h" #include "emuopts.h" #include <utility> #include <vector> -namespace ui { -class menu_keyboard_mode : public menu -{ -public: - menu_keyboard_mode(mame_ui_manager &mui, render_container &container); - virtual ~menu_keyboard_mode(); +struct ui_system_info; -private: - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; -}; + +namespace ui { class menu_network_devices : public menu { @@ -39,23 +33,28 @@ public: virtual ~menu_network_devices(); private: - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; + virtual bool handle(event const *ev) override; }; -class menu_bookkeeping : public menu +class menu_bookkeeping : public menu_textbox { public: menu_bookkeeping(mame_ui_manager &mui, render_container &container); virtual ~menu_bookkeeping(); +protected: + virtual void menu_activated() override; + virtual void populate_text(std::optional<text_layout> &layout, float &width, int &lines) 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; attotime prevtime; }; + class menu_crosshair : public menu { public: @@ -69,36 +68,27 @@ private: CROSSHAIR_ITEM_AUTO_TIME }; - // FIXME: use std::string instead of fixed-length arrays - constexpr static int CROSSHAIR_PIC_NAME_LENGTH = 12; - /* internal crosshair menu item data */ struct crosshair_item_data { - uint8_t type; - uint8_t player; - uint8_t min, max; - uint8_t cur; - uint8_t defvalue; - char last_name[CROSSHAIR_PIC_NAME_LENGTH + 1]; - char next_name[CROSSHAIR_PIC_NAME_LENGTH + 1]; + render_crosshair *crosshair = nullptr; + uint8_t type = 0U; + uint8_t player = 0U; + uint8_t min = 0U, max = 0U; + uint32_t cur = 0U; + uint8_t defvalue = 0U; + std::string last_name; + std::string next_name; }; - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; -}; - -class menu_quit_game : public menu -{ -public: - menu_quit_game(mame_ui_manager &mui, render_container &container); - virtual ~menu_quit_game(); + virtual void populate() override; + virtual bool handle(event const *ev) override; -private: - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + std::vector<crosshair_item_data> m_data; + std::vector<std::string> m_pics; }; + class menu_bios_selection : public menu { public: @@ -106,8 +96,8 @@ public: virtual ~menu_bios_selection(); private: - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; + virtual bool handle(event const *ev) override; }; @@ -122,12 +112,13 @@ public: virtual ~menu_export(); private: - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; + virtual bool handle(event const *ev) override; std::vector<const game_driver*> m_list; }; + //------------------------------------------------- // machine configure menu //------------------------------------------------- @@ -138,14 +129,10 @@ public: menu_machine_configure( mame_ui_manager &mui, render_container &container, - game_driver const &drv, - std::function<void (bool, bool)> &&handler = nullptr, - float x0 = 0.0f, float y0 = 0.0f); + ui_system_info const &info, + std::function<void (bool, bool)> &&handler = nullptr); virtual ~menu_machine_configure(); -protected: - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; - private: using s_bios = std::vector<std::pair<std::string, int>>; @@ -154,29 +141,28 @@ private: ADDFAV = 1, DELFAV, SAVE, - CONTROLLER, VIDEO, + CONTROLLER, BIOS, ADVANCED, LAST = ADVANCED }; - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; + virtual bool handle(event const *ev) override; void setup_bios(); std::function<void (bool, bool)> const m_handler; - game_driver const &m_drv; + ui_system_info const &m_sys; emu_options m_opts; - float const m_x0; - float const m_y0; s_bios m_bios; std::size_t m_curbios; bool const m_was_favorite; bool m_want_favorite; }; + //------------------------------------------------- // plugins configure menu //------------------------------------------------- @@ -188,10 +174,8 @@ public: virtual ~menu_plugins_configure(); protected: - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; - - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void populate() override; + virtual bool handle(event const *ev) override; }; } // namespace ui diff --git a/src/frontend/mame/ui/moptions.cpp b/src/frontend/mame/ui/moptions.cpp index 1b4779ddaf0..d6eba821947 100644 --- a/src/frontend/mame/ui/moptions.cpp +++ b/src/frontend/mame/ui/moptions.cpp @@ -20,63 +20,74 @@ const options_entry ui_options::s_option_entries[] = { // search path options - { nullptr, nullptr, OPTION_HEADER, "UI SEARCH PATH OPTIONS" }, - { OPTION_HISTORY_PATH, "history;dats;.", OPTION_STRING, "path to history files" }, - { OPTION_CATEGORYINI_PATH, "folders", OPTION_STRING, "path to catagory ini files" }, - { OPTION_CABINETS_PATH, "cabinets;cabdevs", OPTION_STRING, "path to cabinets / devices image" }, - { OPTION_CPANELS_PATH, "cpanel", OPTION_STRING, "path to control panel image" }, - { OPTION_PCBS_PATH, "pcb", OPTION_STRING, "path to pcbs image" }, - { OPTION_FLYERS_PATH, "flyers", OPTION_STRING, "path to flyers image" }, - { OPTION_TITLES_PATH, "titles", OPTION_STRING, "path to titles image" }, - { OPTION_ENDS_PATH, "ends", OPTION_STRING, "path to ends image" }, - { OPTION_MARQUEES_PATH, "marquees", OPTION_STRING, "path to marquees image" }, - { OPTION_ARTPREV_PATH, "artwork preview;artpreview", OPTION_STRING, "path to artwork preview image" }, - { OPTION_BOSSES_PATH, "bosses", OPTION_STRING, "path to bosses image" }, - { OPTION_LOGOS_PATH, "logo", OPTION_STRING, "path to logos image" }, - { OPTION_SCORES_PATH, "scores", OPTION_STRING, "path to scores image" }, - { OPTION_VERSUS_PATH, "versus", OPTION_STRING, "path to versus image" }, - { OPTION_GAMEOVER_PATH, "gameover", OPTION_STRING, "path to gameover image" }, - { OPTION_HOWTO_PATH, "howto", OPTION_STRING, "path to howto image" }, - { OPTION_SELECT_PATH, "select", OPTION_STRING, "path to select image" }, - { OPTION_ICONS_PATH, "icons", OPTION_STRING, "path to ICOns image" }, - { OPTION_COVER_PATH, "covers", OPTION_STRING, "path to software cover image" }, - { OPTION_UI_PATH, "ui", OPTION_STRING, "path to UI files" }, + { nullptr, nullptr, option_type::HEADER, "UI SEARCH PATH OPTIONS" }, + { OPTION_HISTORY_PATH, "history;dats;.", option_type::MULTIPATH, "path to system/software info files" }, + { OPTION_CATEGORYINI_PATH, "folders", option_type::MULTIPATH, "path to category ini files" }, + { OPTION_CABINETS_PATH, "cabinets;cabdevs", option_type::MULTIPATH, "path to cabinets / devices image" }, + { OPTION_CPANELS_PATH, "cpanel", option_type::MULTIPATH, "path to control panel image" }, + { OPTION_PCBS_PATH, "pcb", option_type::MULTIPATH, "path to pcbs image" }, + { OPTION_FLYERS_PATH, "flyers", option_type::MULTIPATH, "path to flyers image" }, + { OPTION_TITLES_PATH, "titles", option_type::MULTIPATH, "path to titles image" }, + { OPTION_ENDS_PATH, "ends", option_type::MULTIPATH, "path to ends image" }, + { OPTION_MARQUEES_PATH, "marquees", option_type::MULTIPATH, "path to marquees image" }, + { OPTION_ARTPREV_PATH, "artwork preview;artpreview", option_type::MULTIPATH, "path to artwork preview image" }, + { OPTION_BOSSES_PATH, "bosses", option_type::MULTIPATH, "path to bosses image" }, + { OPTION_LOGOS_PATH, "logo", option_type::MULTIPATH, "path to logos image" }, + { OPTION_SCORES_PATH, "scores", option_type::MULTIPATH, "path to scores image" }, + { OPTION_VERSUS_PATH, "versus", option_type::MULTIPATH, "path to versus image" }, + { OPTION_GAMEOVER_PATH, "gameover", option_type::MULTIPATH, "path to gameover image" }, + { OPTION_HOWTO_PATH, "howto", option_type::MULTIPATH, "path to howto image" }, + { OPTION_SELECT_PATH, "select", option_type::MULTIPATH, "path to select image" }, + { OPTION_ICONS_PATH, "icons", option_type::MULTIPATH, "path to ICOns image" }, + { OPTION_COVER_PATH, "covers", option_type::MULTIPATH, "path to software cover image" }, + { OPTION_UI_PATH, "ui", option_type::MULTIPATH, "path to UI files" }, // misc options - { nullptr, nullptr, OPTION_HEADER, "UI MISC OPTIONS" }, - { OPTION_REMEMBER_LAST, "1", OPTION_BOOLEAN, "reselect in main menu last played game" }, - { OPTION_ENLARGE_SNAPS, "1", OPTION_BOOLEAN, "enlarge arts (snapshot, title, etc...) in right panel (keeping aspect ratio)" }, - { OPTION_FORCED4X3, "1", OPTION_BOOLEAN, "force the appearance of the snapshot in the list software to 4:3" }, - { OPTION_USE_BACKGROUND, "1", OPTION_BOOLEAN, "enable background image in main view" }, - { OPTION_SKIP_BIOS_MENU, "0", OPTION_BOOLEAN, "skip bios submenu, start with configured or default" }, - { OPTION_SKIP_PARTS_MENU, "0", OPTION_BOOLEAN, "skip parts submenu, start with first part" }, - { OPTION_LAST_USED_FILTER, "", OPTION_STRING, "latest used filter" }, - { OPTION_LAST_RIGHT_PANEL "(0-1)", "0", OPTION_INTEGER, "latest right panel focus" }, - { OPTION_LAST_USED_MACHINE, "", OPTION_STRING, "latest used machine" }, - { OPTION_INFO_AUTO_AUDIT, "0", OPTION_BOOLEAN, "enable auto audit in the general info panel" }, - { OPTION_HIDE_ROMLESS, "1", OPTION_BOOLEAN, "hide romless machine from available list" }, + { nullptr, nullptr, option_type::HEADER, "UI MISC OPTIONS" }, + { OPTION_SYSTEM_NAMES, "", option_type::MULTIPATH, "translated system names file" }, + { OPTION_SKIP_WARNINGS, "0", option_type::BOOLEAN, "display fewer repeated warnings about imperfect emulation" }, + { OPTION_UNTHROTTLE_MUTE ";utm", "0", option_type::BOOLEAN, "mute audio when running unthrottled" }, // UI options - { nullptr, nullptr, OPTION_HEADER, "UI OPTIONS" }, - { OPTION_INFOS_SIZE "(0.05-1.00)", "0.75", OPTION_FLOAT, "UI right panel infos text size (0.05 - 1.00)" }, - { OPTION_FONT_ROWS "(25-40)", "30", OPTION_INTEGER, "UI font lines per screen (25 - 40)" }, - { OPTION_HIDE_PANELS "(0-3)", "0", OPTION_INTEGER, "UI hide left/right panel in main view (0 = Show all, 1 = hide left, 2 = hide right, 3 = hide both" }, - { OPTION_UI_BORDER_COLOR, "ffffffff", OPTION_STRING, "UI border color (ARGB)" }, - { OPTION_UI_BACKGROUND_COLOR, "ef101030", OPTION_STRING, "UI background color (ARGB)" }, - { OPTION_UI_CLONE_COLOR, "ff808080", OPTION_STRING, "UI clone color (ARGB)" }, - { OPTION_UI_DIPSW_COLOR, "ffffff00", OPTION_STRING, "UI dipswitch color (ARGB)" }, - { OPTION_UI_GFXVIEWER_BG_COLOR, "ef101030", OPTION_STRING, "UI gfx viewer color (ARGB)" }, - { OPTION_UI_MOUSEDOWN_BG_COLOR, "b0606000", OPTION_STRING, "UI mouse down bg color (ARGB)" }, - { OPTION_UI_MOUSEDOWN_COLOR, "ffffff80", OPTION_STRING, "UI mouse down color (ARGB)" }, - { OPTION_UI_MOUSEOVER_BG_COLOR, "70404000", OPTION_STRING, "UI mouse over bg color (ARGB)" }, - { OPTION_UI_MOUSEOVER_COLOR, "ffffff80", OPTION_STRING, "UI mouse over color (ARGB)" }, - { OPTION_UI_SELECTED_BG_COLOR, "ef808000", OPTION_STRING, "UI selected bg color (ARGB)" }, - { OPTION_UI_SELECTED_COLOR, "ffffff00", OPTION_STRING, "UI selected color (ARGB)" }, - { OPTION_UI_SLIDER_COLOR, "ffffffff", OPTION_STRING, "UI slider color (ARGB)" }, - { OPTION_UI_SUBITEM_COLOR, "ffffffff", OPTION_STRING, "UI subitem color (ARGB)" }, - { OPTION_UI_TEXT_BG_COLOR, "ef000000", OPTION_STRING, "UI text bg color (ARGB)" }, - { OPTION_UI_TEXT_COLOR, "ffffffff", OPTION_STRING, "UI text color (ARGB)" }, - { OPTION_UI_UNAVAILABLE_COLOR, "ff404040", OPTION_STRING, "UI unavailable color (ARGB)" }, + { nullptr, nullptr, option_type::HEADER, "UI OPTIONS" }, + { OPTION_INFOS_SIZE "(0.20-1.00)", "0.75", option_type::FLOAT, "UI right panel infos text size (0.20 - 1.00)" }, + { OPTION_FONT_ROWS "(25-40)", "30", option_type::INTEGER, "UI font lines per screen (25 - 40)" }, + { OPTION_UI_BORDER_COLOR, "ffffffff", option_type::STRING, "UI border color (ARGB)" }, + { OPTION_UI_BACKGROUND_COLOR, "ef101030", option_type::STRING, "UI background color (ARGB)" }, + { OPTION_UI_CLONE_COLOR, "ff808080", option_type::STRING, "UI clone color (ARGB)" }, + { OPTION_UI_DIPSW_COLOR, "ffffff00", option_type::STRING, "UI dipswitch color (ARGB)" }, + { OPTION_UI_GFXVIEWER_BG_COLOR, "ef101030", option_type::STRING, "UI gfx viewer color (ARGB)" }, + { OPTION_UI_MOUSEDOWN_BG_COLOR, "b0606000", option_type::STRING, "UI mouse down bg color (ARGB)" }, + { OPTION_UI_MOUSEDOWN_COLOR, "ffffff80", option_type::STRING, "UI mouse down color (ARGB)" }, + { OPTION_UI_MOUSEOVER_BG_COLOR, "70404000", option_type::STRING, "UI mouse over bg color (ARGB)" }, + { OPTION_UI_MOUSEOVER_COLOR, "ffffff80", option_type::STRING, "UI mouse over color (ARGB)" }, + { OPTION_UI_SELECTED_BG_COLOR, "ef808000", option_type::STRING, "UI selected bg color (ARGB)" }, + { OPTION_UI_SELECTED_COLOR, "ffffff00", option_type::STRING, "UI selected color (ARGB)" }, + { OPTION_UI_SLIDER_COLOR, "ffffffff", option_type::STRING, "UI slider color (ARGB)" }, + { OPTION_UI_SUBITEM_COLOR, "ffffffff", option_type::STRING, "UI subitem color (ARGB)" }, + { OPTION_UI_TEXT_BG_COLOR, "ef000000", option_type::STRING, "UI text bg color (ARGB)" }, + { OPTION_UI_TEXT_COLOR, "ffffffff", option_type::STRING, "UI text color (ARGB)" }, + { OPTION_UI_UNAVAILABLE_COLOR, "ff404040", option_type::STRING, "UI unavailable color (ARGB)" }, + + // system/software selection menu options + { nullptr, nullptr, option_type::HEADER, "SYSTEM/SOFTWARE SELECTION MENU OPTIONS" }, + { OPTION_HIDE_PANELS "(0-3)", "0", option_type::INTEGER, "UI hide left/right panel in main view (0 = Show all, 1 = hide left, 2 = hide right, 3 = hide both" }, + { OPTION_USE_BACKGROUND, "1", option_type::BOOLEAN, "enable background image in main view" }, + { OPTION_SKIP_BIOS_MENU, "0", option_type::BOOLEAN, "skip bios submenu, start with configured or default" }, + { OPTION_SKIP_PARTS_MENU, "0", option_type::BOOLEAN, "skip parts submenu, start with first part" }, + { OPTION_REMEMBER_LAST, "1", option_type::BOOLEAN, "initially select last used system in main menu" }, + { OPTION_LAST_USED_MACHINE, "", option_type::STRING, "last selected system" }, + { OPTION_LAST_USED_FILTER, "", option_type::STRING, "last used system filter" }, + { OPTION_SYSTEM_RIGHT_PANEL, "image", option_type::STRING, "selected system right panel tab" }, + { OPTION_SOFTWARE_RIGHT_PANEL, "image", option_type::STRING, "selected software right panel tab" }, + { OPTION_SYSTEM_RIGHT_IMAGE, "snap", option_type::STRING, "selected system right panel image" }, + { OPTION_SOFTWARE_RIGHT_IMAGE, "snap", option_type::STRING, "selected software right panel image" }, + { OPTION_ENLARGE_SNAPS, "1", option_type::BOOLEAN, "enlarge images in right panel (keeping aspect ratio)" }, + { OPTION_FORCED4X3, "1", option_type::BOOLEAN, "force 4:3 aspect ratio for snapshots in the software menu" }, + { OPTION_INFO_AUTO_AUDIT, "0", option_type::BOOLEAN, "automatically audit media for the general info panel" }, + { OPTION_HIDE_ROMLESS, "1", option_type::BOOLEAN, "hide systems that don't require ROMs in the available system filter" }, + + // sentinel { nullptr } }; @@ -96,7 +107,7 @@ ui_options::ui_options() : core_options() rgb_t ui_options::rgb_value(const char *option) const { // find the entry - core_options::entry::shared_ptr entry = get_entry(option); + core_options::entry::shared_const_ptr entry = get_entry(option); // look up the value, and sanity check the result const char *value = entry->value(); diff --git a/src/frontend/mame/ui/moptions.h b/src/frontend/mame/ui/moptions.h index ddfa3971c3b..ab854870f32 100644 --- a/src/frontend/mame/ui/moptions.h +++ b/src/frontend/mame/ui/moptions.h @@ -38,24 +38,14 @@ #define OPTION_UI_PATH "ui_path" // core misc options -#define OPTION_REMEMBER_LAST "remember_last" -#define OPTION_ENLARGE_SNAPS "enlarge_snaps" -#define OPTION_FORCED4X3 "forced4x3" -#define OPTION_USE_BACKGROUND "use_background" -#define OPTION_SKIP_BIOS_MENU "skip_biosmenu" -#define OPTION_SKIP_PARTS_MENU "skip_partsmenu" -#define OPTION_LAST_USED_FILTER "last_used_filter" -#define OPTION_LAST_RIGHT_PANEL "last_right_panel" -#define OPTION_LAST_USED_MACHINE "last_used_machine" -#define OPTION_INFO_AUTO_AUDIT "info_audit_enabled" -#define OPTION_HIDE_ROMLESS "hide_romless" +#define OPTION_SYSTEM_NAMES "system_names" +#define OPTION_SKIP_WARNINGS "skip_warnings" +#define OPTION_UNTHROTTLE_MUTE "unthrottle_mute" // core UI options #define OPTION_INFOS_SIZE "infos_text_size" #define OPTION_FONT_ROWS "font_rows" -#define OPTION_HIDE_PANELS "hide_main_panel" - #define OPTION_UI_BORDER_COLOR "ui_border_color" #define OPTION_UI_BACKGROUND_COLOR "ui_bg_color" #define OPTION_UI_GFXVIEWER_BG_COLOR "ui_gfxviewer_color" @@ -73,6 +63,24 @@ #define OPTION_UI_DIPSW_COLOR "ui_dipsw_color" #define OPTION_UI_SLIDER_COLOR "ui_slider_color" +// system/software selection menu options +#define OPTION_HIDE_PANELS "hide_main_panel" +#define OPTION_USE_BACKGROUND "use_background" +#define OPTION_SKIP_BIOS_MENU "skip_biosmenu" +#define OPTION_SKIP_PARTS_MENU "skip_partsmenu" +#define OPTION_REMEMBER_LAST "remember_last" +#define OPTION_LAST_USED_MACHINE "last_used_machine" +#define OPTION_LAST_USED_FILTER "last_used_filter" +#define OPTION_SYSTEM_RIGHT_PANEL "system_right_panel" +#define OPTION_SOFTWARE_RIGHT_PANEL "software_right_panel" +#define OPTION_SYSTEM_RIGHT_IMAGE "system_right_image" +#define OPTION_SOFTWARE_RIGHT_IMAGE "software_right_image" +#define OPTION_ENLARGE_SNAPS "enlarge_snaps" +#define OPTION_FORCED4X3 "forced4x3" +#define OPTION_INFO_AUTO_AUDIT "info_audit_enabled" +#define OPTION_HIDE_ROMLESS "hide_romless" + + class ui_options : public core_options { public: @@ -102,22 +110,15 @@ public: const char *ui_path() const { return value(OPTION_UI_PATH); } // Misc options - bool remember_last() const { return bool_value(OPTION_REMEMBER_LAST); } + const char *system_names() const { return value(OPTION_SYSTEM_NAMES); } + bool skip_warnings() const { return bool_value(OPTION_SKIP_WARNINGS); } bool enlarge_snaps() const { return bool_value(OPTION_ENLARGE_SNAPS); } bool forced_4x3_snapshot() const { return bool_value(OPTION_FORCED4X3); } - bool use_background_image() const { return bool_value(OPTION_USE_BACKGROUND); } - bool skip_bios_menu() const { return bool_value(OPTION_SKIP_BIOS_MENU); } - bool skip_parts_menu() const { return bool_value(OPTION_SKIP_PARTS_MENU); } - const char *last_used_machine() const { return value(OPTION_LAST_USED_MACHINE); } - const char *last_used_filter() const { return value(OPTION_LAST_USED_FILTER); } - int last_right_panel() const { return int_value(OPTION_LAST_RIGHT_PANEL); } - bool info_audit() const { return bool_value(OPTION_INFO_AUTO_AUDIT); } - bool hide_romless() const { return bool_value(OPTION_HIDE_ROMLESS); } + bool unthrottle_mute() const { return bool_value(OPTION_UNTHROTTLE_MUTE); } // UI options float infos_size() const { return float_value(OPTION_INFOS_SIZE); } int font_rows() const { return int_value(OPTION_FONT_ROWS); } - int hide_panels() const { return int_value(OPTION_HIDE_PANELS); } rgb_t border_color() const { return rgb_value(OPTION_UI_BORDER_COLOR); } rgb_t background_color() const { return rgb_value(OPTION_UI_BACKGROUND_COLOR); } @@ -136,6 +137,21 @@ public: rgb_t dipsw_color() const { return rgb_value(OPTION_UI_DIPSW_COLOR); } rgb_t slider_color() const { return rgb_value(OPTION_UI_SLIDER_COLOR); } + // system/software selection menu options + int hide_panels() const { return int_value(OPTION_HIDE_PANELS); } + bool use_background_image() const { return bool_value(OPTION_USE_BACKGROUND); } + bool skip_bios_menu() const { return bool_value(OPTION_SKIP_BIOS_MENU); } + bool skip_parts_menu() const { return bool_value(OPTION_SKIP_PARTS_MENU); } + bool remember_last() const { return bool_value(OPTION_REMEMBER_LAST); } + const char *last_used_machine() const { return value(OPTION_LAST_USED_MACHINE); } + const char *last_used_filter() const { return value(OPTION_LAST_USED_FILTER); } + char const *system_right_panel() const { return value(OPTION_SYSTEM_RIGHT_PANEL); } + char const *software_right_panel() const { return value(OPTION_SOFTWARE_RIGHT_PANEL); } + char const *system_right_image() const { return value(OPTION_SYSTEM_RIGHT_IMAGE); } + char const *software_right_image() const { return value(OPTION_SOFTWARE_RIGHT_IMAGE); } + bool info_audit() const { return bool_value(OPTION_INFO_AUTO_AUDIT); } + bool hide_romless() const { return bool_value(OPTION_HIDE_ROMLESS); } + rgb_t rgb_value(const char *option) const; private: diff --git a/src/frontend/mame/ui/optsmenu.cpp b/src/frontend/mame/ui/optsmenu.cpp index 497d527e6cf..07178b2cae4 100644 --- a/src/frontend/mame/ui/optsmenu.cpp +++ b/src/frontend/mame/ui/optsmenu.cpp @@ -11,16 +11,20 @@ #include "emu.h" #include "ui/optsmenu.h" -#include "ui/ui.h" -#include "ui/submenu.h" -#include "ui/selector.h" #include "ui/custui.h" -#include "ui/sndmenu.h" -#include "ui/inputmap.h" #include "ui/dirmenu.h" +#include "ui/inputdevices.h" +#include "ui/inputmap.h" +#include "ui/miscmenu.h" +#include "ui/selector.h" +#include "ui/sndmenu.h" +#include "ui/submenu.h" +#include "ui/ui.h" #include "mame.h" #include "mameopts.h" + +#include "fileio.h" #include "rendfont.h" @@ -37,6 +41,8 @@ menu_simple_game_options::menu_simple_game_options( : menu(mui, container) , m_handler(std::move(handler)) { + set_process_flags(PROCESS_LR_REPEAT); + set_heading(_("General Settings")); } //------------------------------------------------- @@ -54,96 +60,75 @@ menu_simple_game_options::~menu_simple_game_options() // handle //------------------------------------------------- -void menu_simple_game_options::handle() +bool menu_simple_game_options::handle(event const *ev) { - // process the menu - event const *const menu_event(process(PROCESS_LR_REPEAT)); - if (menu_event && menu_event->itemref) - handle_item_event(*menu_event); + return ev && ev->itemref && handle_item_event(*ev); } //------------------------------------------------- // populate //------------------------------------------------- -void menu_simple_game_options::populate(float &customtop, float &custombottom) +void menu_simple_game_options::populate() { - item_append(_(submenu::video_options[0].description), "", 0, (void *)(uintptr_t)DISPLAY_MENU); - item_append(_("Sound Options"), "", 0, (void *)(uintptr_t)SOUND_MENU); - item_append(_(submenu::misc_options[0].description), "", 0, (void *)(uintptr_t)MISC_MENU); - item_append(_(submenu::control_options[0].description), "", 0, (void *)(uintptr_t)CONTROLLER_MENU); - item_append(_("General Inputs"), "", 0, (void *)(uintptr_t)CGI_MENU); - item_append(_(submenu::advanced_options[0].description), "", 0, (void *)(uintptr_t)ADVANCED_MENU); + item_append(_(submenu::video_options()[0].description), 0, (void *)(uintptr_t)DISPLAY_MENU); + item_append(_("Sound Options"), 0, (void *)(uintptr_t)SOUND_MENU); + item_append(_(submenu::misc_options()[0].description), 0, (void *)(uintptr_t)MISC_MENU); + item_append(_(submenu::control_options()[0].description), 0, (void *)(uintptr_t)CONTROLLER_MENU); + item_append(_("Input Assignments"), 0, (void *)(uintptr_t)INPUTASSIGN_MENU); + item_append(_(submenu::advanced_options()[0].description), 0, (void *)(uintptr_t)ADVANCED_MENU); + if (machine().options().plugins()) + item_append(_("Plugins"), 0, (void *)(uintptr_t)PLUGINS_MENU); item_append(menu_item_type::SEPARATOR); - item_append(_("Save Configuration"), "", 0, (void *)(uintptr_t)SAVE_CONFIG); - - custombottom = 2.0f * ui().get_line_height() + 3.0f * ui().box_tb_border(); - customtop = ui().get_line_height() + 3.0f * ui().box_tb_border(); + item_append(_("Input Devices"), 0, (void *)(uintptr_t)INPUTDEV_MENU); + item_append(menu_item_type::SEPARATOR); + item_append(_("Save Settings"), 0, (void *)(uintptr_t)SAVE_CONFIG); } //------------------------------------------------- // handle item //------------------------------------------------- -void menu_simple_game_options::handle_item_event(event const &menu_event) +bool menu_simple_game_options::handle_item_event(event const &menu_event) { - switch ((uintptr_t)menu_event.itemref) + if (IPT_UI_SELECT == menu_event.iptkey) { - case MISC_MENU: - if (menu_event.iptkey == IPT_UI_SELECT) + switch ((uintptr_t)menu_event.itemref) { - menu::stack_push<submenu>(ui(), container(), submenu::misc_options); + case MISC_MENU: + menu::stack_push<submenu>(ui(), container(), submenu::misc_options()); ui_globals::reset = true; - } - break; - case SOUND_MENU: - if (menu_event.iptkey == IPT_UI_SELECT) - { + break; + case SOUND_MENU: menu::stack_push<menu_sound_options>(ui(), container()); ui_globals::reset = true; - } - break; - case DISPLAY_MENU: - if (menu_event.iptkey == IPT_UI_SELECT) - { - menu::stack_push<submenu>(ui(), container(), submenu::video_options); + break; + case DISPLAY_MENU: + menu::stack_push<submenu>(ui(), container(), submenu::video_options()); ui_globals::reset = true; - } - break; - case CONTROLLER_MENU: - if (menu_event.iptkey == IPT_UI_SELECT) - menu::stack_push<submenu>(ui(), container(), submenu::control_options); - break; - case CGI_MENU: - if (menu_event.iptkey == IPT_UI_SELECT) + break; + case CONTROLLER_MENU: + menu::stack_push<submenu>(ui(), container(), submenu::control_options()); + break; + case INPUTASSIGN_MENU: menu::stack_push<menu_input_groups>(ui(), container()); - break; - case ADVANCED_MENU: - if (menu_event.iptkey == IPT_UI_SELECT) - { - menu::stack_push<submenu>(ui(), container(), submenu::advanced_options); + break; + case ADVANCED_MENU: + menu::stack_push<submenu>(ui(), container(), submenu::advanced_options()); ui_globals::reset = true; - } - break; - case SAVE_CONFIG: - if (menu_event.iptkey == IPT_UI_SELECT) + break; + case PLUGINS_MENU: + menu::stack_push<menu_plugins_configure>(ui(), container()); + break; + case INPUTDEV_MENU: + menu::stack_push<menu_input_devices>(ui(), container()); + break; + case SAVE_CONFIG: ui().save_main_option(); - break; + break; + } } -} - -//------------------------------------------------- -// perform our special rendering -//------------------------------------------------- - -void menu_simple_game_options::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) -{ - char const *const toptext[] = { _("Settings") }; - draw_text_box( - std::begin(toptext), std::end(toptext), - 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; } @@ -175,20 +160,16 @@ menu_game_options::~menu_game_options() // handle //------------------------------------------------- -void menu_game_options::handle() +bool menu_game_options::handle(event const *ev) { - // process the menu - process_parent(); - event const *const menu_event(process(PROCESS_LR_REPEAT | PROCESS_NOIMAGE)); - if (menu_event && menu_event->itemref) - handle_item_event(*menu_event); + return ev && ev->itemref && handle_item_event(*ev); } //------------------------------------------------- // populate //------------------------------------------------- -void menu_game_options::populate(float &customtop, float &custombottom) +void menu_game_options::populate() { // set filter arrow std::string fbuff; @@ -196,31 +177,30 @@ void menu_game_options::populate(float &customtop, float &custombottom) // add filter item uint32_t arrow_flags = get_arrow_flags<uint16_t>(machine_filter::FIRST, machine_filter::LAST, m_main_filter); machine_filter &active_filter(m_filter_data.get_filter(m_main_filter)); - item_append(_("Filter"), active_filter.display_name(), arrow_flags, (void *)(uintptr_t)FILTER_MENU); + item_append(_("System Filter"), active_filter.display_name(), arrow_flags, (void *)(uintptr_t)FILTER_MENU); // add subitem if the filter wants it if (active_filter.wants_adjuster()) { - std::string name("^!"); - convert_command_glyph(name); + std::string name(convert_command_glyph("^!")); item_append(name, active_filter.adjust_text(), active_filter.arrow_flags(), (void *)(FILTER_ADJUST)); } item_append(menu_item_type::SEPARATOR); // add options items - item_append(_("Customize UI"), "", 0, (void *)(uintptr_t)CUSTOM_MENU); - item_append(_("Configure Directories"), "", 0, (void *)(uintptr_t)CONF_DIR); + item_append(_("Customize UI"), 0, (void *)(uintptr_t)CUSTOM_MENU); + item_append(_("Configure Folders"), 0, (void *)(uintptr_t)CONF_DIR); // add the options that don't relate to the UI - menu_simple_game_options::populate(customtop, custombottom); + menu_simple_game_options::populate(); } //------------------------------------------------- // handle item //------------------------------------------------- -void menu_game_options::handle_item_event(event const &menu_event) +bool menu_game_options::handle_item_event(event const &menu_event) { bool changed = false; @@ -239,7 +219,7 @@ void menu_game_options::handle_item_event(event const &menu_event) s_sel[index] = machine_filter::display_name(machine_filter::type(index)); menu::stack_push<menu_selector>( - ui(), container(), std::move(s_sel), m_main_filter, + ui(), container(), _("System Filter"), std::move(s_sel), m_main_filter, [this] (int selection) { m_main_filter = machine_filter::type(selection); @@ -266,7 +246,7 @@ void menu_game_options::handle_item_event(event const &menu_event) if (machine_filter::CUSTOM == filter.get_type()) { emu_file file(ui().options().ui_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); - if (file.open("custom_", emulator_info::get_configname(), "_filter.ini") == osd_file::error::NONE) + if (!file.open(util::string_format("custom_%s_filter.ini", emulator_info::get_configname()))) { filter.save_ini(file, 0); file.close(); @@ -282,15 +262,17 @@ void menu_game_options::handle_item_event(event const &menu_event) break; case CUSTOM_MENU: if (menu_event.iptkey == IPT_UI_SELECT) - menu::stack_push<menu_custom_ui>(ui(), container()); + menu::stack_push<menu_custom_ui>(ui(), container(), [this] () { reset(reset_options::REMEMBER_REF); }); break; default: - menu_simple_game_options::handle_item_event(menu_event); - return; + return menu_simple_game_options::handle_item_event(menu_event); } if (changed) reset(reset_options::REMEMBER_REF); + + // triggers an item reset for any changes + return false; } } // namespace ui diff --git a/src/frontend/mame/ui/optsmenu.h b/src/frontend/mame/ui/optsmenu.h index 2afe9b68a89..b300ed90cd5 100644 --- a/src/frontend/mame/ui/optsmenu.h +++ b/src/frontend/mame/ui/optsmenu.h @@ -28,22 +28,22 @@ public: virtual ~menu_simple_game_options() override; protected: - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; - virtual void handle() override; - virtual void populate(float &customtop, float &custombottom) override; + virtual bool handle(event const *ev) override; + virtual void populate() override; - void handle_item_event(event const &menu_event); + bool handle_item_event(event const &menu_event); private: enum { DISPLAY_MENU = 1001, SOUND_MENU, - CONTROLLER_MENU, MISC_MENU, + CONTROLLER_MENU, + INPUTASSIGN_MENU, ADVANCED_MENU, - SAVE_OPTIONS, - CGI_MENU, + PLUGINS_MENU, + INPUTDEV_MENU, SAVE_CONFIG }; @@ -62,10 +62,10 @@ public: virtual ~menu_game_options() override; protected: - virtual void handle() override; - virtual void populate(float &customtop, float &custombottom) override; + virtual bool handle(event const *ev) override; + virtual void populate() override; - void handle_item_event(event const &menu_event); + bool handle_item_event(event const &menu_event); private: enum diff --git a/src/frontend/mame/ui/pluginopt.cpp b/src/frontend/mame/ui/pluginopt.cpp index d892461a75b..809047357fc 100644 --- a/src/frontend/mame/ui/pluginopt.cpp +++ b/src/frontend/mame/ui/pluginopt.cpp @@ -9,134 +9,210 @@ *********************************************************************/ #include "emu.h" +#include "pluginopt.h" -#include "ui/pluginopt.h" +#include "ui/utils.h" -#include "mame.h" #include "luaengine.h" +#include "mame.h" namespace ui { -void menu_plugin::handle() -{ - const event *menu_event = process(0); - if (menu_event != nullptr && menu_event->itemref != nullptr) +bool menu_plugin::handle(event const *ev) +{ + if (ev && ev->itemref) { - if (menu_event->iptkey == IPT_UI_SELECT) - menu::stack_push<menu_plugin_opt>(ui(), container(), (char *)menu_event->itemref); + if (ev->iptkey == IPT_UI_SELECT) + menu::stack_push<menu_plugin_opt>(ui(), container(), (char *)ev->itemref, false); } + return false; } menu_plugin::menu_plugin(mame_ui_manager &mui, render_container &container) : - menu(mui, container), - m_plugins(mame_machine_manager::instance()->lua()->get_menu()) + menu(mui, container), + m_plugins(mame_machine_manager::instance()->lua()->get_menu()) { + set_heading(_("Plugin Options")); } -void menu_plugin::populate(float &customtop, float &custombottom) +void menu_plugin::populate() { for (auto &curplugin : m_plugins) - item_append(curplugin, "", 0, (void *)curplugin.c_str()); + item_append(curplugin, 0, (void *)curplugin.c_str()); item_append(menu_item_type::SEPARATOR); } -void menu_plugin::show_menu(mame_ui_manager &mui, render_container &container, char *menu) +void menu_plugin::show_menu(mame_ui_manager &mui, render_container &container, std::string_view menu) { - // reset the menu stack - menu::stack_reset(mui.machine()); - // add the plugin menu entry - menu::stack_push<menu_plugin_opt>(mui, container, menu); + menu::stack_push<menu_plugin_opt>(mui, container, menu, true); // force the menus on mui.show_menu(); - - // make sure MAME is paused - mui.machine().pause(); } menu_plugin::~menu_plugin() { } -menu_plugin_opt::menu_plugin_opt(mame_ui_manager &mui, render_container &container, char *menu) : - ui::menu(mui, container), - m_menu(menu) +menu_plugin_opt::menu_plugin_opt(mame_ui_manager &mui, render_container &container, std::string_view menu, bool one_shot) : + ui::menu(mui, container), + m_menu(menu), + m_need_idle(false) { + set_one_shot(one_shot); } -void menu_plugin_opt::handle() +bool menu_plugin_opt::handle(event const *ev) { - const event *menu_event = process(0); - - if (menu_event != nullptr && (uintptr_t)menu_event->itemref) + void *const itemref = ev ? ev->itemref : get_selection_ref(); + std::string key; + if (ev) { - std::string key; - switch(menu_event->iptkey) + switch (ev->iptkey) { - case IPT_UI_UP: - key = "up"; - break; - case IPT_UI_DOWN: - key = "down"; - break; - case IPT_UI_LEFT: - key = "left"; - break; - case IPT_UI_RIGHT: - key = "right"; - break; - case IPT_UI_SELECT: - key = "select"; - break; - case IPT_UI_DISPLAY_COMMENT: - key = "comment"; - break; - case IPT_UI_CLEAR: - key = "clear"; - break; - case IPT_UI_CANCEL: - key = "cancel"; - break; - default: - return; + case IPT_UI_UP: + key = "up"; + break; + case IPT_UI_DOWN: + key = "down"; + break; + case IPT_UI_LEFT: + key = "left"; + break; + case IPT_UI_RIGHT: + key = "right"; + break; + case IPT_UI_PREV_GROUP: + key = "prevgroup"; + break; + case IPT_UI_NEXT_GROUP: + key = "nextgroup"; + break; + case IPT_UI_SELECT: + key = "select"; + break; + case IPT_UI_DISPLAY_COMMENT: + key = "comment"; + break; + case IPT_UI_CLEAR: + key = "clear"; + break; + case IPT_UI_BACK: + key = "back"; + break; + case IPT_UI_CANCEL: + key = "cancel"; + break; + case IPT_SPECIAL: + key = std::to_string((u32)ev->unichar); + break; + default: + break; } - if(mame_machine_manager::instance()->lua()->menu_callback(m_menu, (uintptr_t)menu_event->itemref, key)) - reset(reset_options::REMEMBER_REF); } + + if (key.empty() && !m_need_idle) + return false; + + auto const result = mame_machine_manager::instance()->lua()->menu_callback(m_menu, uintptr_t(itemref), key); + if (result.second) + set_selection(reinterpret_cast<void *>(uintptr_t(*result.second))); + if (result.first) + reset(reset_options::REMEMBER_REF); + else if (ev && (ev->iptkey == IPT_UI_BACK)) + stack_pop(); + + return result.second && !result.first; } -void menu_plugin_opt::populate(float &customtop, float &custombottom) +void menu_plugin_opt::populate() { std::vector<std::tuple<std::string, std::string, std::string>> menu_list; - mame_machine_manager::instance()->lua()->menu_populate(m_menu, menu_list); + std::string flags; + auto const sel = mame_machine_manager::instance()->lua()->menu_populate(m_menu, menu_list, flags); + uintptr_t i = 1; - for(auto &item : menu_list) + for (auto &item : menu_list) { - const std::string &text = std::get<0>(item); - const std::string &subtext = std::get<1>(item); - const std::string &tflags = std::get<2>(item); - - uint32_t flags = 0; - if(tflags == "off") - flags = FLAG_DISABLE; - else if(tflags == "l") - flags = FLAG_LEFT_ARROW; - else if(tflags == "r") - flags = FLAG_RIGHT_ARROW; - else if(tflags == "lr") - flags = FLAG_RIGHT_ARROW | FLAG_LEFT_ARROW; - - if(text == "---") + std::string &text = std::get<0>(item); + std::string &subtext = std::get<1>(item); + std::string_view tflags = std::get<2>(item); + + uint32_t item_flags_or = uint32_t(0); + uint32_t item_flags_and = ~uint32_t(0); + auto flag_start = tflags.find_first_not_of(' '); + while (std::string_view::npos != flag_start) { - item_append(menu_item_type::SEPARATOR); - i++; + tflags.remove_prefix(flag_start); + auto const flag_end = tflags.find(' '); + auto const flag = tflags.substr(0, flag_end); + tflags.remove_prefix(flag.length()); + flag_start = tflags.find_first_not_of(' '); + + if (flag == "off") + item_flags_or |= FLAG_DISABLE; + else if (flag == "on") + item_flags_and &= ~FLAG_DISABLE; + else if (flag == "l") + item_flags_or |= FLAG_LEFT_ARROW; + else if (flag == "r") + item_flags_or |= FLAG_RIGHT_ARROW; + else if (flag == "lr") + item_flags_or |= FLAG_RIGHT_ARROW | FLAG_LEFT_ARROW; + else if (flag == "invert") + item_flags_or |= FLAG_INVERT; + else if (flag == "heading") + item_flags_or |= FLAG_DISABLE | FLAG_UI_HEADING; + else + osd_printf_info("menu_plugin_opt: unknown flag '%s' for item %d (%s)\n", flag, i, text); } + + if (text == "---") + item_append(menu_item_type::SEPARATOR); else - item_append(text, subtext, flags, (void *)i++); + item_append(std::move(text), std::move(subtext), item_flags_or & item_flags_and, reinterpret_cast<void *>(i)); + ++i; } item_append(menu_item_type::SEPARATOR); + + if (sel) + set_selection(reinterpret_cast<void *>(uintptr_t(*sel))); + + uint32_t process_flags = 0U; + m_need_idle = false; + if (!flags.empty()) + { + std::string_view mflags = flags; + auto flag_start = mflags.find_first_not_of(' '); + while (std::string_view::npos != flag_start) + { + mflags.remove_prefix(flag_start); + auto const flag_end = mflags.find(' '); + auto const flag = mflags.substr(0, flag_end); + mflags.remove_prefix(flag.length()); + flag_start = mflags.find_first_not_of(' '); + + if (flag == "nokeys") + process_flags |= PROCESS_NOKEYS; + else if (flag == "lralways") + process_flags |= PROCESS_LR_ALWAYS; + else if (flag == "lrrepeat") + process_flags |= PROCESS_LR_REPEAT; + else if (flag == "customnav") + process_flags |= PROCESS_CUSTOM_NAV; + else if (flag == "ignorepause") + process_flags |= PROCESS_IGNOREPAUSE; + else if (flag == "idle") + m_need_idle = true; + else + osd_printf_info("menu_plugin_opt: unknown processing flag '%s'\n", flag); + } + if (process_flags & PROCESS_NOKEYS) + m_need_idle = true; + } + set_process_flags(process_flags); } menu_plugin_opt::~menu_plugin_opt() diff --git a/src/frontend/mame/ui/pluginopt.h b/src/frontend/mame/ui/pluginopt.h index 5a4b457654b..3ac87acd709 100644 --- a/src/frontend/mame/ui/pluginopt.h +++ b/src/frontend/mame/ui/pluginopt.h @@ -7,49 +7,55 @@ Internal menu for the plugin interface. ***************************************************************************/ - -#pragma once - #ifndef MAME_FRONTEND_UI_PLUGINOPT_H #define MAME_FRONTEND_UI_PLUGINOPT_H -#include "ui/ui.h" +#pragma once + #include "ui/menu.h" +#include "ui/ui.h" #include <string> +#include <string_view> #include <vector> namespace ui { + class menu_plugin : public menu { public: menu_plugin(mame_ui_manager &mui, render_container &container); - static void show_menu(mame_ui_manager &mui, render_container &container, char *menu); + static void show_menu(mame_ui_manager &mui, render_container &container, std::string_view menu); virtual ~menu_plugin(); private: - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; + virtual bool handle(event const *ev) override; - std::vector<std::string> &m_plugins; + std::vector<std::string> const &m_plugins; }; + class menu_plugin_opt : public menu { public: - menu_plugin_opt(mame_ui_manager &mui, render_container &container, char *menu); + menu_plugin_opt(mame_ui_manager &mui, render_container &container, std::string_view menu, bool one_shot); virtual ~menu_plugin_opt(); +protected: + virtual bool custom_ui_back() override { return true; } + private: - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; + virtual bool handle(event const *ev) override; - std::string m_menu; + std::string const m_menu; + bool m_need_idle; }; } // namespace ui -#endif /* MAME_FRONTEND_UI_PLUGINOPT_H */ +#endif // MAME_FRONTEND_UI_PLUGINOPT_H diff --git a/src/frontend/mame/ui/prscntrl.cpp b/src/frontend/mame/ui/prscntrl.cpp new file mode 100644 index 00000000000..1d5d2fb4f81 --- /dev/null +++ b/src/frontend/mame/ui/prscntrl.cpp @@ -0,0 +1,69 @@ +// license:BSD-3-Clause +// copyright-holders:Nathan Woods +/*************************************************************************** + + ui/prscntrl.cpp + + MAME's clunky built-in preset selection in a fixed list + +***************************************************************************/ + +#include "emu.h" + +#include "ui/prscntrl.h" +#include "ui/ui.h" + +namespace ui { + +/*************************************************************************** + IMPLEMENTATION +***************************************************************************/ + +//------------------------------------------------- +// ctor +//------------------------------------------------- + +menu_control_device_preset::menu_control_device_preset(mame_ui_manager &mui, render_container &container, device_image_interface &image) + : menu(mui, container) + , m_image(image) +{ +} + + +//------------------------------------------------- +// dtor +//------------------------------------------------- + +menu_control_device_preset::~menu_control_device_preset() +{ +} + + +//------------------------------------------------- +// populate +//------------------------------------------------- + +void menu_control_device_preset::populate() +{ + auto presets = m_image.preset_images_list(); + for(uintptr_t i = 0; i != uintptr_t(presets.size()); i++) + item_append(presets[i], 0, reinterpret_cast<void *>(i)); + set_selection(reinterpret_cast<void *>(uintptr_t(m_image.current_preset_image_id()))); +} + + +//------------------------------------------------- +// handle +//------------------------------------------------- + +bool menu_control_device_preset::handle(event const *ev) +{ + if (ev && (ev->iptkey == IPT_UI_SELECT)) { + int id = reinterpret_cast<uintptr_t>(ev->itemref); + m_image.switch_preset_image(id); + stack_pop(); + } + return false; +} + +} // namespace ui diff --git a/src/frontend/mame/ui/prscntrl.h b/src/frontend/mame/ui/prscntrl.h new file mode 100644 index 00000000000..a89d6712efc --- /dev/null +++ b/src/frontend/mame/ui/prscntrl.h @@ -0,0 +1,45 @@ +// license:BSD-3-Clause +// copyright-holders:Nathan Woods +/*************************************************************************** + + ui/prscntrl.h + + MAME's clunky built-in preset selection in a fixed list + +***************************************************************************/ + +#ifndef MAME_FRONTEND_UI_PRESETCNTRL_H +#define MAME_FRONTEND_UI_PRESETCNTRL_H + +#pragma once + +#include "ui/filesel.h" +#include "ui/menu.h" +#include "ui/swlist.h" + + +namespace ui { + +// ======================> menu_control_device_preset + +class menu_control_device_preset : public menu +{ +public: + menu_control_device_preset(mame_ui_manager &mui, render_container &container, device_image_interface &image); + virtual ~menu_control_device_preset() override; + +protected: + // methods + virtual bool handle(event const *ev) override; + +private: + // instance variables + device_image_interface & m_image; + + // methods + virtual void populate() override; +}; + +} // namespace ui + +#endif // MAME_FRONTEND_UI_PRESETCNTRL_H diff --git a/src/frontend/mame/ui/quitmenu.cpp b/src/frontend/mame/ui/quitmenu.cpp new file mode 100644 index 00000000000..2177313a0cd --- /dev/null +++ b/src/frontend/mame/ui/quitmenu.cpp @@ -0,0 +1,53 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + ui/quitmenu.h + + Menus involved in quitting MAME. + +***************************************************************************/ + +#include "emu.h" +#include "quitmenu.h" + +#include "uiinput.h" + + +namespace ui { + +menu_confirm_quit::menu_confirm_quit(mame_ui_manager &mui, render_container &container) + : autopause_menu<>(mui, container) +{ + set_one_shot(true); + set_needs_prev_menu_item(false); + set_heading(_("menu-quit", "Are you sure you want to quit?")); +} + + +menu_confirm_quit::~menu_confirm_quit() +{ +} + + +void menu_confirm_quit::populate() +{ + item_append(_("menu-quit", "Quit"), 0, nullptr); + item_append(_("menu-quit", "Return to emulation"), 0, nullptr); +} + + +bool menu_confirm_quit::handle(event const *ev) +{ + if (ev && (IPT_UI_SELECT == ev->iptkey)) + { + if (0 == selected_index()) + machine().schedule_exit(); + else + stack_pop(); + } + + return false; +} + +} // namespace ui diff --git a/src/frontend/mame/ui/quitmenu.h b/src/frontend/mame/ui/quitmenu.h new file mode 100644 index 00000000000..9f22801fdba --- /dev/null +++ b/src/frontend/mame/ui/quitmenu.h @@ -0,0 +1,33 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + ui/quitmenu.h + + Menus involved in quitting MAME. + +***************************************************************************/ +#ifndef MAME_FRONTEND_UI_QUITMENU_H +#define MAME_FRONTEND_UI_QUITMENU_H + +#pragma once + +#include "ui/menu.h" + + +namespace ui { + +class menu_confirm_quit : public autopause_menu<> +{ +public: + menu_confirm_quit(mame_ui_manager &mui, render_container &container); + virtual ~menu_confirm_quit(); + +private: + virtual void populate() override; + virtual bool handle(event const *ev) override; +}; + +} // namespace ui + +#endif // MAME_FRONTEND_UI_QUITMENU_H diff --git a/src/frontend/mame/ui/selector.cpp b/src/frontend/mame/ui/selector.cpp index e57012ca5af..53f68006c04 100644 --- a/src/frontend/mame/ui/selector.cpp +++ b/src/frontend/mame/ui/selector.cpp @@ -14,6 +14,9 @@ #include "ui/ui.h" #include "ui/utils.h" +#include "corestr.h" +#include "unicode.h" + namespace ui { @@ -24,10 +27,12 @@ namespace ui { menu_selector::menu_selector( mame_ui_manager &mui, render_container &container, + std::string &&title, std::vector<std::string> &&sel, int initial, std::function<void (int)> &&handler) : menu(mui, container) + , m_title(std::move(title)) , m_search() , m_str_items(std::move(sel)) , m_handler(std::move(handler)) @@ -44,51 +49,66 @@ menu_selector::~menu_selector() // handle //------------------------------------------------- -void menu_selector::handle() +bool menu_selector::handle(event const *ev) { - // process the menu - const event *menu_event = process(0); + if (!ev) + return false; - if (menu_event != nullptr && menu_event->itemref != nullptr) + switch (ev->iptkey) { - if (menu_event->iptkey == IPT_UI_SELECT) + case IPT_UI_SELECT: + if (ev->itemref) { int selection(-1); for (size_t idx = 0; (m_str_items.size() > idx) && (0 > selection); ++idx) - if ((void*)&m_str_items[idx] == menu_event->itemref) + if ((void*)&m_str_items[idx] == ev->itemref) selection = int(unsigned(idx)); m_handler(selection); - stack_pop(); } - else if (menu_event->iptkey == IPT_SPECIAL) - { - if (input_character(m_search, menu_event->unichar, uchar_is_printable)) - reset(reset_options::SELECT_FIRST); - } + break; + + case IPT_UI_PASTE: + if (paste_text(m_search, uchar_is_printable)) + reset(reset_options::SELECT_FIRST); + break; - // escape pressed with non-empty text clears the text - else if (menu_event->iptkey == IPT_UI_CANCEL && !m_search.empty()) + case IPT_SPECIAL: + if (input_character(m_search, ev->unichar, uchar_is_printable)) + reset(reset_options::SELECT_FIRST); + break; + + case IPT_UI_CANCEL: + if (!m_search.empty()) { + // escape pressed with non-empty search text clears the search text m_search.clear(); reset(reset_options::SELECT_FIRST); } + break; } + + return false; // any changes will trigger an item reset } //------------------------------------------------- // populate //------------------------------------------------- -void menu_selector::populate(float &customtop, float &custombottom) +void menu_selector::populate() { + set_heading(util::string_format(_("menu-selector", "%1$s - Search: %2$s_"), m_title, m_search)); + if (!m_search.empty()) { find_matches(m_search.c_str()); - for (int curitem = 0; m_searchlist[curitem]; ++curitem) - item_append(*m_searchlist[curitem], "", 0, (void *)m_searchlist[curitem]); + int curitem; + for (curitem = 0; m_searchlist[curitem]; ++curitem) + item_append(*m_searchlist[curitem], 0, (void *)m_searchlist[curitem]); + if (!curitem) + item_append(_("menu-selector", "[no matches]"), FLAG_DISABLE, nullptr); } else { @@ -97,35 +117,41 @@ void menu_selector::populate(float &customtop, float &custombottom) if ((0 <= m_initial) && (unsigned(m_initial) == index)) set_selected_index(index); - item_append(m_str_items[index], "", 0, (void *)&m_str_items[index]); + item_append(m_str_items[index], 0, (void *)&m_str_items[index]); } + + if (m_str_items.empty()) + item_append(_("menu-selector", "[no choices]"), FLAG_DISABLE, nullptr); // the caller was probably being dumb } item_append(menu_item_type::SEPARATOR); - customtop = custombottom = ui().get_line_height() + 3.0f * ui().box_tb_border(); m_initial = -1; } //------------------------------------------------- -// perform our special rendering +// recompute metrics //------------------------------------------------- -void menu_selector::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_selector::recompute_metrics(uint32_t width, uint32_t height, float aspect) { - std::string tempbuf[1] = { std::string(_("Selection List - Search: ")).append(m_search).append("_") }; - draw_text_box( - std::begin(tempbuf), std::end(tempbuf), - 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); + menu::recompute_metrics(width, height, aspect); + + set_custom_space(0.0F, line_height() + 3.0F * tb_border()); +} +//------------------------------------------------- +// perform our special rendering +//------------------------------------------------- + +void menu_selector::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +{ // get the text for 'UI Select' - tempbuf[0] = string_format(_("Double click or press %1$s to select"), machine().input().seq_name(machine().ioport().type_seq(IPT_UI_SELECT, 0, SEQ_TYPE_STANDARD))); + std::string const tempbuf[] = { util::string_format(_("menu-selector", "Double-click or press %1$s to select"), ui().get_general_input_setting(IPT_UI_SELECT)) }; 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, false, - ui().colors().text_color(), UI_RED_COLOR, 1.0f); + origx1, origx2, origy2 + tb_border(), origy2 + bottom, + text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, false, + ui().colors().text_color(), ui().colors().background_color()); } //------------------------------------------------- @@ -136,7 +162,7 @@ void menu_selector::find_matches(const char *str) { // allocate memory to track the penalty value m_ucs_items.reserve(m_str_items.size()); - std::vector<double> penalty(VISIBLE_GAMES_IN_SEARCH, 1.0); + std::vector<double> penalty(VISIBLE_SEARCH_ITEMS, 2.0); // impossibly high penalty for unpopulated slots std::u32string const search(ustr_from_utf8(normalize_unicode(str, unicode_normalization_form::D, true))); int index = 0; @@ -148,14 +174,14 @@ void menu_selector::find_matches(const char *str) double const curpenalty(util::edit_distance(search, m_ucs_items[index])); // insert into the sorted table of matches - for (int matchnum = VISIBLE_GAMES_IN_SEARCH - 1; matchnum >= 0; --matchnum) + for (int matchnum = VISIBLE_SEARCH_ITEMS - 1; matchnum >= 0; --matchnum) { // stop if we're worse than the current entry if (curpenalty >= penalty[matchnum]) break; // as long as this isn't the last entry, bump this one down - if (matchnum < VISIBLE_GAMES_IN_SEARCH - 1) + if (matchnum < VISIBLE_SEARCH_ITEMS - 1) { penalty[matchnum + 1] = penalty[matchnum]; m_searchlist[matchnum + 1] = m_searchlist[matchnum]; @@ -165,7 +191,7 @@ void menu_selector::find_matches(const char *str) penalty[matchnum] = curpenalty; } } - (index < VISIBLE_GAMES_IN_SEARCH) ? m_searchlist[index] = nullptr : m_searchlist[VISIBLE_GAMES_IN_SEARCH] = nullptr; + (index < VISIBLE_SEARCH_ITEMS) ? m_searchlist[index] = nullptr : m_searchlist[VISIBLE_SEARCH_ITEMS] = nullptr; } } // namespace ui diff --git a/src/frontend/mame/ui/selector.h b/src/frontend/mame/ui/selector.h index f3921536267..d52c081da05 100644 --- a/src/frontend/mame/ui/selector.h +++ b/src/frontend/mame/ui/selector.h @@ -12,9 +12,12 @@ #pragma once - #include "ui/menu.h" +#include <functional> +#include <string> +#include <vector> + namespace ui { @@ -28,31 +31,34 @@ public: menu_selector( mame_ui_manager &mui, render_container &container, + std::string &&title, std::vector<std::string> &&sel, int initial, std::function<void (int)> &&handler); virtual ~menu_selector() override; protected: - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; - virtual bool menu_has_search_active() override { return !m_search.empty(); } + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) override; + virtual bool custom_ui_back() override { return !m_search.empty(); } private: - enum { VISIBLE_GAMES_IN_SEARCH = 200 }; + enum { VISIBLE_SEARCH_ITEMS = 200 }; - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; + virtual bool handle(event const *ev) override; void find_matches(const char *str); + std::string const m_title; std::string m_search; std::vector<std::string> m_str_items; std::function<void (int)> m_handler; std::vector<std::u32string> m_ucs_items; int m_initial; - std::string *m_searchlist[VISIBLE_GAMES_IN_SEARCH + 1]; + std::string *m_searchlist[VISIBLE_SEARCH_ITEMS + 1]; }; } // namespace ui -#endif /* MAME_FRONTEND_UI_SELECTOR_H */ +#endif // MAME_FRONTEND_UI_SELECTOR_H diff --git a/src/frontend/mame/ui/selgame.cpp b/src/frontend/mame/ui/selgame.cpp index ae86b2e2f52..dda281f684a 100644 --- a/src/frontend/mame/ui/selgame.cpp +++ b/src/frontend/mame/ui/selgame.cpp @@ -18,202 +18,32 @@ #include "ui/optsmenu.h" #include "ui/selector.h" #include "ui/selsoft.h" +#include "ui/systemlist.h" #include "ui/ui.h" -#include "../info.h" +#include "infoxml.h" +#include "luaengine.h" +#include "mame.h" -#include "audit.h" +#include "corestr.h" #include "drivenum.h" #include "emuopts.h" -#include "mame.h" +#include "fileio.h" #include "rendutil.h" #include "romload.h" #include "softlist_dev.h" #include "uiinput.h" -#include "luaengine.h" +#include "unicode.h" -#include <atomic> -#include <condition_variable> #include <cstring> #include <iterator> #include <memory> -#include <mutex> -#include <thread> extern const char UI_VERSION_TAG[]; namespace ui { -namespace { - -constexpr uint32_t FLAGS_UI = ui::menu::FLAG_LEFT_ARROW | ui::menu::FLAG_RIGHT_ARROW; - -} // anonymous namespace - -class menu_select_game::persistent_data -{ -public: - enum available : unsigned - { - AVAIL_NONE = 0U, - AVAIL_SORTED_LIST = 1U << 0, - AVAIL_BIOS_COUNT = 1U << 1, - AVAIL_UCS_SHORTNAME = 1U << 2, - AVAIL_UCS_DESCRIPTION = 1U << 3, - AVAIL_UCS_MANUF_DESC = 1U << 4, - AVAIL_FILTER_DATA = 1U << 5 - }; - - ~persistent_data() - { - if (m_thread) - m_thread->join(); - } - - void cache_data() - { - std::unique_lock<std::mutex> lock(m_mutex); - do_start_caching(); - } - - bool is_available(available desired) - { - return (m_available.load(std::memory_order_acquire) & desired) == desired; - } - - void wait_available(available desired) - { - if (!is_available(desired)) - { - std::unique_lock<std::mutex> lock(m_mutex); - do_start_caching(); - m_condition.wait(lock, [this, desired] () { return is_available(desired); }); - } - } - - std::vector<ui_system_info> &sorted_list() - { - wait_available(AVAIL_SORTED_LIST); - return m_sorted_list; - } - - int bios_count() - { - wait_available(AVAIL_BIOS_COUNT); - return m_bios_count; - } - - bool unavailable_systems() - { - wait_available(AVAIL_SORTED_LIST); - return std::find_if(m_sorted_list.begin(), m_sorted_list.end(), [] (ui_system_info const &info) { return !info.available; }) != m_sorted_list.end(); - } - - machine_filter_data &filter_data() - { - wait_available(AVAIL_FILTER_DATA); - return m_filter_data; - } - - static persistent_data &instance() - { - static persistent_data data; - return data; - } - -private: - persistent_data() - : m_started(false) - , m_available(AVAIL_NONE) - , m_bios_count(0) - { - } - - void notify_available(available value) - { - std::unique_lock<std::mutex> lock(m_mutex); - m_available.fetch_or(value, std::memory_order_release); - m_condition.notify_all(); - } - - void do_start_caching() - { - if (!m_started) - { - m_started = true; - m_thread = std::make_unique<std::thread>([this] { do_cache_data(); }); - } - } - - void do_cache_data() - { - // generate full list - m_sorted_list.reserve(driver_list::total()); - std::unordered_set<std::string> manufacturers, years; - for (int x = 0; x < driver_list::total(); ++x) - { - game_driver const &driver(driver_list::driver(x)); - if (&driver != &GAME_NAME(___empty)) - { - if (driver.flags & machine_flags::IS_BIOS_ROOT) - ++m_bios_count; - - m_sorted_list.emplace_back(driver, x, false); - m_filter_data.add_manufacturer(driver.manufacturer); - m_filter_data.add_year(driver.year); - } - } - - // notify that BIOS count is valie - notify_available(AVAIL_BIOS_COUNT); - - // sort drivers and notify - std::stable_sort( - m_sorted_list.begin(), - m_sorted_list.end(), - [] (ui_system_info const &lhs, ui_system_info const &rhs) { return sorted_game_list(lhs.driver, rhs.driver); }); - notify_available(AVAIL_SORTED_LIST); - - // sort manufacturers and years - m_filter_data.finalise(); - notify_available(AVAIL_FILTER_DATA); - - // convert shortnames to UCS-4 - for (ui_system_info &info : m_sorted_list) - info.ucs_shortname = ustr_from_utf8(normalize_unicode(info.driver->name, unicode_normalization_form::D, true)); - notify_available(AVAIL_UCS_SHORTNAME); - - // convert descriptions to UCS-4 - for (ui_system_info &info : m_sorted_list) - info.ucs_description = ustr_from_utf8(normalize_unicode(info.driver->type.fullname(), unicode_normalization_form::D, true)); - notify_available(AVAIL_UCS_DESCRIPTION); - - // convert "<manufacturer> <description>" to UCS-4 - std::string buf; - for (ui_system_info &info : m_sorted_list) - { - buf.assign(info.driver->manufacturer); - buf.append(1, ' '); - buf.append(info.driver->type.fullname()); - info.ucs_manufacturer_description = ustr_from_utf8(normalize_unicode(buf, unicode_normalization_form::D, true)); - } - notify_available(AVAIL_UCS_MANUF_DESC); - } - - // synchronisation - std::mutex m_mutex; - std::condition_variable m_condition; - std::unique_ptr<std::thread> m_thread; - std::atomic<bool> m_started; - std::atomic<unsigned> m_available; - - // data - std::vector<ui_system_info> m_sorted_list; - machine_filter_data m_filter_data; - int m_bios_count; -}; - bool menu_select_game::s_first_start = true; @@ -223,19 +53,19 @@ bool menu_select_game::s_first_start = true; menu_select_game::menu_select_game(mame_ui_manager &mui, render_container &container, const char *gamename) : menu_select_launch(mui, container, false) - , m_persistent_data(persistent_data::instance()) + , m_persistent_data(system_list::instance()) , m_icons(MAX_ICONS_RENDER) , m_icon_paths() , m_displaylist() , m_searchlist() - , m_searched_fields(persistent_data::AVAIL_NONE) + , m_searched_fields(system_list::AVAIL_NONE) , m_populated_favorites(false) { std::string error_string, last_filter, sub_filter; ui_options &moptions = mui.options(); // load drivers cache - m_persistent_data.cache_data(); + m_persistent_data.cache_data(mui.options()); // check if there are available system icons check_for_icons(nullptr); @@ -246,30 +76,26 @@ menu_select_game::menu_select_game(mame_ui_manager &mui, render_container &conta if (s_first_start) { - //s_first_start = false; TODO: why wansn't it ever clearing the first start flag? + //s_first_start = false; TODO: why wasn't it ever clearing the first start flag? reselect_last::set_driver(moptions.last_used_machine()); - ui_globals::rpanel = std::min<int>(std::max<int>(moptions.last_right_panel(), RP_FIRST), RP_LAST); std::string tmp(moptions.last_used_filter()); - std::size_t const found = tmp.find_first_of(","); + std::size_t const found = tmp.find_first_of(','); std::string fake_ini; if (found == std::string::npos) { - fake_ini = util::string_format("%s = 1\n", tmp); + fake_ini = util::string_format(u8"\uFEFF%s = 1\n", tmp); } else { std::string const sub_filter(tmp.substr(found + 1)); tmp.resize(found); - fake_ini = util::string_format("%s = %s\n", tmp, sub_filter); + fake_ini = util::string_format(u8"\uFEFF%s = %s\n", tmp, sub_filter); } - emu_file file(ui().options().ui_path(), OPEN_FLAG_READ); - if (file.open_ram(fake_ini.c_str(), fake_ini.size()) == osd_file::error::NONE) - { - m_persistent_data.filter_data().load_ini(file); - file.close(); - } + util::core_file::ptr file; + if (!util::core_file::open_ram(fake_ini.c_str(), fake_ini.size(), OPEN_FLAG_READ, file)) + m_persistent_data.filter_data().load_ini(*file); } // do this after processing the last used filter setting so it overwrites the placeholder @@ -281,8 +107,11 @@ menu_select_game::menu_select_game(mame_ui_manager &mui, render_container &conta mui.machine().options().set_value(OPTION_SNAPNAME, "%g/%i", OPTION_PRIORITY_CMDLINE); + // restore last right panel settings + set_right_panel(moptions.system_right_panel()); + set_right_image(moptions.system_right_image()); + ui_globals::curdats_view = 0; - ui_globals::panels_status = moptions.hide_panels(); ui_globals::curdats_total = 1; } @@ -292,91 +121,108 @@ menu_select_game::menu_select_game(mame_ui_manager &mui, render_container &conta menu_select_game::~menu_select_game() { - std::string error_string, last_driver; - game_driver const *driver; - ui_software_info const *swinfo; - get_selection(swinfo, driver); - if (swinfo) - last_driver = swinfo->shortname; - else - if (driver) - last_driver = driver->name; + // TODO: reconsider when to do this + ui().save_ui_options(); +} - std::string const filter(m_persistent_data.filter_data().get_config_string()); - ui_options &mopt = ui().options(); - mopt.set_value(OPTION_LAST_RIGHT_PANEL, ui_globals::rpanel, OPTION_PRIORITY_CMDLINE); - mopt.set_value(OPTION_LAST_USED_FILTER, filter.c_str(), OPTION_PRIORITY_CMDLINE); - mopt.set_value(OPTION_LAST_USED_MACHINE, last_driver.c_str(), OPTION_PRIORITY_CMDLINE); - mopt.set_value(OPTION_HIDE_PANELS, ui_globals::panels_status, OPTION_PRIORITY_CMDLINE); - ui().save_ui_options(); +//------------------------------------------------- +// recompute metrics +//------------------------------------------------- + +void menu_select_game::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + menu_select_launch::recompute_metrics(width, height, aspect); + + m_icons.clear(); + + // configure the custom rendering + set_custom_space(3.0F * line_height() + 5.0F * tb_border(), 4.0F * line_height() + 3.0F * tb_border()); } + //------------------------------------------------- -// handle +// menu_activated //------------------------------------------------- -void menu_select_game::handle() +void menu_select_game::menu_activated() { - if (!m_prev_selected) - m_prev_selected = item(0).ref; + menu_select_launch::menu_activated(); // if I have to load datfile, perform a hard reset if (ui_globals::reset) { + // dumb workaround for not being able to add an exit notifier + struct cache_reset { ~cache_reset() { system_list::instance().reset_cache(); } }; + ui().get_session_data<cache_reset, cache_reset>(); + ui_globals::reset = false; machine().schedule_hard_reset(); stack_reset(); - return; } +} + +//------------------------------------------------- +// menu_deactivated +//------------------------------------------------- + +void menu_select_game::menu_deactivated() +{ + menu_select_launch::menu_deactivated(); + + // get the "last selected system" string + ui_system_info const *system; + ui_software_info const *swinfo; + get_selection(swinfo, system); + std::string last_driver; + if (swinfo) + last_driver = swinfo->shortname; + else if (system) + last_driver = system->driver->name; + + // serialise the selected filter settings + std::string const filter(m_persistent_data.filter_data().get_config_string()); + + ui_options &mopt = ui().options(); + mopt.set_value(OPTION_LAST_USED_MACHINE, last_driver, OPTION_PRIORITY_CMDLINE); + mopt.set_value(OPTION_LAST_USED_FILTER, filter, OPTION_PRIORITY_CMDLINE); + mopt.set_value(OPTION_SYSTEM_RIGHT_PANEL, right_panel_config_string(), OPTION_PRIORITY_CMDLINE); + mopt.set_value(OPTION_SYSTEM_RIGHT_IMAGE, right_image_config_string(), OPTION_PRIORITY_CMDLINE); +} + +//------------------------------------------------- +// handle +//------------------------------------------------- + +bool menu_select_game::handle(event const *ev) +{ + if (!m_prev_selected && (item_count() > 0)) + m_prev_selected = item(0).ref(); // if I have to select software, force software list submenu if (reselect_last::get()) { - const game_driver *driver; + // FIXME: this is never hit, need a better way to return to software selection if necessary + const ui_system_info *system; const ui_software_info *software; - get_selection(software, driver); - menu::stack_push<menu_select_software>(ui(), container(), *driver); - return; + get_selection(software, system); + menu::stack_push<menu_select_software>(ui(), container(), *system); + return false; } - // ignore pause keys by swallowing them before we process the menu - machine().ui_input().pressed(IPT_UI_PAUSE); + // FIXME: everything above here used to run before events were processed // process the menu - const event *menu_event = process(PROCESS_LR_REPEAT); - if (menu_event) + bool changed = false; + if (ev) { if (dismiss_error()) { - // reset the error on any future menu_event + // reset the error on any subsequent menu event + changed = true; } - else switch (menu_event->iptkey) + else switch (ev->iptkey) { - case IPT_UI_UP: - if ((get_focus() == focused_menu::LEFT) && (machine_filter::FIRST < m_filter_highlight)) - --m_filter_highlight; - break; - - case IPT_UI_DOWN: - if ((get_focus() == focused_menu::LEFT) && (machine_filter::LAST > m_filter_highlight)) - m_filter_highlight++; - break; - - case IPT_UI_HOME: - if (get_focus() == focused_menu::LEFT) - m_filter_highlight = machine_filter::FIRST; - break; - - case IPT_UI_END: - if (get_focus() == focused_menu::LEFT) - m_filter_highlight = machine_filter::LAST; - break; - - case IPT_UI_CONFIGURE: - inkey_navigation(); - break; - case IPT_UI_EXPORT: inkey_export(); break; @@ -386,92 +232,43 @@ void menu_select_game::handle() break; default: - if (menu_event->itemref) + if (ev->itemref) { - switch (menu_event->iptkey) + switch (ev->iptkey) { case IPT_UI_SELECT: if (get_focus() == focused_menu::MAIN) { if (m_populated_favorites) - inkey_select_favorite(menu_event); + changed = inkey_select_favorite(ev); else - inkey_select(menu_event); - } - break; - - case IPT_CUSTOM: - // handle IPT_CUSTOM (mouse right click) - if (!m_populated_favorites) - { - menu::stack_push<menu_machine_configure>( - ui(), container(), - *reinterpret_cast<const game_driver *>(m_prev_selected), - nullptr, - menu_event->mouse.x0, menu_event->mouse.y0); - } - else - { - ui_software_info *sw = reinterpret_cast<ui_software_info *>(m_prev_selected); - menu::stack_push<menu_machine_configure>( - ui(), container(), - *sw->driver, - [this, empty = sw->startempty] (bool fav, bool changed) - { - if (changed) - reset(empty ? reset_options::SELECT_FIRST : reset_options::REMEMBER_REF); - }, - menu_event->mouse.x0, menu_event->mouse.y0); - } - break; - - case IPT_UI_LEFT: - if (ui_globals::rpanel == RP_IMAGES) - { - // Images - previous_image_view(); - } - else if (ui_globals::rpanel == RP_INFOS) - { - // Infos - change_info_pane(-1); - } - break; - - case IPT_UI_RIGHT: - if (ui_globals::rpanel == RP_IMAGES) - { - // Images - next_image_view(); - } - else if (ui_globals::rpanel == RP_INFOS) - { - // Infos - change_info_pane(1); + changed = inkey_select(ev); } break; case IPT_UI_FAVORITES: - if (uintptr_t(menu_event->itemref) > skip_main_items) + if (uintptr_t(ev->itemref) > m_skip_main_items) { favorite_manager &mfav(mame_machine_manager::instance()->favorite()); if (!m_populated_favorites) { - game_driver const *const driver(reinterpret_cast<game_driver const *>(menu_event->itemref)); - if (!mfav.is_favorite_system(*driver)) + auto const &info(*reinterpret_cast<ui_system_info const *>(ev->itemref)); + auto const &driver(*info.driver); + if (!mfav.is_favorite_system(driver)) { - mfav.add_favorite_system(*driver); - machine().popmessage(_("%s\n added to favorites list."), driver->type.fullname()); + mfav.add_favorite_system(driver); + machine().popmessage(_("%s\n added to favorites list."), info.description); } else { - mfav.remove_favorite_system(*driver); - machine().popmessage(_("%s\n removed from favorites list."), driver->type.fullname()); + mfav.remove_favorite_system(driver); + machine().popmessage(_("%s\n removed from favorites list."), info.description); } + changed = true; } else { - ui_software_info const *const swinfo(reinterpret_cast<ui_software_info const *>(menu_event->itemref)); + ui_software_info const *const swinfo(reinterpret_cast<ui_software_info const *>(ev->itemref)); machine().popmessage(_("%s\n removed from favorites list."), swinfo->longname); mfav.remove_favorite_software(*swinfo); reset(reset_options::SELECT_FIRST); @@ -479,37 +276,34 @@ void menu_select_game::handle() } break; - case IPT_UI_AUDIT_FAST: - if (m_persistent_data.unavailable_systems()) - menu::stack_push<menu_audit>(ui(), container(), m_persistent_data.sorted_list(), menu_audit::mode::FAST); - break; - - case IPT_UI_AUDIT_ALL: - menu::stack_push<menu_audit>(ui(), container(), m_persistent_data.sorted_list(), menu_audit::mode::ALL); + case IPT_UI_AUDIT: + menu::stack_push<menu_audit>(ui(), container()); break; } } } } - // if we're in an error state, overlay an error message - draw_error_text(); + return changed; } //------------------------------------------------- // populate //------------------------------------------------- -void menu_select_game::populate(float &customtop, float &custombottom) +void menu_select_game::populate() { for (auto &icon : m_icons) // TODO: why is this here? maybe better on resize or setting change? icon.second.texture.reset(); set_switch_image(); - int old_item_selected = -1; + bool have_prev_selected = false; + int old_item_selected = -1; if (!isfavorite()) { + if (m_populated_favorites) + m_prev_selected = nullptr; m_populated_favorites = false; m_displaylist.clear(); machine_filter const *const flt(m_persistent_data.filter_data().get_current_filter()); @@ -538,41 +332,46 @@ void menu_select_game::populate(float &customtop, float &custombottom) else { // if filter is set on category, build category list - std::vector<ui_system_info> const &sorted(m_persistent_data.sorted_list()); + auto const &sorted(m_persistent_data.sorted_list()); if (!flt) - std::copy(sorted.begin(), sorted.end(), std::back_inserter(m_displaylist)); + { + for (ui_system_info const &sysinfo : sorted) + m_displaylist.emplace_back(sysinfo); + } else - flt->apply(sorted.begin(), sorted.end(), std::back_inserter(m_displaylist)); + { + for (ui_system_info const &sysinfo : sorted) + { + if (flt->apply(sysinfo)) + m_displaylist.emplace_back(sysinfo); + } + } } // iterate over entries int curitem = 0; for (ui_system_info const &elem : m_displaylist) { - if (old_item_selected == -1 && elem.driver->name == reselect_last::driver()) + have_prev_selected = have_prev_selected || (&elem == m_prev_selected); + if ((old_item_selected == -1) && (elem.driver->name == reselect_last::driver())) old_item_selected = curitem; - bool cloneof = strcmp(elem.driver->parent, "0"); - if (cloneof) - { - int cx = driver_list::find(elem.driver->parent); - if (cx != -1 && ((driver_list::driver(cx).flags & machine_flags::IS_BIOS_ROOT) != 0)) - cloneof = false; - } - - item_append(elem.driver->type.fullname(), "", (cloneof) ? (FLAGS_UI | FLAG_INVERT) : FLAGS_UI, (void *)elem.driver); + item_append(elem.description, elem.is_clone ? FLAG_INVERT : 0, (void *)&elem); curitem++; } } else { // populate favorites list + if (!m_populated_favorites) + m_prev_selected = nullptr; m_populated_favorites = true; m_search.clear(); mame_machine_manager::instance()->favorite().apply_sorted( - [this, &old_item_selected, curitem = 0] (ui_software_info const &info) mutable + [this, &have_prev_selected, &old_item_selected, curitem = 0] (ui_software_info const &info) mutable { - if (info.startempty == 1) + have_prev_selected = have_prev_selected || (&info == m_prev_selected); + if (info.startempty) { if (old_item_selected == -1 && info.shortname == reselect_last::driver()) old_item_selected = curitem; @@ -580,53 +379,45 @@ void menu_select_game::populate(float &customtop, float &custombottom) bool cloneof = strcmp(info.driver->parent, "0"); if (cloneof) { - int cx = driver_list::find(info.driver->parent); - if (cx != -1 && ((driver_list::driver(cx).flags & machine_flags::IS_BIOS_ROOT) != 0)) + int const cx = driver_list::find(info.driver->parent); + if ((0 <= cx) && ((driver_list::driver(cx).flags & machine_flags::IS_BIOS_ROOT) != 0)) cloneof = false; } - item_append(info.longname, "", cloneof ? (FLAGS_UI | FLAG_INVERT) : FLAGS_UI, (void *)&info); + ui_system_info const &sysinfo = m_persistent_data.systems()[driver_list::find(info.driver->name)]; + item_append(sysinfo.description, cloneof ? FLAG_INVERT : 0, (void *)&info); } else { if (old_item_selected == -1 && info.shortname == reselect_last::driver()) old_item_selected = curitem; - item_append(info.longname, info.devicetype, - info.parentname.empty() ? FLAGS_UI : (FLAG_INVERT | FLAGS_UI), (void *)&info); + item_append(info.longname, info.devicetype, info.parentname.empty() ? 0 : FLAG_INVERT, (void *)&info); } curitem++; }); } - item_append(menu_item_type::SEPARATOR, FLAGS_UI); - // add special items if (stack_has_special_main_menu()) { - item_append(_("Configure Options"), "", FLAGS_UI, (void *)(uintptr_t)CONF_OPTS); - item_append(_("Configure Machine"), "", FLAGS_UI, (void *)(uintptr_t)CONF_MACHINE); - skip_main_items = 2; - if (machine().options().plugins()) - { - item_append(_("Plugins"), "", FLAGS_UI, (void *)(uintptr_t)CONF_PLUGINS); - skip_main_items++; - } + item_append(menu_item_type::SEPARATOR, 0); + item_append(_("General Settings"), 0, (void *)(uintptr_t)CONF_OPTS); + item_append(_("System Settings"), 0, (void *)(uintptr_t)CONF_MACHINE); + m_skip_main_items = 3; + + if (m_prev_selected && !have_prev_selected && (item_count() > 0)) + m_prev_selected = item(0).ref(); } else - skip_main_items = 0; - - // configure the custom rendering - customtop = 3.0f * ui().get_line_height() + 5.0f * ui().box_tb_border(); - custombottom = 5.0f * ui().get_line_height() + 3.0f * ui().box_tb_border(); + { + m_skip_main_items = 0; + } // reselect prior game launched, if any if (old_item_selected != -1) { set_selected_index(old_item_selected); - if (ui_globals::visible_main_lines == 0) - top_line = (selected_index() != 0) ? selected_index() - 1 : 0; - else - top_line = selected_index() - (ui_globals::visible_main_lines / 2); + centre_selection(); if (reselect_last::software().empty()) reselect_last::reset(); @@ -655,7 +446,7 @@ void menu_select_game::build_available_list() char const *src; // build a name for it - for (src = dir->name; *src != 0 && *src != '.' && dst < &drivername[ARRAY_LENGTH(drivername) - 1]; ++src) + for (src = dir->name; *src != 0 && *src != '.' && dst < &drivername[std::size(drivername) - 1]; ++src) *dst++ = tolower(uint8_t(*src)); *dst = 0; @@ -667,7 +458,7 @@ void menu_select_game::build_available_list() // now check and include NONE_NEEDED if (!ui().options().hide_romless()) { - // FIXME: can't use the convenience macros tiny ROM entries + // FIXME: can't use the convenience macros with tiny ROM entries auto const is_required_rom = [] (tiny_rom_entry const &rom) { return ROMENTRY_ISFILE(rom) && !ROM_ISOPTIONAL(rom) && !std::strchr(rom.hashdata, '!'); }; for (std::size_t x = 0; total > x; ++x) @@ -744,14 +535,9 @@ void menu_select_game::build_available_list() void menu_select_game::force_game_select(mame_ui_manager &mui, render_container &container) { - // reset the menu stack - menu::stack_reset(mui.machine()); - - // add the quit entry followed by the game select entry - menu::stack_push_special_main<menu_quit_game>(mui, container); - menu::stack_push<menu_select_game>(mui, container, nullptr); - - // force the menus on + // drop any existing menus and start the system selection menu + menu::stack_reset(mui); + menu::stack_push_special_main<menu_select_game>(mui, container, nullptr); mui.show_menu(); // make sure MAME is paused @@ -762,11 +548,11 @@ void menu_select_game::force_game_select(mame_ui_manager &mui, render_container // handle select key event //------------------------------------------------- -void menu_select_game::inkey_select(const event *menu_event) +bool menu_select_game::inkey_select(const event *menu_event) { - const game_driver *driver = (const game_driver *)menu_event->itemref; + auto const system = reinterpret_cast<ui_system_info const *>(menu_event->itemref); - if ((uintptr_t)driver == CONF_OPTS) + if (uintptr_t(system) == CONF_OPTS) { // special case for configure options menu::stack_push<menu_game_options>( @@ -774,48 +560,47 @@ void menu_select_game::inkey_select(const event *menu_event) container(), m_persistent_data.filter_data(), [this] () { reset(reset_options::SELECT_FIRST); }); + return false; } - else if (uintptr_t(driver) == CONF_MACHINE) + else if (uintptr_t(system) == CONF_MACHINE) { // special case for configure machine if (m_prev_selected) - menu::stack_push<menu_machine_configure>(ui(), container(), *reinterpret_cast<const game_driver *>(m_prev_selected)); - return; - } - else if ((uintptr_t)driver == CONF_PLUGINS) - { - // special case for configure plugins - menu::stack_push<menu_plugins_configure>(ui(), container()); + menu::stack_push<menu_machine_configure>(ui(), container(), *reinterpret_cast<const ui_system_info *>(m_prev_selected)); + return false; } else { // anything else is a driver - - // audit the game first to see if we're going to work - driver_enumerator enumerator(machine().options(), *driver); + driver_enumerator enumerator(machine().options(), *system->driver); enumerator.next(); - media_auditor auditor(enumerator); - media_auditor::summary const summary = auditor.audit_media(AUDIT_VALIDATE_FAST); - // if everything looks good, schedule the new driver - if (summary == media_auditor::CORRECT || summary == media_auditor::BEST_AVAILABLE || summary == media_auditor::NONE_NEEDED) + // if there are software entries, show a software selection menu + for (software_list_device &swlistdev : software_list_device_enumerator(enumerator.config()->root_device())) { - for (software_list_device &swlistdev : software_list_device_iterator(enumerator.config()->root_device())) + if (!swlistdev.get_info().empty()) { - if (!swlistdev.get_info().empty()) - { - menu::stack_push<menu_select_software>(ui(), container(), *driver); - return; - } + menu::stack_push<menu_select_software>(ui(), container(), *system); + return false; } + } + + // audit the system ROMs first to see if we're going to work + media_auditor auditor(enumerator); + media_auditor::summary const summary = auditor.audit_media(AUDIT_VALIDATE_FAST); - if (!select_bios(*driver, false)) - launch_system(*driver); + // if everything looks good, schedule the new driver + if (audit_passed(summary)) + { + if (!select_bios(*system->driver, false)) + launch_system(*system->driver); + return false; } else { // otherwise, display an error - set_error(reset_options::REMEMBER_REF, make_error_text(media_auditor::NOTFOUND != summary, auditor)); + set_error(reset_options::REMEMBER_REF, make_system_audit_fail_text(auditor, summary)); + return true; } } } @@ -824,7 +609,7 @@ void menu_select_game::inkey_select(const event *menu_event) // handle select key event for favorites menu //------------------------------------------------- -void menu_select_game::inkey_select_favorite(const event *menu_event) +bool menu_select_game::inkey_select_favorite(const event *menu_event) { ui_software_info *ui_swinfo = (ui_software_info *)menu_event->itemref; @@ -836,6 +621,7 @@ void menu_select_game::inkey_select_favorite(const event *menu_event) container(), m_persistent_data.filter_data(), [this] () { reset(reset_options::SELECT_FIRST); }); + return false; } else if ((uintptr_t)ui_swinfo == CONF_MACHINE) { @@ -843,75 +629,89 @@ void menu_select_game::inkey_select_favorite(const event *menu_event) if (m_prev_selected) { ui_software_info *swinfo = reinterpret_cast<ui_software_info *>(m_prev_selected); + ui_system_info const &sysinfo = m_persistent_data.systems()[driver_list::find(swinfo->driver->name)]; menu::stack_push<menu_machine_configure>( ui(), container(), - *swinfo->driver, + sysinfo, [this, empty = swinfo->startempty] (bool fav, bool changed) { if (changed) reset(empty ? reset_options::SELECT_FIRST : reset_options::REMEMBER_REF); }); } - return; - } - else if ((uintptr_t)ui_swinfo == CONF_PLUGINS) - { - // special case for configure plugins - menu::stack_push<menu_plugins_configure>(ui(), container()); + return false; } - else if (ui_swinfo->startempty == 1) + else if (ui_swinfo->startempty) { - // audit the game first to see if we're going to work driver_enumerator enumerator(machine().options(), *ui_swinfo->driver); enumerator.next(); - media_auditor auditor(enumerator); - media_auditor::summary const summary = auditor.audit_media(AUDIT_VALIDATE_FAST); - if (summary == media_auditor::CORRECT || summary == media_auditor::BEST_AVAILABLE || summary == media_auditor::NONE_NEEDED) + // if there are software entries, show a software selection menu + for (software_list_device &swlistdev : software_list_device_enumerator(enumerator.config()->root_device())) { - for (software_list_device &swlistdev : software_list_device_iterator(enumerator.config()->root_device())) + if (!swlistdev.get_info().empty()) { - if (!swlistdev.get_info().empty()) - { - menu::stack_push<menu_select_software>(ui(), container(), *ui_swinfo->driver); - return; - } + ui_system_info const &system(m_persistent_data.systems()[driver_list::find(ui_swinfo->driver->name)]); + menu::stack_push<menu_select_software>(ui(), container(), system); + return false; } + } + + // audit the system ROMs first to see if we're going to work + media_auditor auditor(enumerator); + media_auditor::summary const summary = auditor.audit_media(AUDIT_VALIDATE_FAST); + if (audit_passed(summary)) + { // if everything looks good, schedule the new driver if (!select_bios(*ui_swinfo->driver, false)) { reselect_last::reselect(true); launch_system(*ui_swinfo->driver); } + return false; } else { // otherwise, display an error - set_error(reset_options::REMEMBER_REF, make_error_text(media_auditor::NOTFOUND != summary, auditor)); + set_error(reset_options::REMEMBER_REF, make_system_audit_fail_text(auditor, summary)); + return true; } } else { - // first validate + // first audit the system ROMs driver_enumerator drv(machine().options(), *ui_swinfo->driver); media_auditor auditor(drv); drv.next(); - software_list_device *swlist = software_list_device::find_by_name(*drv.config(), ui_swinfo->listname.c_str()); - const software_info *swinfo = swlist->find(ui_swinfo->shortname.c_str()); - - media_auditor::summary const summary = auditor.audit_software(swlist->list_name(), swinfo, AUDIT_VALIDATE_FAST); - - if (summary == media_auditor::CORRECT || summary == media_auditor::BEST_AVAILABLE || summary == media_auditor::NONE_NEEDED) + media_auditor::summary const sysaudit = auditor.audit_media(AUDIT_VALIDATE_FAST); + if (!audit_passed(sysaudit)) { - if (!select_bios(*ui_swinfo, false) && !select_part(*swinfo, *ui_swinfo)) - launch_system(drv.driver(), *ui_swinfo, ui_swinfo->part); + set_error(reset_options::REMEMBER_REF, make_system_audit_fail_text(auditor, sysaudit)); + return true; } else { - // otherwise, display an error - set_error(reset_options::REMEMBER_POSITION, make_error_text(media_auditor::NOTFOUND != summary, auditor)); + // now audit the software + software_list_device *swlist = software_list_device::find_by_name(*drv.config(), ui_swinfo->listname); + const software_info *swinfo = swlist->find(ui_swinfo->shortname); + + media_auditor::summary const swaudit = auditor.audit_software(*swlist, *swinfo, AUDIT_VALIDATE_FAST); + + if (audit_passed(swaudit)) + { + reselect_last::reselect(true); + if (!select_bios(*ui_swinfo, false) && !select_part(*swinfo, *ui_swinfo)) + launch_system(drv.driver(), *ui_swinfo, ui_swinfo->part); + return false; + } + else + { + // otherwise, display an error + set_error(reset_options::REMEMBER_REF, make_software_audit_fail_text(auditor, swaudit)); + return true; + } } } } @@ -927,41 +727,6 @@ bool menu_select_game::isfavorite() const //------------------------------------------------- -// change what's displayed in the info box -//------------------------------------------------- - -void menu_select_game::change_info_pane(int delta) -{ - auto const cap_delta = [this, &delta] (uint8_t ¤t, uint8_t &total) - { - if ((0 > delta) && (-delta > current)) - delta = -int(unsigned(current)); - else if ((0 < delta) && ((current + unsigned(delta)) >= total)) - delta = int(unsigned(total - current - 1)); - if (delta) - { - current += delta; - m_topline_datsview = 0; - } - }; - game_driver const *drv; - ui_software_info const *soft; - get_selection(soft, drv); - if (!m_populated_favorites) - { - if (uintptr_t(drv) > skip_main_items) - cap_delta(ui_globals::curdats_view, ui_globals::curdats_total); - } - else if (uintptr_t(soft) > skip_main_items) - { - if (soft->startempty) - cap_delta(ui_globals::curdats_view, ui_globals::curdats_total); - else - cap_delta(ui_globals::cur_sw_dats_view, ui_globals::cur_sw_dats_total); - } -} - -//------------------------------------------------- // populate search list //------------------------------------------------- @@ -970,7 +735,7 @@ void menu_select_game::populate_search() // ensure search list is populated if (m_searchlist.empty()) { - std::vector<ui_system_info> const &sorted(m_persistent_data.sorted_list()); + auto const &sorted(m_persistent_data.sorted_list()); m_searchlist.reserve(sorted.size()); for (ui_system_info const &info : sorted) m_searchlist.emplace_back(1.0, std::ref(info)); @@ -979,39 +744,53 @@ void menu_select_game::populate_search() // keep track of what we matched against const std::u32string ucs_search(ustr_from_utf8(normalize_unicode(m_search, unicode_normalization_form::D, true))); - // match shortnames - if (m_persistent_data.is_available(persistent_data::AVAIL_UCS_SHORTNAME)) + // check available search data + if (m_persistent_data.is_available(system_list::AVAIL_UCS_SHORTNAME)) + m_searched_fields |= system_list::AVAIL_UCS_SHORTNAME; + if (m_persistent_data.is_available(system_list::AVAIL_UCS_DESCRIPTION)) + m_searched_fields |= system_list::AVAIL_UCS_DESCRIPTION; + if (m_persistent_data.is_available(system_list::AVAIL_UCS_MANUF_DESC)) + m_searched_fields |= system_list::AVAIL_UCS_MANUF_DESC; + if (m_persistent_data.is_available(system_list::AVAIL_UCS_DFLT_DESC)) + m_searched_fields |= system_list::AVAIL_UCS_DFLT_DESC; + if (m_persistent_data.is_available(system_list::AVAIL_UCS_MANUF_DFLT_DESC)) + m_searched_fields |= system_list::AVAIL_UCS_MANUF_DFLT_DESC; + + for (std::pair<double, std::reference_wrapper<ui_system_info const> > &info : m_searchlist) { - m_searched_fields |= persistent_data::AVAIL_UCS_SHORTNAME; - for (std::pair<double, std::reference_wrapper<ui_system_info const> > &info : m_searchlist) - info.first = util::edit_distance(ucs_search, info.second.get().ucs_shortname); - } + info.first = 1.0; + ui_system_info const &sys(info.second); - // match descriptions - if (m_persistent_data.is_available(persistent_data::AVAIL_UCS_DESCRIPTION)) - { - m_searched_fields |= persistent_data::AVAIL_UCS_DESCRIPTION; - for (std::pair<double, std::reference_wrapper<ui_system_info const> > &info : m_searchlist) + // match shortnames + if (m_searched_fields & system_list::AVAIL_UCS_SHORTNAME) + info.first = util::edit_distance(ucs_search, sys.ucs_shortname); + + // match reading + if (info.first && !sys.ucs_reading_description.empty()) { + info.first = (std::min)(util::edit_distance(ucs_search, sys.ucs_reading_description), info.first); + + // match "<manufacturer> <reading>" if (info.first) - { - double const penalty(util::edit_distance(ucs_search, info.second.get().ucs_description)); - info.first = (std::min)(penalty, info.first); - } + info.first = (std::min)(util::edit_distance(ucs_search, sys.ucs_manufacturer_reading_description), info.first); } - } - // match "<manufacturer> <description>" - if (m_persistent_data.is_available(persistent_data::AVAIL_UCS_MANUF_DESC)) - { - m_searched_fields |= persistent_data::AVAIL_UCS_MANUF_DESC; - for (std::pair<double, std::reference_wrapper<ui_system_info const> > &info : m_searchlist) + // match descriptions + if (info.first && (m_searched_fields & system_list::AVAIL_UCS_DESCRIPTION)) + info.first = (std::min)(util::edit_distance(ucs_search, sys.ucs_description), info.first); + + // match "<manufacturer> <description>" + if (info.first && (m_searched_fields & system_list::AVAIL_UCS_MANUF_DESC)) + info.first = (std::min)(util::edit_distance(ucs_search, sys.ucs_manufacturer_description), info.first); + + // match default description + if (info.first && (m_searched_fields & system_list::AVAIL_UCS_DFLT_DESC) && !sys.ucs_default_description.empty()) { - if (info.first) - { - double const penalty(util::edit_distance(ucs_search, info.second.get().ucs_manufacturer_description)); - info.first = (std::min)(penalty, info.first); - } + info.first = (std::min)(util::edit_distance(ucs_search, sys.ucs_default_description), info.first); + + // match "<manufacturer> <default description>" + if (info.first && (m_searched_fields & system_list::AVAIL_UCS_MANUF_DFLT_DESC)) + info.first = (std::min)(util::edit_distance(ucs_search, sys.ucs_manufacturer_default_description), info.first); } } @@ -1022,192 +801,6 @@ void menu_select_game::populate_search() [] (auto const &lhs, auto const &rhs) { return lhs.first < rhs.first; }); } -//------------------------------------------------- -// generate general info -//------------------------------------------------- - -void menu_select_game::general_info(const game_driver *driver, std::string &buffer) -{ - system_flags const &flags(get_system_flags(*driver)); - std::ostringstream str; - - str << "#j2\n"; - - util::stream_format(str, _("Romset\t%1$-.100s\n"), driver->name); - util::stream_format(str, _("Year\t%1$s\n"), driver->year); - util::stream_format(str, _("Manufacturer\t%1$-.100s\n"), driver->manufacturer); - - int cloneof = driver_list::non_bios_clone(*driver); - if (cloneof != -1) - util::stream_format(str, _("Driver is Clone of\t%1$-.100s\n"), driver_list::driver(cloneof).type.fullname()); - else - str << _("Driver is Parent\t\n"); - - if (flags.has_analog()) - str << _("Analog Controls\tYes\n"); - if (flags.has_keyboard()) - str << _("Keyboard Inputs\tYes\n"); - - 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 Machine\tYes\n") : _("Mechanical Machine\tNo\n")); - str << ((flags.machine_flags() & machine_flags::REQUIRES_ARTWORK) ? _("Requires Artwork\tYes\n") : _("Requires Artwork\tNo\n")); - str << ((flags.machine_flags() & machine_flags::CLICKABLE_ARTWORK) ? _("Requires Clickable Artwork\tYes\n") : _("Requires Clickable Artwork\tNo\n")); - str << ((flags.machine_flags() & machine_flags::NO_COCKTAIL) ? _("Support Cocktail\tYes\n") : _("Support Cocktail\tNo\n")); - str << ((flags.machine_flags() & machine_flags::IS_BIOS_ROOT) ? _("Driver is BIOS\tYes\n") : _("Driver 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()) - { - if (region.is_diskdata()) - { - found = true; - break; - } - } - str << (found ? _("Requires CHD\tYes\n") : _("Requires CHD\tNo\n")); - - // 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 (summary == media_auditor::CORRECT || summary == media_auditor::BEST_AVAILABLE || summary == media_auditor::NONE_NEEDED) - str << _("ROM Audit Result\tOK\n"); - else - str << _("ROM Audit Result\tBAD\n"); - - if (summary_samples == media_auditor::NONE_NEEDED) - str << _("Samples Audit Result\tNone Needed\n"); - else if (summary_samples == media_auditor::CORRECT || summary_samples == media_auditor::BEST_AVAILABLE) - str << _("Samples Audit Result\tOK\n"); - else - str << _("Samples Audit Result\tBAD\n"); - } - else - { - str << _("ROM Audit \tDisabled\nSamples Audit \tDisabled\n"); - } - - buffer = str.str(); -} - //------------------------------------------------- // get (possibly cached) icon texture @@ -1217,7 +810,7 @@ render_texture *menu_select_game::get_icon_texture(int linenum, void *selectedre { game_driver const *const driver(m_populated_favorites ? reinterpret_cast<ui_software_info const *>(selectedref)->driver - : reinterpret_cast<game_driver const *>(selectedref)); + : reinterpret_cast<ui_system_info const *>(selectedref)->driver); assert(driver); icon_cache::iterator icon(m_icons.find(driver)); @@ -1248,12 +841,12 @@ render_texture *menu_select_game::get_icon_texture(int linenum, void *selectedre bitmap_argb32 tmp; emu_file snapfile(std::string(m_icon_paths), OPEN_FLAG_READ); - if (snapfile.open(std::string(driver->name), ".ico") == osd_file::error::NONE) + if (!snapfile.open(std::string(driver->name) + ".ico")) { render_load_ico_highest_detail(snapfile, tmp); snapfile.close(); } - if (!tmp.valid() && cloneof && (snapfile.open(std::string(driver->parent), ".ico") == osd_file::error::NONE)) + if (!tmp.valid() && cloneof && !snapfile.open(std::string(driver->parent) + ".ico")) { render_load_ico_highest_detail(snapfile, tmp); snapfile.close(); @@ -1266,6 +859,10 @@ render_texture *menu_select_game::get_icon_texture(int linenum, void *selectedre } +//------------------------------------------------- +// export displayed list +//------------------------------------------------- + void menu_select_game::inkey_export() { std::vector<game_driver const *> list; @@ -1298,7 +895,7 @@ bool menu_select_game::load_available_machines() { // try to load available drivers from file emu_file file(ui().options().ui_path(), OPEN_FLAG_READ); - if (file.open(emulator_info::get_configname(), "_avail.ini") != osd_file::error::NONE) + if (file.open(std::string(emulator_info::get_configname()) + "_avail.ini")) return false; char rbuf[MAX_CHAR_INFO]; @@ -1319,8 +916,7 @@ bool menu_select_game::load_available_machines() std::unordered_set<std::string> available; while (file.gets(rbuf, MAX_CHAR_INFO)) { - readbuf = rbuf; - strtrimspace(readbuf); + readbuf = strtrimspace(rbuf); if (readbuf.empty() || ('#' == readbuf[0])) // ignore empty lines and line comments ; @@ -1351,7 +947,7 @@ bool menu_select_game::load_available_machines() void menu_select_game::load_custom_filters() { emu_file file(ui().options().ui_path(), OPEN_FLAG_READ); - if (file.open("custom_", emulator_info::get_configname(), "_filter.ini") == osd_file::error::NONE) + if (!file.open(util::string_format("custom_%s_filter.ini", emulator_info::get_configname()))) { machine_filter::ptr flt(machine_filter::create(file, m_persistent_data.filter_data())); if (flt) @@ -1366,10 +962,10 @@ void menu_select_game::load_custom_filters() // draw left box //------------------------------------------------- -float menu_select_game::draw_left_panel(float x1, float y1, float x2, float y2) +void menu_select_game::draw_left_panel(u32 flags) { machine_filter_data &filter_data(m_persistent_data.filter_data()); - return menu_select_launch::draw_left_panel<machine_filter>(filter_data.get_current_filter_type(), filter_data.get_filters(), x1, y1, x2, y2); + menu_select_launch::draw_left_panel<machine_filter>(flags, filter_data.get_current_filter_type(), filter_data.get_filters()); } @@ -1377,26 +973,52 @@ float menu_select_game::draw_left_panel(float x1, float y1, float x2, float y2) // get selected software and/or driver //------------------------------------------------- -void menu_select_game::get_selection(ui_software_info const *&software, game_driver const *&driver) const +void menu_select_game::get_selection(ui_software_info const *&software, ui_system_info const *&system) const { if (m_populated_favorites) { software = reinterpret_cast<ui_software_info const *>(get_selection_ptr()); - driver = software ? software->driver : nullptr; + system = software ? &m_persistent_data.systems()[driver_list::find(software->driver->name)] : nullptr; } else { software = nullptr; - driver = reinterpret_cast<game_driver const *>(get_selection_ptr()); + system = reinterpret_cast<ui_system_info const *>(get_selection_ptr()); + } +} + +void menu_select_game::show_config_menu(int index) +{ + if (!m_populated_favorites) + { + menu::stack_push<menu_machine_configure>( + ui(), + container(), + *reinterpret_cast<ui_system_info const *>(item(index).ref()), + nullptr); + } + else + { + ui_software_info *sw = reinterpret_cast<ui_software_info *>(item(index).ref()); + ui_system_info const &sys = m_persistent_data.systems()[driver_list::find(sw->driver->name)]; + menu::stack_push<menu_machine_configure>( + ui(), + container(), + sys, + [this, empty = sw->startempty] (bool fav, bool changed) + { + if (changed) + reset(empty ? reset_options::SELECT_FIRST : reset_options::REMEMBER_REF); + }); } } void menu_select_game::make_topbox_text(std::string &line0, std::string &line1, std::string &line2) const { - line0 = string_format(_("%1$s %2$s ( %3$d / %4$d machines (%5$d BIOS) )"), + line0 = string_format(_("%1$s %2$s ( %3$d / %4$d systems (%5$d BIOS) )"), emulator_info::get_appname(), bare_build_version, - visible_items, + m_available_items, (driver_list::total() - 1), m_persistent_data.bios_count()); @@ -1418,58 +1040,36 @@ void menu_select_game::make_topbox_text(std::string &line0, std::string &line1, } -std::string menu_select_game::make_driver_description(game_driver const &driver) const -{ - // first line is game name - return string_format(_("Romset: %1$-.100s"), driver.name); -} - - -std::string menu_select_game::make_software_description(ui_software_info const &software) const +std::string menu_select_game::make_software_description(ui_software_info const &software, ui_system_info const *system) const { // first line is system - return string_format(_("System: %1$-.100s"), software.driver->type.fullname()); + return string_format(_("System: %1$-.100s"), system->description); } -void menu_select_game::filter_selected() +void menu_select_game::filter_selected(int index) { - if ((machine_filter::FIRST <= m_filter_highlight) && (machine_filter::LAST >= m_filter_highlight)) - { - m_persistent_data.filter_data().get_filter(machine_filter::type(m_filter_highlight)).show_ui( - ui(), - container(), - [this] (machine_filter &filter) + assert((machine_filter::FIRST <= index) && (machine_filter::LAST >= index)); + + m_persistent_data.filter_data().get_filter(machine_filter::type(index)).show_ui( + ui(), + container(), + [this] (machine_filter &filter) + { + set_switch_image(); + machine_filter::type const new_type(filter.get_type()); + if (machine_filter::CUSTOM == new_type) { - set_switch_image(); - machine_filter::type const new_type(filter.get_type()); - if (machine_filter::CUSTOM == new_type) + emu_file file(ui().options().ui_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); + if (!file.open(util::string_format("custom_%s_filter.ini", emulator_info::get_configname()))) { - emu_file file(ui().options().ui_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); - if (file.open("custom_", emulator_info::get_configname(), "_filter.ini") == osd_file::error::NONE) - { - filter.save_ini(file, 0); - file.close(); - } + filter.save_ini(file, 0); + file.close(); } - m_persistent_data.filter_data().set_current_filter_type(new_type); - reset(reset_options::SELECT_FIRST); - }); - } -} - - -std::string menu_select_game::make_error_text(bool summary, media_auditor const &auditor) -{ - std::ostringstream str; - str << _("The selected machine is missing one or more required ROM or CHD images. Please select a different machine.\n\n"); - if (summary) - { - auditor.summarize(nullptr, &str); - str << "\n"; - } - str << _("Press any key to continue."); - return str.str(); + } + m_persistent_data.filter_data().set_current_filter_type(new_type); + reset(reset_options::REMEMBER_REF); + }); } } // namespace ui diff --git a/src/frontend/mame/ui/selgame.h b/src/frontend/mame/ui/selgame.h index da65fee98ce..8a9037a8864 100644 --- a/src/frontend/mame/ui/selgame.h +++ b/src/frontend/mame/ui/selgame.h @@ -18,10 +18,11 @@ #include <functional> -class media_auditor; - namespace ui { +class system_list; + + class menu_select_game : public menu_select_launch { public: @@ -31,19 +32,22 @@ public: // force game select menu static void force_game_select(mame_ui_manager &mui, render_container &container); +protected: + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + + void menu_activated() override; + void menu_deactivated() override; + private: enum { CONF_OPTS = 1, CONF_MACHINE, - CONF_PLUGINS, }; using icon_cache = texture_lru<game_driver const *>; - class persistent_data; - - persistent_data &m_persistent_data; + system_list &m_persistent_data; icon_cache m_icons; std::string m_icon_paths; std::vector<std::reference_wrapper<ui_system_info const> > m_displaylist; @@ -54,31 +58,28 @@ private: static bool s_first_start; - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; + virtual bool handle(event const *ev) override; // drawing - virtual float draw_left_panel(float x1, float y1, float x2, float y2) override; + virtual void draw_left_panel(u32 flags) override; virtual render_texture *get_icon_texture(int linenum, void *selectedref) override; // get selected software and/or driver - virtual void get_selection(ui_software_info const *&software, game_driver const *&driver) const override; + virtual void get_selection(ui_software_info const *&software, ui_system_info const *&system) const override; + virtual void show_config_menu(int index) override; virtual bool accept_search() const override { return !isfavorite(); } // text for main top/bottom panels virtual void make_topbox_text(std::string &line0, std::string &line1, std::string &line2) const override; - virtual std::string make_driver_description(game_driver const &driver) const override; - virtual std::string make_software_description(ui_software_info const &software) const override; + virtual std::string make_software_description(ui_software_info const &software, ui_system_info const *system) const override; // filter navigation - virtual void filter_selected() override; + virtual void filter_selected(int index) override; // toolbar virtual void inkey_export() override; - // internal methods - void change_info_pane(int delta); - void build_available_list(); bool isfavorite() const; @@ -86,14 +87,9 @@ private: bool load_available_machines(); void load_custom_filters(); - static std::string make_error_text(bool summary, media_auditor const &auditor); - - // General info - virtual void general_info(const game_driver *driver, std::string &buffer) override; - // handlers - void inkey_select(const event *menu_event); - void inkey_select_favorite(const event *menu_event); + bool inkey_select(const event *menu_event); + bool inkey_select_favorite(const event *menu_event); }; } // namespace ui diff --git a/src/frontend/mame/ui/selmenu.cpp b/src/frontend/mame/ui/selmenu.cpp index 1833f61528c..a9d4c9e9bed 100644 --- a/src/frontend/mame/ui/selmenu.cpp +++ b/src/frontend/mame/ui/selmenu.cpp @@ -15,17 +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" @@ -34,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, @@ -63,39 +73,107 @@ enum SCORES_VIEW, SELECT_VIEW, MARQUEES_VIEW, - LAST_VIEW = MARQUEES_VIEW + COVERS_VIEW, + 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"), }; -} // anonymous namespace +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 } +}; -constexpr std::size_t menu_select_launch::MAX_VISIBLE_SEARCH; // stupid non-inline semantics + +void load_image(bitmap_argb32 &bitmap, emu_file &file, std::string const &base) +{ + if (!file.open(base + ".png")) + { + render_load_png(bitmap, file); + file.close(); + } + + if (!bitmap.valid() && !file.open(base + ".jpg")) + { + render_load_jpeg(bitmap, file); + file.close(); + } + + if (!bitmap.valid() && !file.open(base + ".bmp")) + { + render_load_msdib(bitmap, file); + file.close(); + } +} + + +void load_driver_image(bitmap_argb32 &bitmap, emu_file &file, game_driver const &driver) +{ + // try to load snapshot first from saved "0000.png" file + std::string fullname = driver.name; + load_image(bitmap, file, util::path_concat(fullname, "0000")); + + // if fail, attempt to load from standard file + if (!bitmap.valid()) + load_image(bitmap, file, fullname); + + // if fail again, attempt to load from parent file + if (!bitmap.valid()) + { + // ignore BIOS sets + bool isclone = strcmp(driver.parent, "0") != 0; + if (isclone) + { + int const cx = driver_list::find(driver.parent); + if ((0 <= cx) && (driver_list::driver(cx).flags & machine_flags::IS_BIOS_ROOT)) + isclone = false; + } + + if (isclone) + { + fullname = driver.parent; + load_image(bitmap, file, util::path_concat(fullname, "0000")); + + if (!bitmap.valid()) + load_image(bitmap, file, fullname); + } + } +} + +} // anonymous namespace class menu_select_launch::software_parts : public menu @@ -104,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; @@ -122,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; @@ -141,18 +213,16 @@ 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) : m_machine_flags(info.machine_flags()) + , m_emulation_flags(info.emulation_flags()) , m_unemulated_features(info.unemulated_features()) , m_imperfect_features(info.imperfect_features()) , m_has_keyboard(info.has_keyboard()) @@ -203,6 +273,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")); } //------------------------------------------------- @@ -217,53 +288,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; } @@ -288,6 +344,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")); } //------------------------------------------------- @@ -302,28 +359,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) - item_append(elem.first, "", 0, (void *)&elem.first); + 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) { @@ -345,8 +399,8 @@ void menu_select_launch::bios_selection::handle() machine().options().set_value(OPTION_BIOS, elem.second, OPTION_PRIORITY_CMDLINE); // oh dear, relying on this persisting through the part selection menu driver_enumerator drivlist(machine().options(), *ui_swinfo->driver); drivlist.next(); - software_list_device *swlist = software_list_device::find_by_name(*drivlist.config(), ui_swinfo->listname.c_str()); - const software_info *swinfo = swlist->find(ui_swinfo->shortname.c_str()); + software_list_device *swlist = software_list_device::find_by_name(*drivlist.config(), ui_swinfo->listname); + const software_info *swinfo = swlist->find(ui_swinfo->shortname); if (!select_part(ui(), container(), *swinfo, *ui_swinfo)) { reselect_last::reselect(true); @@ -356,20 +410,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; } @@ -379,63 +421,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.pix32(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.pix32(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().pix32(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().pix32(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() { } @@ -443,6 +508,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) @@ -453,56 +519,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); } @@ -562,7 +731,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); } @@ -576,7 +745,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); } @@ -584,52 +798,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(uint32_t 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; @@ -639,111 +853,156 @@ 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); + game_driver const &driver(system ? *system->driver : *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); - - // 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)); - if (flags.machine_flags() & machine_flags::NOT_WORKING) - tempbuf[3] = _("Overall: NOT WORKING"); + system_flags const &flags(get_system_flags(driver)); + if (flags.emulation_flags() & device_t::flags::NOT_WORKING) + tempbuf[2] = _("Status: NOT WORKING"); else if ((flags.unemulated_features() | flags.imperfect_features()) & device_t::feature::PROTECTION) - tempbuf[3] = _("Overall: Unemulated Protection"); + tempbuf[2] = _("Status: Unemulated Protection"); else - tempbuf[3] = _("Overall: Working"); + tempbuf[2] = _("Status: 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); + + // first line is version string + tempbuf[line++] = string_format("%s %s", emulator_info::get_appname(), build_version); - 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(); + // 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(), origy2 + (2.0f * ui().box_tb_border())); + draw_star(origx1 + lr_border(), origy2 + (2.0f * tb_border())); } -void menu_select_launch::inkey_navigation() +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); +} + + +void menu_select_launch::rotate_focus(int dir) { switch (get_focus()) { case focused_menu::MAIN: - if (selected_index() <= visible_items) + if (selected_index() >= m_available_items) { - m_prev_selected = get_selection_ref(); - set_selected_index(visible_items + 1); + if ((m_panels_status == HIDE_BOTH) || ((0 > dir) && m_available_items)) + select_prev(); + else if (0 > dir) + set_focus((m_panels_status == HIDE_RIGHT_PANEL) ? focused_menu::LEFT : focused_menu::RIGHTBOTTOM); + else + set_focus((m_panels_status == HIDE_LEFT_PANEL) ? focused_menu::RIGHTTOP : focused_menu::LEFT); } - else + else if (m_skip_main_items || (m_panels_status != HIDE_BOTH)) { - if (ui_globals::panels_status != HIDE_LEFT_PANEL) + 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 (ui_globals::panels_status == HIDE_BOTH) + } + else if (m_panels_status != HIDE_BOTH) { - for (int x = 0; x < item_count(); ++x) - if (item(x).ref == m_prev_selected) - set_selected_index(x); + set_focus(focused_menu::RIGHTBOTTOM); } else { - set_focus(focused_menu::RIGHTTOP); + set_selected_index(m_available_items + 1); } } break; case focused_menu::LEFT: - if (ui_globals::panels_status != HIDE_RIGHT_PANEL) + if (0 > dir) + { + set_focus(focused_menu::MAIN); + if (m_skip_main_items) + set_selected_index(m_available_items + 1); + else + select_prev(); + } + else if (m_panels_status != HIDE_RIGHT_PANEL) { set_focus(focused_menu::RIGHTTOP); } @@ -755,12 +1014,34 @@ void menu_select_launch::inkey_navigation() break; case focused_menu::RIGHTTOP: - set_focus(focused_menu::RIGHTBOTTOM); + if (0 < dir) + { + set_focus(focused_menu::RIGHTBOTTOM); + } + else if (m_panels_status != HIDE_LEFT_PANEL) + { + set_focus(focused_menu::LEFT); + } + else + { + set_focus(focused_menu::MAIN); + if (m_skip_main_items) + set_selected_index(m_available_items + 1); + else + select_prev(); + } break; case focused_menu::RIGHTBOTTOM: - set_focus(focused_menu::MAIN); - select_prev(); + if (0 > dir) + { + set_focus(focused_menu::RIGHTTOP); + } + else + { + set_focus(focused_menu::MAIN); + select_prev(); + } break; } } @@ -769,225 +1050,221 @@ void menu_select_launch::inkey_navigation() 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(); - 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.c_str(), 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(tmp); - float const text_sign = ui().get_string_width(tmp.c_str(), 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); + + 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); - // 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); + // 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); - x2 = x1 + left_width + 2.0f * ui().box_lr_border();; - ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color()); - x1 += ui().box_lr_border(); - x2 -= ui().box_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 = std::string("_> "); - convert_command_glyph(str); - } - 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.c_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 + ui().box_lr_border(); - x2 = x1 + 2.0f * ui().box_lr_border(); - y1 = origy1; - y2 = origy2; - float const space = x2 - x1; - float const lr_arrow_width = 0.4f * space * machine().render().ui_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 + ui().box_lr_border(); + // draw the divider + draw_divider(flags, lr_border() + m_left_panel_width, false); } @@ -1008,12 +1285,8 @@ 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); - } - osd_printf_verbose("Checking for icons in directory %s\n", current.c_str()); + util::path_append(current, listname); + osd_printf_verbose("Checking for icons in directory %s\n", current); // open and walk the directory osd::directory::ptr const dir(osd::directory::open(current)); @@ -1057,11 +1330,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()) @@ -1093,24 +1362,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()); - 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 + // scale the source bitmap bitmap_argb32 tmp; - float const ratio((std::min)({ float(max_height) / src.height(), float(max_width) / src.width(), 1.0F })); - if (1.0F > ratio) + float const ratio((std::min)(float(m_icon_height) / src.height(), float(m_icon_width) / src.width())); + if ((1.0F > ratio) || (1.2F < 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 @@ -1119,10 +1378,10 @@ 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.pix32(y, x) = tmp.pix32(y, x); + dst.bitmap.pix(y, x) = tmp.pix(y, x); dst.texture->set_bitmap(dst.bitmap, dst.bitmap.cliprect(), TEXFORMAT_ARGB32); return true; } @@ -1175,44 +1434,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 - x1 += ui().box_lr_border(); - x2 -= ui().box_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) * container().manager().ui_aspect(&container()); - 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)); } } @@ -1223,16 +1525,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().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); + } } @@ -1245,9 +1547,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)); }; } @@ -1257,29 +1558,28 @@ 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); - path_iterator path(tmp.c_str()); - path_iterator path_iter(addpath.c_str()); + path_iterator path(tmp); + path_iterator path_iter(addpath); std::string c_path, curpath; // iterate over path and add path for zipped formats @@ -1289,20 +1589,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 (have_pointer() && !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)) @@ -1313,15 +1897,24 @@ 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)) + return false; + } + + if (exclusive_input_pressed(iptkey, IPT_UI_BACK, 0)) + { + if (m_ui_error) { - iptkey = IPT_UI_CANCEL; + // 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; } - return; } if (exclusive_input_pressed(iptkey, IPT_UI_CANCEL, 0)) @@ -1330,122 +1923,217 @@ void menu_select_launch::handle_keys(uint32_t flags, int &iptkey) { // dismiss error } - else if (menu_has_search_active()) + else 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; + if (m_ui_error) + { + // dismiss error + return false; + } + else if (m_focus == focused_menu::RIGHTTOP) + { + // 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; + if (m_ui_error) + { + // dismiss error + return false; + } + else if (m_focus == focused_menu::RIGHTTOP) + { + // 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) + { + // swallow it + iptkey = IPT_INVALID; + if (m_filter_highlight) + { + --m_filter_highlight; + updated = true; + } + } + else if ((m_focus == focused_menu::RIGHTTOP) || (m_focus == focused_menu::RIGHTBOTTOM)) { - return; + // swallow it + iptkey = IPT_INVALID; + if (m_topline_datsview) + { + m_topline_datsview--; + updated = true; + } } - else if (!rightclose && m_focus == focused_menu::RIGHTBOTTOM) + else if (selected_index() == m_available_items + 1 || is_first_selected() || m_ui_error) { - m_topline_datsview--; - return; + return updated; } - else if (selected_index() == visible_items + 1 || is_first_selected() || m_ui_error) + else { - return; + 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() == visible_items - 1 || m_ui_error) + 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() < visible_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)); - top_line -= m_visible_items - (top_line + m_visible_lines == visible_items); + top_line -= m_visible_items - (top_line + m_visible_lines == m_available_items); } } // 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() < visible_items && !m_ui_error) + else if ((m_focus == focused_menu::RIGHTTOP) || (m_focus == focused_menu::RIGHTBOTTOM)) { - set_selected_index(std::min(selected_index() + m_visible_lines - 2 + (selected_index() == 0), visible_items - 1)); + // 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)); top_line += m_visible_lines - 2; } @@ -1454,388 +2142,1133 @@ 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() < visible_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; } + else if (selected_index() < m_available_items && !m_ui_error) + { + set_selected_index(top_line = m_available_items - 1); + } + } - if (selected_index() < visible_items && !m_ui_error) - set_selected_index(top_line = visible_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; + } } - // pause enables/disables pause - if (!m_ui_error && !ignorepause && exclusive_input_pressed(iptkey, IPT_UI_PAUSE, 0)) + // focus next rotates throw targets forward + if (exclusive_input_pressed(iptkey, IPT_UI_FOCUS_PREV, 12)) { - if (machine().paused()) - machine().resume(); - else - machine().pause(); + if (!m_ui_error) + { + rotate_focus(-1); + 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) { for (int code = IPT_UI_FIRST + 1; code < IPT_UI_LAST; code++) { - if (m_ui_error || code == IPT_UI_CONFIGURE || (code == IPT_UI_LEFT && ignoreleft) || (code == IPT_UI_RIGHT && ignoreright) || (code == IPT_UI_PAUSE && ignorepause)) + if (m_ui_error) continue; + switch (code) + { + case IPT_UI_FOCUS_NEXT: + case IPT_UI_FOCUS_PREV: + case IPT_UI_PAUSE: + 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::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()) + // 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 (hover() >= visible_items - 1 && selected_index() < visible_items) - m_prev_selected = get_selection_ref(); - set_selected_index(hover()); - m_focus = focused_menu::MAIN; + auto const [updated, notend] = previous_right_panel_view(); + if (notend) + { + 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; + } + return std::make_tuple(IPT_INVALID, true, updated); } - else if (hover() == HOVER_ARROW_UP) + else if ((x >= (m_right_content_hbounds.second - lr_border() - lr_arrow_width())) && (x < (m_right_content_hbounds.second - lr_border()))) { - set_selected_index(std::max(selected_index() - m_visible_items, 0)); - top_line -= m_visible_items - (top_line + m_visible_lines == visible_items); - set_pressed(); + auto const [updated, notend] = next_right_panel_view(); + if (notend) + { + 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); } - else if (hover() == HOVER_ARROW_DOWN) + } + + // 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) { - set_selected_index(std::min(selected_index() + m_visible_lines - 2 + (selected_index() == 0), visible_items - 1)); - top_line += m_visible_lines - 2; - set_pressed(); + m_clicked_line = m_image_view; } - 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) + else if (right_panel() == RP_INFOS) { - 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; + 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_RPANEL_ARROW) + 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) { - 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; + 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_FAV) + } + } + } + + // 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) { - ev.iptkey = IPT_UI_FAVORITES; - stop = true; + m_prev_selected = get_selection_ref(); + set_selected_index(m_available_items + 1); } - else if (hover() == HOVER_B_EXPORT) + } + 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) { - inkey_export(); - stop = true; + --top_line; + if (main_at_top()) + m_pointer_action = pointer_action::NONE; } - else if (hover() == HOVER_B_DATS) + else { - inkey_dats(); - stop = true; + ++top_line; + if (main_at_bottom()) + m_pointer_action = pointer_action::NONE; } - else if (hover() >= HOVER_RP_FIRST && hover() <= HOVER_RP_LAST) + if (main_force_visible_selection()) + key = IPT_CUSTOM; // stop processing events so the info pane can be rebuilt + redraw = true; + } + } + } + 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) + { + m_pointer_action = pointer_action::MAIN_DRAG; + m_last_pointer = m_base_pointer; + m_clicked_line = top_line; + return update_main_drag(changed, uievt); + } + } + + // 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))) + { + if (m_clicked_line < m_visible_lines) { - ui_globals::rpanel = (HOVER_RP_FIRST - hover()) * (-1); - stop = true; + // 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 (hover() >= HOVER_FILTER_FIRST && hover() <= HOVER_FILTER_LAST) + else if ((m_clicked_line >= m_primary_lines) && (m_clicked_line < (m_primary_lines + m_skip_main_items))) { - m_prev_selected = nullptr; - m_filter_highlight = hover() - HOVER_FILTER_FIRST; - filter_selected(); - stop = true; + // 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; + } } } - break; + return std::make_tuple(key, false, true); + } + } - // if we are hovering over a valid item, fake a UI_SELECT with a double-click - case ui_event::MOUSE_DOUBLE_CLICK: - if (hover() >= 0 && hover() < item_count()) - { - set_selected_index(hover()); - ev.iptkey = IPT_UI_SELECT; - } + // 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); +} - if (is_last_selected()) - { - ev.iptkey = IPT_UI_CANCEL; - stack_pop(); - } - stop = true; - break; - // caught scroll event - case ui_event::MOUSE_WHEEL: - if (hover() >= 0 && hover() < item_count() - skip_main_items - 1) +//------------------------------------------------- +// 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() >= visible_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() >= visible_items - 1 || m_ui_error) - break; - set_selected_index(std::min(selected_index() + local_menu_event.num_lines, visible_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::IME_CHAR: - if (exclusive_input_pressed(ev.iptkey, IPT_UI_CONFIGURE, 0)) + else if (v) { - ev.iptkey = IPT_UI_CONFIGURE; - 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::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::MOUSE_DOWN: - case ui_event::MOUSE_RDOWN: - case ui_event::MOUSE_DOUBLE_CLICK: - case ui_event::MOUSE_WHEEL: - stop = true; - break; - case ui_event::NONE: - case ui_event::MOUSE_MOVE: - case ui_event::MOUSE_LEAVE: - case ui_event::MOUSE_UP: - case ui_event::MOUSE_RUP: - case ui_event::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 line_height = ui().get_line_height(); - float const ud_arrow_width = line_height * machine().render().ui_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 * ui().box_lr_border() : 0.3f; - float visible_width = 1.0f - 4.0f * ui().box_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 if (m_prev_selected) + { + 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(); - visible_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 - ui().box_lr_border(); - float y1 = visible_top - ui().box_tb_border(); - float x2 = x1 + 2.0f * ui().box_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 * ui().box_lr_border(); - - // compute and add main box - x1 = visible_left - ui().box_lr_border(); - x2 = visible_left + visible_width + ui().box_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 (visible_items < m_visible_lines) - m_visible_lines = visible_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() < visible_items && top_line + m_visible_lines >= visible_items) - top_line = visible_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; - - if ((m_focus == focused_menu::MAIN) && (selected_index() < visible_items)) + } + 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, visible_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 char *itemtext = pitem.text.c_str(); + 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); @@ -1843,167 +3276,180 @@ 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); + // 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 (linenum == m_visible_lines - 1 && itemnum != visible_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); - } - 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; - const char *subitem_text = pitem.subtext.c_str(); - 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.c_str()), - 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 = visible_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 char *itemtext = pitem.text.c_str(); - 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) + 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 (is_selectable(pitem)) { - fgcolor = ui().options().mouseover_color(); - bgcolor = ui().colors().mouseover_bg_color(); - highlight(line_x0, line_y0, line_x1, line_y1, bgcolor); + bool const hovered(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 (pointerline && hovered) + { + // 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(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 - ui().box_lr_border(); - x2 = primary_left + primary_width + ui().box_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 != visible_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); } @@ -2011,114 +3457,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) { - 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())); - float const space(x2 - origx1); - float const lr_arrow_width(0.4f * space * machine().render().ui_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.c_str()) + 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)) { - if (ui_globals::rpanel != cells) + // 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) + { + 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)); + } +} - ui().draw_text_full(container(), buffer[cells].c_str(), 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; + +//------------------------------------------------- +// 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)); } - return (y1 + line_height + UI_LINE_WIDTH); + 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); + } + + 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); + } } @@ -2126,312 +3655,289 @@ 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) { // Load driver snapshot - std::string fullname = std::string(software->driver->name) + ".png"; - render_load_png(tmp_bitmap, snapfile, nullptr, fullname.c_str()); - - if (!tmp_bitmap.valid()) - { - fullname.assign(software->driver->name).append(".jpg"); - render_load_jpeg(tmp_bitmap, snapfile, nullptr, fullname.c_str()); - } + load_driver_image(tmp_bitmap, snapfile, *software->driver); } else { // First attempt from name list - std::string pathname = software->listname; - std::string fullname = software->shortname + ".png"; - render_load_png(tmp_bitmap, snapfile, pathname.c_str(), fullname.c_str()); + load_image(tmp_bitmap, snapfile, util::path_concat(software->listname, software->shortname)); + // Second attempt from driver name + part name if (!tmp_bitmap.valid()) - { - fullname.assign(software->shortname).append(".jpg"); - render_load_jpeg(tmp_bitmap, snapfile, pathname.c_str(), fullname.c_str()); - } - - if (!tmp_bitmap.valid()) - { - // Second attempt from driver name + part name - pathname.assign(software->driver->name).append(software->part); - fullname.assign(software->shortname).append(".png"); - render_load_png(tmp_bitmap, snapfile, pathname.c_str(), fullname.c_str()); - - if (!tmp_bitmap.valid()) - { - fullname.assign(software->shortname).append(".jpg"); - render_load_jpeg(tmp_bitmap, snapfile, pathname.c_str(), fullname.c_str()); - } - } + 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.c_str(), OPEN_FLAG_READ); - snapfile.set_restrict_to_mediapath(true); + emu_file snapfile(get_arts_searchpath(), OPEN_FLAG_READ); bitmap_argb32 tmp_bitmap; + load_driver_image(tmp_bitmap, snapfile, *system->driver); - // try to load snapshot first from saved "0000.png" file - std::string fullname(driver->name); - render_load_png(tmp_bitmap, snapfile, fullname.c_str(), "0000.png"); - - if (!tmp_bitmap.valid()) - render_load_jpeg(tmp_bitmap, snapfile, fullname.c_str(), "0000.jpg"); - - // if fail, attemp to load from standard file - if (!tmp_bitmap.valid()) - { - fullname.assign(driver->name).append(".png"); - render_load_png(tmp_bitmap, snapfile, nullptr, fullname.c_str()); - - if (!tmp_bitmap.valid()) - { - fullname.assign(driver->name).append(".jpg"); - render_load_jpeg(tmp_bitmap, snapfile, nullptr, fullname.c_str()); - } - } - - // if fail again, attemp to load from parent file - if (!tmp_bitmap.valid()) - { - // set clone status - bool cloneof = strcmp(driver->parent, "0"); - if (cloneof) - { - int cx = driver_list::find(driver->parent); - if ((cx >= 0) && (driver_list::driver(cx).flags & machine_flags::IS_BIOS_ROOT)) - cloneof = false; - } - - if (cloneof) - { - fullname.assign(driver->parent).append(".png"); - render_load_png(tmp_bitmap, snapfile, nullptr, fullname.c_str()); - - if (!tmp_bitmap.valid()) - { - fullname.assign(driver->parent).append(".jpg"); - render_load_jpeg(tmp_bitmap, snapfile, nullptr, fullname.c_str()); - } - } - } - - 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); + draw_snapx(); } } //------------------------------------------------- -// common function for images render +// perform rendering of image //------------------------------------------------- -std::string menu_select_launch::arts_render_common(float origx1, float origy1, float origx2, float origy2) +void menu_select_launch::arts_render_images(bitmap_argb32 &&tmp_bitmap) { - float const line_height = ui().get_line_height(); - float const gutter_width = 0.4f * line_height * machine().render().ui_aspect() * 1.3f; + // if it fails, use the default image + bool no_available(!tmp_bitmap.valid()); + if (no_available) + { + tmp_bitmap.allocate(256, 256); + 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); + } + } - std::string snaptext, searchstr; - get_title_search(snaptext, searchstr); + bitmap_argb32 &snapx_bitmap(m_cache.snapx_bitmap()); + if (!tmp_bitmap.valid()) + { + snapx_bitmap.reset(); + return; + } + + 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); - // apply title to right panel - float title_size = 0.0f; - for (int x = FIRST_VIEW; x < LAST_VIEW; x++) + auto [screen_width, screen_height] = target_size(); + if (machine().render().ui_target().orientation() & ORIENTATION_SWAP_XY) { - 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); + using std::swap; + swap(screen_height, screen_width); } - 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; + int const panel_width_pixel(panel_width * screen_width); + int const panel_height_pixel(panel_height * screen_height); - // 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; + // 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())); - if (bgcolor != ui().colors().text_bg_color()) + 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)) { - 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)); + // force 4:3 ratio min + 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 + float const ratio(std::min(ratioW, ratioH)); + dest_xPixel = tmp_bitmap.width() * ratio; + dest_yPixel = tmp_bitmap.height() * ratio; } - ui().draw_text_full(container(), - snaptext.c_str(), 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); + // 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 + { + dest_bitmap = std::move(tmp_bitmap); + } - return searchstr; + 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); } //------------------------------------------------- -// perform rendering of image +// draw snapshot //------------------------------------------------- -void menu_select_launch::arts_render_images(bitmap_argb32 &&tmp_bitmap, float origx1, float origy1, float origx2, float origy2) +void menu_select_launch::draw_snapx() { - bool no_available = false; - float line_height = ui().get_line_height(); - - // if it fails, use the default image - if (!tmp_bitmap.valid()) + // if the image is available, loaded and valid, display it + if (snapx_valid()) { - tmp_bitmap.allocate(256, 256); - 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.pix32(y, x) = src.pix32(y, x); - } - no_available = true; + 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)); } +} - 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(); - - if (machine().render().ui_target().orientation() & ORIENTATION_SWAP_XY) - std::swap(screen_height, screen_width); - int panel_width_pixel = panel_width * screen_width; - int panel_height_pixel = panel_height * screen_height; +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; +} - // 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(); +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]); +} - // 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; - } - // 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; - } +void menu_select_launch::set_right_panel(u8 index) +{ + assert(std::size(RIGHT_PANEL_NAMES) > index); + m_right_panel = index; +} - bitmap_argb32 dest_bitmap; +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(); + } +} - // 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); +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); +} - 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); +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); +} - for (int x = 0; x < dest_xPixel; x++) - for (int y = 0; y < dest_yPixel; y++) - snapx_bitmap.pix32(y + y1, x + x1) = dest_bitmap.pix32(y, x); - // apply bitmap - m_cache->snapx_texture()->set_bitmap(snapx_bitmap, snapx_bitmap.cliprect(), TEXFORMAT_ARGB32); - } - else +std::string menu_select_launch::make_system_audit_fail_text(media_auditor const &auditor, media_auditor::summary summary) +{ + std::ostringstream str; + if (!auditor.records().empty()) { - snapx_bitmap.reset(); + str << "System media audit failed:\n"; + auditor.summarize(nullptr, &str); + osd_printf_info(str.str()); + str.str(""); } + str << _("Required ROM/disk images for the selected system are missing or incorrect. Please acquire the correct files or select a different system.\n\n"); + make_audit_fail_text(str, auditor, summary); + return str.str(); } -//------------------------------------------------- -// draw snapshot -//------------------------------------------------- - -void menu_select_launch::draw_snapx(float origx1, float origy1, float origx2, float origy2) +std::string menu_select_launch::make_software_audit_fail_text(media_auditor const &auditor, media_auditor::summary summary) { - // if the image is available, loaded and valid, display it - if (snapx_valid()) + std::ostringstream str; + if (!auditor.records().empty()) { - 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; + str << "System media audit failed:\n"; + auditor.summarize(nullptr, &str); + osd_printf_info(str.str()); + str.str(""); + } + str << _("Required ROM/disk images for the selected software are missing or incorrect. Please acquire the correct files or select a different software item.\n\n"); + make_audit_fail_text(str, auditor, summary); + return str.str(); +} - // apply texture - container().add_quad(x1, y1, x2, y2, rgb_t::white(), m_cache->snapx_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + +void menu_select_launch::make_audit_fail_text(std::ostream &str, media_auditor const &auditor, media_auditor::summary summary) +{ + if ((media_auditor::NOTFOUND != summary) && !auditor.records().empty()) + { + char const *message = nullptr; + for (media_auditor::audit_record const &record : auditor.records()) + { + switch (record.substatus()) + { + case media_auditor::audit_substatus::FOUND_BAD_CHECKSUM: + message = _("incorrect checksum"); + break; + case media_auditor::audit_substatus::FOUND_WRONG_LENGTH: + message = _("incorrect length"); + break; + case media_auditor::audit_substatus::NOT_FOUND: + message = _("not found"); + break; + case media_auditor::audit_substatus::GOOD: + case media_auditor::audit_substatus::GOOD_NEEDS_REDUMP: + case media_auditor::audit_substatus::FOUND_NODUMP: + case media_auditor::audit_substatus::NOT_FOUND_NODUMP: + case media_auditor::audit_substatus::NOT_FOUND_OPTIONAL: + case media_auditor::audit_substatus::UNVERIFIED: + continue; + } + if (record.shared_device()) + util::stream_format(str, _("%1$s (%2$s) - %3$s\n"), record.name(), record.shared_device()->shortname(), message); + else + util::stream_format(str, _("%1$s - %2$s\n"), record.name(), message); + } + str << '\n'; } + str << _("Press any key to continue."); } @@ -2459,7 +3965,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)) { @@ -2475,67 +3981,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 space = x2 - x1; - float const lr_arrow_width = 0.4f * space * machine().render().ui_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(); -} - - //------------------------------------------------- // 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; @@ -2553,7 +4019,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 { @@ -2563,34 +4029,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); } } @@ -2601,162 +4071,246 @@ void menu_select_launch::infos_render(float origx1, float origy1, float origx2, return; } - origy1 += ui().box_tb_border(); - float gutter_width = 0.4f * line_height * machine().render().ui_aspect() * 1.3f; - float ud_arrow_width = line_height * machine().render().ui_aspect(); - float oy1 = origy1 + line_height; - - char const *const snaptext(m_info_view ? m_items_list[m_info_view - 1].c_str() : _(first)); + // 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); - // 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)) { - char const *const name(x ? m_items_list[x - 1].c_str() : _(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"; - draw_common_arrow(origx1, origy1, origx2, origy2, m_info_view, 0, total - 1, title_size); - if (justify == 'f') + 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); + + int cloneof = driver_list::non_bios_clone(driver); + if (0 <= cloneof) { - m_total_lines = ui().wrap_text( - container(), m_info_buffer.c_str(), - 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.c_str(), - 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.emulation_flags() & device_t::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.emulation_flags() & device_t::flags::SAVE_UNSUPPORTED) ? _("Support Save\tNo\n") : _("Support Save\tYes\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 const tempbuf(m_info_buffer.substr(xstart[itemline], xend[itemline] - xstart[itemline])); - if (tempbuf[0] == '#') - continue; - - if (r == 0 && m_topline_datsview != 0) // up arrow - { - 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 + if (region.is_diskdata()) { - 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::size_type const splitpos(tempbuf.find('\t')); - std::string const leftcol(tempbuf.substr(0, (std::string::npos == splitpos) ? 0U : splitpos)); - std::string const rightcol(tempbuf.substr((std::string::npos == splitpos) ? 0U : (splitpos + 1U))); - - // measure space needed, condense if necessary - float const leftlen(ui().get_string_width(leftcol.c_str(), text_size)); - float const rightlen(ui().get_string_width(rightcol.c_str(), 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.c_str(), - 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.c_str(), - 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.c_str(), text_size); - float tmp_size3 = (textlen > sc) ? text_size * (sc / textlen) : text_size; - ui().draw_text_full( - container(), tempbuf.c_str(), - 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.c_str(), - 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 diff --git a/src/frontend/mame/ui/selmenu.h b/src/frontend/mame/ui/selmenu.h index 728ca02cd32..f95dd8fb163 100644 --- a/src/frontend/mame/ui/selmenu.h +++ b/src/frontend/mame/ui/selmenu.h @@ -13,17 +13,36 @@ #pragma once #include "ui/menu.h" +#include "ui/utils.h" +#include "audit.h" + +#include "lrucache.h" + +#include <chrono> #include <map> #include <memory> -#include <mutex> +#include <optional> +#include <string_view> +#include <tuple> +#include <utility> #include <vector> +struct ui_system_info; struct ui_software_info; + namespace ui { +enum +{ + RP_FIRST = 0, + RP_IMAGES = RP_FIRST, + RP_INFOS, + RP_LAST = RP_INFOS +}; + class machine_static_info; class menu_select_launch : public menu @@ -33,8 +52,8 @@ public: virtual ~menu_select_launch() override; protected: - static constexpr std::size_t MAX_ICONS_RENDER = 128; - static constexpr std::size_t MAX_VISIBLE_SEARCH = 200; + static inline constexpr std::size_t MAX_ICONS_RENDER = 128; + static inline constexpr std::size_t MAX_VISIBLE_SEARCH = 200; // tab navigation enum class focused_menu @@ -69,6 +88,7 @@ protected: system_flags &operator=(system_flags &&) = default; ::machine_flags::type machine_flags() const { return m_machine_flags; } + device_t::flags_type emulation_flags() const { return m_emulation_flags; } device_t::feature_type unemulated_features() const { return m_unemulated_features; } device_t::feature_type imperfect_features() const { return m_imperfect_features; } bool has_keyboard() const { return m_has_keyboard; } @@ -77,6 +97,7 @@ protected: private: ::machine_flags::type m_machine_flags; + device_t::flags_type m_emulation_flags; device_t::feature_type m_unemulated_features; device_t::feature_type m_imperfect_features; bool m_has_keyboard; @@ -107,9 +128,6 @@ protected: menu_select_launch(mame_ui_manager &mui, render_container &container, bool is_swlist); focused_menu get_focus() const { return m_focus; } - void set_focus(focused_menu focus) { m_focus = focus; } - void next_image_view(); - void previous_image_view(); bool dismiss_error(); void set_error(reset_options ropt, std::string &&message); @@ -120,24 +138,21 @@ protected: void launch_system(game_driver const &driver, ui_software_info const &swinfo) { launch_system(ui(), driver, &swinfo, nullptr, nullptr); } void launch_system(game_driver const &driver, ui_software_info const &swinfo, std::string const &part) { launch_system(ui(), driver, &swinfo, &part, nullptr); } - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) override; + virtual void menu_activated() override; + virtual void menu_deactivated() override; // handlers - void inkey_navigation(); virtual void inkey_export() = 0; void inkey_dats(); // draw arrow void draw_common_arrow(float origx1, float origy1, float origx2, float origy2, int current, int dmin, int dmax, float title); - void draw_info_arrow(int ub, float origx1, float origx2, float oy1, float line_height, float text_size, float ud_arrow_width); - - bool draw_error_text(); + void draw_info_arrow(u32 flags, int line); template <typename Filter> - float 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 draw_left_panel(u32 flags, typename Filter::type current, std::map<typename Filter::type, typename Filter::ptr> const &filters); // icon helpers void check_for_icons(char const *listname); @@ -153,10 +168,30 @@ protected: void *get_selection_ptr() const { void *const selected_ref(get_selection_ref()); - return (uintptr_t(selected_ref) > skip_main_items) ? selected_ref : m_prev_selected; + return (uintptr_t(selected_ref) > m_skip_main_items) ? selected_ref : m_prev_selected; } - int visible_items; + bool show_left_panel() const { return !(m_panels_status & HIDE_LEFT_PANEL); } + bool show_right_panel() const { return !(m_panels_status & HIDE_RIGHT_PANEL); } + u8 right_panel() const { return m_right_panel; } + u8 right_image() const { return m_image_view; } + + char const *right_panel_config_string() const; + char const *right_image_config_string() const; + void set_right_panel(u8 index); + void set_right_image(u8 index); + void set_right_panel(std::string_view value); + void set_right_image(std::string_view value); + + static std::string make_system_audit_fail_text(media_auditor const &auditor, media_auditor::summary summary); + static std::string make_software_audit_fail_text(media_auditor const &auditor, media_auditor::summary summary); + static constexpr bool audit_passed(media_auditor::summary summary) + { + return (media_auditor::CORRECT == summary) || (media_auditor::BEST_AVAILABLE == summary) || (media_auditor::NONE_NEEDED == summary); + } + + int m_available_items; + int m_skip_main_items; void *m_prev_selected; int m_total_lines; int m_topline_datsview; @@ -164,11 +199,28 @@ protected: std::string m_search; private: + enum class pointer_action + { + NONE, + MAIN_TRACK_LINE, + MAIN_TRACK_RBUTTON, + MAIN_DRAG, + LEFT_TRACK_LINE, + LEFT_DRAG, + RIGHT_TRACK_TAB, + RIGHT_TRACK_ARROW, + RIGHT_TRACK_LINE, + RIGHT_SWITCH, + RIGHT_DRAG, + TOOLBAR_TRACK, + DIVIDER_TRACK + }; + using bitmap_vector = std::vector<bitmap_argb32>; using texture_ptr_vector = std::vector<texture_ptr>; using s_parts = std::unordered_map<std::string, std::string>; - using s_bios = std::vector<std::pair<std::string, int>>; + using s_bios = std::vector<std::pair<std::string, int> >; class software_parts; class bios_selection; @@ -187,12 +239,11 @@ private: void set_snapx_software(ui_software_info const *software) { m_snapx_software = software; } bitmap_argb32 &no_avail_bitmap() { return m_no_avail_bitmap; } - render_texture *star_texture() { return m_star_texture.get(); } - bitmap_vector const &toolbar_bitmap() { return m_toolbar_bitmap; } - bitmap_vector const &sw_toolbar_bitmap() { return m_sw_toolbar_bitmap; } - texture_ptr_vector const &toolbar_texture() { return m_toolbar_texture; } - texture_ptr_vector const &sw_toolbar_texture() { return m_sw_toolbar_texture; } + bitmap_vector const &toolbar_bitmaps() { return m_toolbar_bitmaps; } + texture_ptr_vector const &toolbar_textures() { return m_toolbar_textures; } + + void cache_toolbar(running_machine &machine, float width, float height); private: bitmap_ptr m_snapx_bitmap; @@ -201,47 +252,78 @@ private: ui_software_info const *m_snapx_software; bitmap_argb32 m_no_avail_bitmap; - bitmap_argb32 m_star_bitmap; - texture_ptr m_star_texture; - bitmap_vector m_toolbar_bitmap; - bitmap_vector m_sw_toolbar_bitmap; - texture_ptr_vector m_toolbar_texture; - texture_ptr_vector m_sw_toolbar_texture; + bitmap_vector m_toolbar_bitmaps; + texture_ptr_vector m_toolbar_textures; }; - using cache_ptr = std::shared_ptr<cache>; - using cache_ptr_map = std::map<running_machine *, cache_ptr>; - using flags_cache = util::lru_cache_map<game_driver const *, system_flags>; + // this is to satisfy the std::any requirement that objects be copyable + class cache_wrapper : public cache + { + public: + cache_wrapper(running_machine &machine) : cache(machine), m_machine(machine) { } + cache_wrapper(cache_wrapper const &that) : cache(that.m_machine), m_machine(that.m_machine) { } + private: + running_machine &m_machine; + }; - void reset_pressed() { m_pressed = false; m_repeat = 0; } - bool mouse_pressed() const { return (osd_ticks() >= m_repeat); } - void set_pressed(); + using flags_cache = util::lru_cache_map<game_driver const *, system_flags>; - bool snapx_valid() const { return m_cache->snapx_bitmap().valid(); } + // various helpers for common calculations + bool main_at_top() const noexcept { return !top_line; } + bool main_at_bottom() const noexcept { return (top_line + m_primary_lines) >= m_available_items; } + bool is_main_up_arrow(int index) const noexcept { return !index && !main_at_top(); } + bool is_main_down_arrow(int index) const noexcept { return ((m_primary_lines - 1) == index) && !main_at_bottom(); } + bool left_at_top() const noexcept { return !m_left_visible_top; } + bool left_at_bottom() const noexcept { return (m_left_visible_top + m_left_visible_lines) >= m_left_item_count; } + bool is_left_up_arrow(int index) const noexcept { return !index && !left_at_top(); } + bool is_left_down_arrow(int index) const noexcept { return ((m_left_visible_lines - 1) == index) && !left_at_bottom(); } + bool info_at_top() const noexcept { return !m_topline_datsview; } + bool info_at_bottom() const noexcept { return (m_topline_datsview + m_right_visible_lines) >= m_total_lines; } + + // getting precalculated geometry + float left_panel_left() const noexcept { return lr_border(); } + float left_panel_right() const noexcept { return lr_border() + m_left_panel_width; } + float right_panel_left() const noexcept { return 1.0F - lr_border() - m_right_panel_width; } + float right_panel_right() const noexcept { return 1.0F - lr_border(); } + float right_tab_width() const noexcept { return m_right_panel_width / float(RP_LAST - RP_FIRST + 1); } + float right_arrows_top() const noexcept { return m_right_heading_top + (0.1F * line_height()); } + float right_arrows_bottom() const noexcept { return m_right_heading_top + (0.9F * line_height()); } + float left_divider_left() const noexcept { return lr_border() + m_left_panel_width; } + float left_divider_right() const noexcept { return lr_border() + m_left_panel_width + m_divider_width; } + float right_divider_left() const noexcept { return 1.0F - lr_border() - m_right_panel_width - m_divider_width; } + float right_divider_right() const noexcept { return 1.0F - lr_border() - m_right_panel_width; } + + bool snapx_valid() const { return m_cache.snapx_bitmap().valid(); } + + void draw_divider(u32 flags, float x1, bool right); // draw left panel - virtual float draw_left_panel(float x1, float y1, float x2, float y2) = 0; - float draw_collapsed_left_panel(float x1, float y1, float x2, float y2); + virtual void draw_left_panel(u32 flags) = 0; // draw infos - void infos_render(float x1, float y1, float x2, float y2); - virtual void general_info(const game_driver *driver, std::string &buffer) = 0; + void infos_render(u32 flags); + void general_info(ui_system_info const *system, game_driver const &driver, std::string &buffer); // get selected software and/or driver - virtual void get_selection(ui_software_info const *&software, game_driver const *&driver) const = 0; + virtual void get_selection(ui_software_info const *&software, ui_system_info const *&system) const = 0; + + // show configuration menu + virtual void show_config_menu(int index) = 0; + virtual bool accept_search() const { return true; } void select_prev() { if (!m_prev_selected) { - set_selected_index(0); + if (m_available_items) + set_selected_index(0); } else { for (int x = 0; x < item_count(); ++x) { - if (item(x).ref == m_prev_selected) + if (item(x).ref() == m_prev_selected) { set_selected_index(x); break; @@ -249,51 +331,86 @@ private: } } } - - void draw_toolbar(float x1, float y1, float x2, float y2); + void set_focus(focused_menu focus) { m_focus = focus; } + void rotate_focus(int dir); + std::pair<bool, bool> next_right_panel_view(); + std::pair<bool, bool> previous_right_panel_view(); + std::pair<bool, bool> next_image_view(); + std::pair<bool, bool> previous_image_view(); + std::pair<bool, bool> next_info_view(); + std::pair<bool, bool> previous_info_view(); + + void draw_toolbar(u32 flags, float x1, float y1, float x2, float y2); void draw_star(float x0, float y0); void draw_icon(int linenum, void *selectedref, float x1, float y1); virtual render_texture *get_icon_texture(int linenum, void *selectedref) = 0; - void get_title_search(std::string &title, std::string &search); - - // handle keys - virtual void handle_keys(uint32_t flags, int &iptkey) override; - - // handle mouse - virtual void handle_events(uint32_t flags, event &ev) override; - - // live search active? - virtual bool menu_has_search_active() override { return !m_search.empty(); } + std::string get_arts_searchpath(); + + // event handling + virtual bool handle_events(u32 flags, event &ev) override; + virtual bool handle_keys(u32 flags, int &iptkey) override; + virtual std::tuple<int, bool, bool> custom_pointer_updated(bool changed, ui_event const &uievt) override; + + // pointer handling helpers + std::tuple<int, bool, bool> handle_primary_down(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> handle_right_down(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> handle_middle_down(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> update_main_track_line(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> update_main_track_rbutton(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> update_main_drag(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> update_left_track_line(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> update_left_drag(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> update_right_track_tab(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> update_right_track_arrow(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> update_right_track_line(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> update_right_switch(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> update_right_drag(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> update_toolbar_track(bool changed, ui_event const &uievt); + std::tuple<int, bool, bool> update_divider_track(bool changed, ui_event const &uievt); + + bool main_force_visible_selection(); // draw game list - virtual void draw(uint32_t flags) override; + virtual void draw(u32 flags) override; // draw right panel - void draw_right_panel(float origx1, float origy1, float origx2, float origy2); - float draw_right_box_title(float x1, float y1, float x2, float y2); + void draw_right_panel(u32 flags); + void draw_right_box_tabs(u32 flags); + void draw_right_box_heading(u32 flags, bool larrow, bool rarrow, std::string_view text); // images render - void arts_render(float origx1, float origy1, float origx2, float origy2); - std::string arts_render_common(float origx1, float origy1, float origx2, float origy2); - void arts_render_images(bitmap_argb32 &&bitmap, float origx1, float origy1, float origx2, float origy2); - void draw_snapx(float origx1, float origy1, float origx2, float origy2); + void arts_render(u32 flags); + void arts_render_images(bitmap_argb32 &&bitmap); + void draw_snapx(); // text for main top/bottom panels virtual void make_topbox_text(std::string &line0, std::string &line1, std::string &line2) const = 0; - virtual std::string make_driver_description(game_driver const &driver) const = 0; - virtual std::string make_software_description(ui_software_info const &software) const = 0; + virtual std::string make_software_description(ui_software_info const &software, ui_system_info const *system) const = 0; // filter navigation - virtual void filter_selected() = 0; + virtual void filter_selected(int index) = 0; + static void make_audit_fail_text(std::ostream &str, media_auditor const &auditor, media_auditor::summary summary); static void launch_system(mame_ui_manager &mui, game_driver const &driver, ui_software_info const *swinfo, std::string const *part, int const *bios); static bool select_part(mame_ui_manager &mui, render_container &container, software_info const &info, ui_software_info const &ui_info); static bool has_multiple_bios(ui_software_info const &swinfo, s_bios &biosname); static bool has_multiple_bios(game_driver const &driver, s_bios &biosname); - // cleanup function - static void exit(running_machine &machine); + bool check_scroll_repeat(float top, std::pair<float, float> hbounds, float height) + { + float const linetop(top + (float(m_clicked_line) * height)); + float const linebottom(top + (float(m_clicked_line + 1) * height)); + if (pointer_in_rect(hbounds.first, linetop, hbounds.second, linebottom)) + { + if (std::chrono::steady_clock::now() >= m_scroll_repeat) + { + m_scroll_repeat += std::chrono::milliseconds(100); + return true; + } + } + return false; + } bool m_ui_error; std::string m_error_text; @@ -303,23 +420,58 @@ private: int m_info_view; std::vector<std::string> m_items_list; std::string m_info_buffer; - - cache_ptr m_cache; - bool m_is_swlist; - focused_menu m_focus; - bool m_pressed; // mouse button held down - osd_ticks_t m_repeat; - - int m_right_visible_lines; // right box lines - - bool m_has_icons; - bool m_switch_image; - bool m_default_image; - uint8_t m_image_view; - flags_cache m_flags; - - static std::mutex s_cache_guard; - static cache_ptr_map s_caches; + std::optional<text_layout> m_info_layout; + + int m_icon_width; + int m_icon_height; + float m_divider_width; + float m_divider_arrow_width; + float m_divider_arrow_height; + float m_info_line_height; + + cache &m_cache; + bool m_is_swlist; + focused_menu m_focus; + + pointer_action m_pointer_action; + std::chrono::steady_clock::time_point m_scroll_repeat; + std::pair<float, float> m_base_pointer; + std::pair<float, float> m_last_pointer; + int m_clicked_line; + focused_menu m_wheel_target; + int m_wheel_movement; + + std::pair<float, float> m_primary_vbounds; + float m_primary_items_top; + std::pair<float, float> m_primary_items_hbounds; + int m_primary_lines; + + float m_left_panel_width; + std::pair<float, float> m_left_items_hbounds; + float m_left_items_top; + int m_left_item_count; + int m_left_visible_lines; + int m_left_visible_top; + + float m_right_panel_width; + float m_right_tabs_bottom; + float m_right_heading_top; + std::pair<float, float> m_right_content_vbounds; + std::pair<float, float> m_right_content_hbounds; + int m_right_visible_lines; // right box lines + + std::pair<float, float> m_toolbar_button_vbounds; + float m_toolbar_button_width; + float m_toolbar_button_spacing; + float m_toolbar_backtrack_left; + float m_toolbar_main_left; + + u8 m_panels_status; + u8 m_right_panel; + bool m_has_icons; + bool m_switch_image; + u8 m_image_view; + flags_cache m_flags; }; } // namespace ui diff --git a/src/frontend/mame/ui/selsoft.cpp b/src/frontend/mame/ui/selsoft.cpp index 4c86aaae9a4..4e9a16bb196 100644 --- a/src/frontend/mame/ui/selsoft.cpp +++ b/src/frontend/mame/ui/selsoft.cpp @@ -14,106 +14,400 @@ #include "ui/ui.h" #include "ui/icorender.h" #include "ui/inifile.h" +#include "ui/miscmenu.h" #include "ui/selector.h" -#include "audit.h" #include "drivenum.h" #include "emuopts.h" +#include "fileio.h" #include "mame.h" #include "rendutil.h" #include "softlist_dev.h" #include "uiinput.h" #include "luaengine.h" +#include "corestr.h" +#include "path.h" +#include "unicode.h" + #include <algorithm> #include <iterator> #include <functional> +#include <thread> +#include <locale> namespace ui { -namespace { +struct menu_select_software::search_item +{ + search_item(search_item const &) = default; + search_item(search_item &&) = default; + search_item &operator=(search_item const &) = default; + search_item &operator=(search_item &&) = default; + + search_item(ui_software_info const &s) + : software(s) + , ucs_shortname(ustr_from_utf8(normalize_unicode(s.shortname, unicode_normalization_form::D, true))) + , ucs_longname(ustr_from_utf8(normalize_unicode(s.longname, unicode_normalization_form::D, true))) + , ucs_alttitles() + , penalty(1.0) + { + ucs_alttitles.reserve(s.alttitles.size()); + for (std::string const &alttitle : s.alttitles) + ucs_alttitles.emplace_back(ustr_from_utf8(normalize_unicode(alttitle, unicode_normalization_form::D, true))); + } -//------------------------------------------------- -// compares two items in the software list and -// sort them by parent-clone -//------------------------------------------------- + void set_penalty(std::u32string const &search) + { + penalty = util::edit_distance(search, ucs_shortname); + if (penalty) + penalty = (std::min)(penalty, util::edit_distance(search, ucs_longname)); + auto it(ucs_alttitles.begin()); + while (penalty && (ucs_alttitles.end() != it)) + penalty = (std::min)(penalty, util::edit_distance(search, *it++)); + } + + std::reference_wrapper<ui_software_info const> software; + std::u32string ucs_shortname; + std::u32string ucs_longname; + std::vector<std::u32string> ucs_alttitles; + double penalty; +}; -bool compare_software(ui_software_info const &a, ui_software_info const &b) -{ - bool const clonex = !a.parentname.empty() && !a.parentlongname.empty(); - bool const cloney = !b.parentname.empty() && !b.parentlongname.empty(); - if (!clonex && !cloney) + +class menu_select_software::machine_data +{ +public: + machine_data(menu_select_software &menu) + : m_icons(MAX_ICONS_RENDER) + , m_has_empty_start(false) + , m_filter_data() + , m_filters() + , m_filter_type(software_filter::ALL) + , m_swinfo() + , m_searchlist() + , m_right_panel(menu.right_panel()) + , m_right_image(menu.right_image()) { - return 0 > core_stricmp(a.longname.c_str(), b.longname.c_str()); + // add start empty item + m_swinfo.emplace_back(*menu.m_system.driver); + + machine_config config(*menu.m_system.driver, menu.machine().options()); + + // see if any media devices require an image to be loaded + m_has_empty_start = true; + for (device_image_interface &image : image_interface_enumerator(config.root_device())) + { + if (!image.filename() && image.must_be_loaded()) + { + m_has_empty_start = false; + break; + } + } + + // iterate through all software lists + std::vector<std::size_t> orphans; + struct orphan_less + { + std::vector<ui_software_info> &swinfo; + bool operator()(std::string const &a, std::string const &b) const { return a < b; }; + bool operator()(std::string const &a, std::size_t b) const { return a < swinfo[b].parentname; }; + bool operator()(std::size_t a, std::string const &b) const { return swinfo[a].parentname < b; }; + bool operator()(std::size_t a, std::size_t b) const { return swinfo[a].parentname < swinfo[b].parentname; }; + }; + orphan_less const orphan_cmp{ m_swinfo }; + for (software_list_device &swlist : software_list_device_enumerator(config.root_device())) + { + m_filter_data.add_list(swlist.list_name(), swlist.description()); + menu.check_for_icons(swlist.list_name().c_str()); + orphans.clear(); + std::map<std::string, std::string> parentnames; + std::map<std::string, std::string>::const_iterator prevparent(parentnames.end()); + for (const software_info &swinfo : swlist.get_info()) + { + // check for previously-encountered clones + if (swinfo.parentname().empty()) + { + if (parentnames.emplace(swinfo.shortname(), swinfo.longname()).second) + { + auto const clones(std::equal_range(orphans.begin(), orphans.end(), swinfo.shortname(), orphan_cmp)); + for (auto it = clones.first; clones.second != it; ++it) + m_swinfo[*it].parentlongname = swinfo.longname(); + orphans.erase(clones.first, clones.second); + } + else + { + assert([] (auto const x) { return x.first == x.second; } (std::equal_range(orphans.begin(), orphans.end(), swinfo.shortname(), orphan_cmp))); + } + } + + const software_part &part = swinfo.parts().front(); + if (swlist.is_compatible(part) == SOFTWARE_IS_COMPATIBLE) + { + char const *instance_name(nullptr); + char const *type_name(nullptr); + for (device_image_interface &image : image_interface_enumerator(config.root_device())) + { + char const *const interface = image.image_interface(); + if (interface && part.matches_interface(interface)) + { + instance_name = image.instance_name().c_str(); + type_name = image.image_type_name(); + break; + } + } + + if (instance_name && type_name) + { + // add to collection and try to resolve parent if applicable + auto const ins(m_swinfo.emplace(m_swinfo.end(), swinfo, part, *menu.m_system.driver, swlist.list_name(), instance_name, type_name)); + if (!swinfo.parentname().empty()) + { + if ((parentnames.end() == prevparent) || (swinfo.parentname() != prevparent->first)) + prevparent = parentnames.find(swinfo.parentname()); + + if (parentnames.end() != prevparent) + { + ins->parentlongname = prevparent->second; + } + else + { + orphans.emplace( + std::upper_bound(orphans.begin(), orphans.end(), swinfo.parentname(), orphan_cmp), + std::distance(m_swinfo.begin(), ins)); + } + } + + // populate filter choices + m_filter_data.add_region(ins->longname); + m_filter_data.add_publisher(ins->publisher); + m_filter_data.add_year(ins->year); + for (software_info_item const &i : ins->info) + m_filter_data.add_info(i); + m_filter_data.add_device_type(ins->devicetype); + } + } + } + } + + std::string searchstr, curpath; + for (auto &elem : m_filter_data.list_names()) + { + path_iterator path(menu.machine().options().media_path()); + while (path.next(curpath)) + { + searchstr.assign(curpath).append(PATH_SEPARATOR).append(elem).append(";"); + file_enumerator fpath(searchstr.c_str()); + + // iterate while we get new objects + osd::directory::entry const *dir; + while ((dir = fpath.next()) != nullptr) + { + std::string name; + if (dir->type == osd::directory::entry::entry_type::FILE) + name = strmakelower(core_filename_extract_base(dir->name, true)); + else if (dir->type == osd::directory::entry::entry_type::DIR && strcmp(dir->name, ".") != 0) + name = strmakelower(dir->name); + else + continue; + + for (auto & yelem : m_swinfo) + if (yelem.shortname == name && yelem.listname == elem) + { + yelem.available = true; + break; + } + } + } + } + + // sort array + std::locale const lcl; + std::collate<wchar_t> const &coll = std::use_facet<std::collate<wchar_t> >(lcl); + auto const compare_names = + [&coll] (std::string const &xl, std::string const &xd, std::string const &yl, std::string const &yd) -> bool + { + std::wstring const wx = wstring_from_utf8(xd); + std::wstring const wy = wstring_from_utf8(yd); + auto const cmp(coll.compare(wx.data(), wx.data() + wx.size(), wy.data(), wy.data() + wy.size())); + if (cmp) + return 0 > cmp; + else + return xl < yl; + }; + std::stable_sort( + m_swinfo.begin() + 1, + m_swinfo.end(), + [&compare_names] (ui_software_info const &a, ui_software_info const &b) -> bool + { + bool const clonex = !a.parentname.empty() && !a.parentlongname.empty(); + bool const cloney = !b.parentname.empty() && !b.parentlongname.empty(); + + if (!clonex && !cloney) + { + return compare_names(a.listname, a.longname, b.listname, b.longname); + } + else if (!clonex && cloney) + { + if ((a.shortname == b.parentname) && (a.listname == b.listname)) + return true; + else + return compare_names(a.listname, a.longname, b.listname, b.parentlongname); + } + else if (clonex && !cloney) + { + if ((a.parentname == b.shortname) && (a.listname == b.listname)) + return false; + else + return compare_names(a.listname, a.parentlongname, b.listname, b.longname); + } + else if ((a.parentname == b.parentname) && (a.listname == b.listname)) + { + return compare_names(a.listname, a.longname, b.listname, b.longname); + } + else + { + return compare_names(a.listname, a.parentlongname, b.listname, b.parentlongname); + } + }); + + // start populating search info in background + m_search_thread = std::make_unique<std::thread>( + [this] () + { + m_searchlist.reserve(m_swinfo.size()); + for (ui_software_info const &sw : m_swinfo) + { + if (!sw.startempty) + m_searchlist.emplace_back(sw); + } + }); + + // build derivative filter data + m_filter_data.finalise(); + + // load custom filters info from file + emu_file file(menu.ui().options().ui_path(), OPEN_FLAG_READ); + if (!file.open(util::string_format("custom_%s_filter.ini", menu.m_system.driver->name))) + { + software_filter::ptr flt(software_filter::create(file, m_filter_data)); + if (flt) + m_filters.emplace(flt->get_type(), std::move(flt)); + file.close(); + } } - else if (!clonex && cloney) + + ~machine_data() { - if ((a.shortname == b.parentname) && (a.instance == b.instance)) - return true; - else - return 0 > core_stricmp(a.longname.c_str(), b.parentlongname.c_str()); + if (m_search_thread) + m_search_thread->join(); } - else if (clonex && !cloney) + + icon_cache &icons() { return m_icons; } + + bool has_empty_start() const noexcept { return m_has_empty_start; } + + filter_map const &filters() const noexcept { return m_filters; } + + software_filter::type filter_type() const noexcept { return m_filter_type; } + void set_filter_type(software_filter::type type) noexcept { m_filter_type = type; } + + software_filter const *current_filter() const noexcept { - if ((a.parentname == b.shortname) && (a.instance == b.instance)) - return false; - else - return 0 > core_stricmp(a.parentlongname.c_str(), b.longname.c_str()); + auto const found(m_filters.find(m_filter_type)); + return (m_filters.end() != found) ? found->second.get() : nullptr; } - else if ((a.parentname == b.parentname) && (a.instance == b.instance)) + + software_filter &get_filter(software_filter::type type) { - return 0 > core_stricmp(a.longname.c_str(), b.longname.c_str()); + filter_map::const_iterator it(m_filters.find(type)); + if (m_filters.end() != it) + return *it->second; + else + return *m_filters.emplace(type, software_filter::create(type, m_filter_data)).first->second; } - else + + std::vector<ui_software_info> const &swinfo() const noexcept { return m_swinfo; } + + std::vector<search_item> const &find_matches(std::string const &search) { - return 0 > core_stricmp(a.parentlongname.c_str(), b.parentlongname.c_str()); + // ensure search list is populated + if (m_search_thread) + { + m_search_thread->join(); + m_search_thread.reset(); + } + + // update search + const std::u32string ucs_search(ustr_from_utf8(normalize_unicode(search, unicode_normalization_form::D, true))); + for (search_item &entry : m_searchlist) + entry.set_penalty(ucs_search); + + // sort according to edit distance + std::stable_sort( + m_searchlist.begin(), + m_searchlist.end(), + [] (search_item const &lhs, search_item const &rhs) { return lhs.penalty < rhs.penalty; }); + + // return reference to search results + return m_searchlist; } -} -} // anonymous namespace + u8 right_panel() const { return m_right_panel; } + u8 right_image() const { return m_right_image; } + void set_right_panel(u8 index) { m_right_panel = index; } + void set_right_image(u8 index) { m_right_image = index; } +private: + icon_cache m_icons; + bool m_has_empty_start; + software_filter_data m_filter_data; + filter_map m_filters; + software_filter::type m_filter_type; + std::vector<ui_software_info> m_swinfo; + std::vector<search_item> m_searchlist; -menu_select_software::search_item::search_item(ui_software_info const &s) - : software(s) - , ucs_shortname(ustr_from_utf8(normalize_unicode(s.shortname, unicode_normalization_form::D, true))) - , ucs_longname(ustr_from_utf8(normalize_unicode(s.longname, unicode_normalization_form::D, true))) - , penalty(1.0) -{ -} + u8 m_right_panel; + u8 m_right_image; -void menu_select_software::search_item::set_penalty(std::u32string const &search) -{ - // TODO: search alternate title as well - penalty = util::edit_distance(search, ucs_shortname); - if (penalty) - penalty = (std::min)(penalty, util::edit_distance(search, ucs_longname)); -} + std::unique_ptr<std::thread> m_search_thread; +}; //------------------------------------------------- // ctor //------------------------------------------------- -menu_select_software::menu_select_software(mame_ui_manager &mui, render_container &container, game_driver const &driver) +menu_select_software::menu_select_software(mame_ui_manager &mui, render_container &container, ui_system_info const &system) : menu_select_launch(mui, container, true) , m_icon_paths() - , m_icons(MAX_ICONS_RENDER) - , m_driver(driver) - , m_has_empty_start(false) - , m_filter_data() - , m_filters() - , m_filter_type(software_filter::ALL) - , m_swinfo() - , m_searchlist() + , m_system(system) , m_displaylist() { reselect_last::reselect(false); - build_software_list(); - load_sw_custom_filters(); - m_filter_highlight = m_filter_type; + using machine_data_cache = util::lru_cache_map<game_driver const *, std::shared_ptr<machine_data> >; + auto &cached(mui.get_session_data<menu_select_software, machine_data_cache>(8)[system.driver]); + if (cached) + { + // restore last right panel settings for this machine + set_right_panel(cached->right_panel()); + set_right_image(cached->right_image()); + } + else + { + // restore last right panel settings from UI options + ui_options &moptions = ui().options(); + set_right_panel(moptions.software_right_panel()); + set_right_image(moptions.software_right_image()); + + cached = std::make_shared<machine_data>(*this); + } + m_data = cached; + + m_filter_highlight = m_data->filter_type(); set_switch_image(); ui_globals::cur_sw_dats_view = 0; @@ -132,79 +426,27 @@ menu_select_software::~menu_select_software() // handle //------------------------------------------------- -void menu_select_software::handle() +bool menu_select_software::handle(event const *ev) { - if (m_prev_selected == nullptr) - m_prev_selected = item(0).ref; + if (!m_prev_selected && (item_count() > 0)) + m_prev_selected = item(0).ref(); - // ignore pause keys by swallowing them before we process the menu - machine().ui_input().pressed(IPT_UI_PAUSE); + // FIXME: everything above here used run before events were processed // process the menu - const event *menu_event = process(PROCESS_LR_REPEAT); - if (menu_event) + bool changed = false; + if (ev) { if (dismiss_error()) { - // reset the error on any future event + // reset the error on any subsequent menu event + changed = true; } - else switch (menu_event->iptkey) + else switch (ev->iptkey) { case IPT_UI_SELECT: - if ((get_focus() == focused_menu::MAIN) && menu_event->itemref) - inkey_select(menu_event); - break; - - case IPT_UI_LEFT: - if (ui_globals::rpanel == RP_IMAGES) - { - // Images - previous_image_view(); - } - else if (ui_globals::rpanel == RP_INFOS && ui_globals::cur_sw_dats_view > 0) - { - // Infos - ui_globals::cur_sw_dats_view--; - m_topline_datsview = 0; - } - break; - - case IPT_UI_RIGHT: - if (ui_globals::rpanel == RP_IMAGES) - { - // Images - next_image_view(); - } - else if (ui_globals::rpanel == RP_INFOS && ui_globals::cur_sw_dats_view < (ui_globals::cur_sw_dats_total - 1)) - { - // Infos - ui_globals::cur_sw_dats_view++; - m_topline_datsview = 0; - } - break; - - case IPT_UI_UP: - if ((get_focus() == focused_menu::LEFT) && (software_filter::FIRST < m_filter_highlight)) - --m_filter_highlight; - break; - - case IPT_UI_DOWN: - if ((get_focus() == focused_menu::LEFT) && (software_filter::LAST > m_filter_highlight)) - ++m_filter_highlight; - break; - - case IPT_UI_HOME: - if (get_focus() == focused_menu::LEFT) - m_filter_highlight = software_filter::FIRST; - break; - - case IPT_UI_END: - if (get_focus() == focused_menu::LEFT) - m_filter_highlight = software_filter::LAST; - break; - - case IPT_UI_CONFIGURE: - inkey_navigation(); + if ((get_focus() == focused_menu::MAIN) && ev->itemref) + changed = inkey_select(ev); break; case IPT_UI_DATS: @@ -212,12 +454,12 @@ void menu_select_software::handle() break; default: - if (menu_event->itemref) + if (ev->itemref) { - if (menu_event->iptkey == IPT_UI_FAVORITES) + if (ev->iptkey == IPT_UI_FAVORITES) { // handle UI_FAVORITES - ui_software_info *swinfo = (ui_software_info *)menu_event->itemref; + ui_software_info *swinfo = (ui_software_info *)ev->itemref; if ((uintptr_t)swinfo > 2) { @@ -225,81 +467,97 @@ void menu_select_software::handle() if (!mfav.is_favorite_system_software(*swinfo)) { mfav.add_favorite_software(*swinfo); - machine().popmessage(_("%s\n added to favorites list."), swinfo->longname.c_str()); + machine().popmessage(_("%s\n added to favorites list."), swinfo->longname); } - else { - machine().popmessage(_("%s\n removed from favorites list."), swinfo->longname.c_str()); + machine().popmessage(_("%s\n removed from favorites list."), swinfo->longname); mfav.remove_favorite_software(*swinfo); } + changed = true; } } } } } - // if we're in an error state, overlay an error message - draw_error_text(); + return changed; +} + +//------------------------------------------------- +// recompute_metrics +//------------------------------------------------- + +void menu_select_software::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + menu_select_launch::recompute_metrics(width, height, aspect); + + // configure the custom rendering + set_custom_space(4.0F * line_height() + 5.0F * tb_border(), 4.0F * line_height() + 3.0F * tb_border()); +} + +//------------------------------------------------- +// menu_deactivated +//------------------------------------------------- + +void menu_select_software::menu_deactivated() +{ + menu_select_launch::menu_deactivated(); + + // save last right panel settings + m_data->set_right_panel(right_panel()); + m_data->set_right_image(right_image()); + ui_options &mopt = ui().options(); + mopt.set_value(OPTION_SOFTWARE_RIGHT_PANEL, right_panel_config_string(), OPTION_PRIORITY_CMDLINE); + mopt.set_value(OPTION_SOFTWARE_RIGHT_IMAGE, right_image_config_string(), OPTION_PRIORITY_CMDLINE); } //------------------------------------------------- // populate //------------------------------------------------- -void menu_select_software::populate(float &customtop, float &custombottom) +void menu_select_software::populate() { - for (auto &icon : m_icons) // TODO: why is this here? maybe better on resize or setting change? + for (auto &icon : m_data->icons()) // TODO: why is this here? maybe better on resize or setting change? icon.second.texture.reset(); - uint32_t flags_ui = FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW; - m_has_empty_start = true; int old_software = -1; - // FIXME: why does it do this relatively expensive operation every time? - machine_config config(m_driver, machine().options()); - for (device_image_interface &image : image_interface_iterator(config.root_device())) - { - if (!image.filename() && image.must_be_loaded()) - { - m_has_empty_start = false; - break; - } - } - // start with an empty list m_displaylist.clear(); - filter_map::const_iterator const flt(m_filters.find(m_filter_type)); + software_filter const *const flt(m_data->current_filter()); // no active search if (m_search.empty()) { - // if the device can be loaded empty, add an item - if (m_has_empty_start) - item_append("[Start empty]", "", flags_ui, (void *)&m_swinfo[0]); + // add an item to start empty or let the user use the file manager + item_append( + m_data->has_empty_start() ? _("[Start empty]") : _("[Use file manager]"), + 0, + (void *)&m_data->swinfo()[0]); - if (m_filters.end() == flt) - std::copy(std::next(m_swinfo.begin()), m_swinfo.end(), std::back_inserter(m_displaylist)); + if (!flt) + std::copy(std::next(m_data->swinfo().begin()), m_data->swinfo().end(), std::back_inserter(m_displaylist)); else - flt->second->apply(std::next(m_swinfo.begin()), m_swinfo.end(), std::back_inserter(m_displaylist)); + flt->apply(std::next(m_data->swinfo().begin()), m_data->swinfo().end(), std::back_inserter(m_displaylist)); } else { - find_matches(); + std::vector<search_item> const &searchlist = m_data->find_matches(m_search); - if (m_filters.end() == flt) + if (!flt) { std::transform( - m_searchlist.begin(), - std::next(m_searchlist.begin(), (std::min)(m_searchlist.size(), MAX_VISIBLE_SEARCH)), + searchlist.begin(), + std::next(searchlist.begin(), (std::min)(searchlist.size(), MAX_VISIBLE_SEARCH)), std::back_inserter(m_displaylist), [] (search_item const &entry) { return entry.software; }); } else { - for (auto it = m_searchlist.begin(); (m_searchlist.end() != it) && (MAX_VISIBLE_SEARCH > m_displaylist.size()); ++it) + for (auto it = searchlist.begin(); (searchlist.end() != it) && (MAX_VISIBLE_SEARCH > m_displaylist.size()); ++it) { - if (flt->second->apply(it->software)) + if (flt->apply(it->software)) m_displaylist.emplace_back(it->software); } } @@ -311,261 +569,85 @@ void menu_select_software::populate(float &customtop, float &custombottom) if (reselect_last::software() == "[Start empty]" && !reselect_last::driver().empty()) old_software = 0; else if (m_displaylist[curitem].get().shortname == reselect_last::software() && m_displaylist[curitem].get().listname == reselect_last::swlist()) - old_software = m_has_empty_start ? curitem + 1 : curitem; + old_software = curitem + 1; item_append( m_displaylist[curitem].get().longname, m_displaylist[curitem].get().devicetype, - m_displaylist[curitem].get().parentname.empty() ? flags_ui : (FLAG_INVERT | flags_ui), (void *)&m_displaylist[curitem].get()); + m_displaylist[curitem].get().parentname.empty() ? 0 : FLAG_INVERT, (void *)&m_displaylist[curitem].get()); } - item_append(menu_item_type::SEPARATOR, flags_ui); - - // configure the custom rendering - customtop = 4.0f * ui().get_line_height() + 5.0f * ui().box_tb_border(); - custombottom = 5.0f * ui().get_line_height() + 4.0f * ui().box_tb_border(); + m_skip_main_items = 0; if (old_software != -1) { set_selected_index(old_software); - top_line = selected_index() - (ui_globals::visible_sw_lines / 2); + centre_selection(); } reselect_last::reset(); } -//------------------------------------------------- -// build a list of software -//------------------------------------------------- - -void menu_select_software::build_software_list() -{ - // add start empty item - m_swinfo.emplace_back(m_driver); - - machine_config config(m_driver, machine().options()); - - // iterate through all software lists - std::vector<std::size_t> orphans; - struct orphan_less - { - std::vector<ui_software_info> &swinfo; - bool operator()(std::string const &a, std::string const &b) const { return a < b; }; - bool operator()(std::string const &a, std::size_t b) const { return a < swinfo[b].parentname; }; - bool operator()(std::size_t a, std::string const &b) const { return swinfo[a].parentname < b; }; - bool operator()(std::size_t a, std::size_t b) const { return swinfo[a].parentname < swinfo[b].parentname; }; - }; - orphan_less const orphan_cmp{ m_swinfo }; - for (software_list_device &swlist : software_list_device_iterator(config.root_device())) - { - m_filter_data.add_list(swlist.list_name(), swlist.description()); - check_for_icons(swlist.list_name().c_str()); - orphans.clear(); - std::map<std::string, std::string> parentnames; - std::map<std::string, std::string>::const_iterator prevparent(parentnames.end()); - for (const software_info &swinfo : swlist.get_info()) - { - // check for previously-encountered clones - if (swinfo.parentname().empty()) - { - if (parentnames.emplace(swinfo.shortname(), swinfo.longname()).second) - { - auto const clones(std::equal_range(orphans.begin(), orphans.end(), swinfo.shortname(), orphan_cmp)); - for (auto it = clones.first; clones.second != it; ++it) - m_swinfo[*it].parentlongname = swinfo.longname(); - orphans.erase(clones.first, clones.second); - } - else - { - assert([] (auto const x) { return x.first == x.second; } (std::equal_range(orphans.begin(), orphans.end(), swinfo.shortname(), orphan_cmp))); - } - } - - const software_part &part = swinfo.parts().front(); - if (swlist.is_compatible(part) == SOFTWARE_IS_COMPATIBLE) - { - char const *instance_name(nullptr); - char const *type_name(nullptr); - for (device_image_interface &image : image_interface_iterator(config.root_device())) - { - char const *const interface = image.image_interface(); - if (interface && part.matches_interface(interface)) - { - instance_name = image.instance_name().c_str(); - type_name = image.image_type_name(); - break; - } - } - - if (instance_name && type_name) - { - // add to collection and try to resolve parent if applicable - auto const ins(m_swinfo.emplace(m_swinfo.end(), swinfo, part, m_driver, swlist.list_name(), instance_name, type_name)); - if (!swinfo.parentname().empty()) - { - if ((parentnames.end() == prevparent) || (swinfo.parentname() != prevparent->first)) - prevparent = parentnames.find(swinfo.parentname()); - - if (parentnames.end() != prevparent) - { - ins->parentlongname = prevparent->second; - } - else - { - orphans.emplace( - std::upper_bound(orphans.begin(), orphans.end(), swinfo.parentname(), orphan_cmp), - std::distance(m_swinfo.begin(), ins)); - } - } - - // populate filter choices - m_filter_data.add_region(ins->longname); - m_filter_data.add_publisher(ins->publisher); - m_filter_data.add_year(ins->year); - m_filter_data.add_device_type(ins->devicetype); - } - } - } - } - - std::string searchstr, curpath; - for (auto & elem : m_filter_data.list_names()) - { - path_iterator path(machine().options().media_path()); - while (path.next(curpath)) - { - searchstr.assign(curpath).append(PATH_SEPARATOR).append(elem).append(";"); - file_enumerator fpath(searchstr.c_str()); - - // iterate while we get new objects - osd::directory::entry const *dir; - while ((dir = fpath.next()) != nullptr) - { - std::string name; - if (dir->type == osd::directory::entry::entry_type::FILE) - name = core_filename_extract_base(dir->name, true); - else if (dir->type == osd::directory::entry::entry_type::DIR && strcmp(dir->name, ".") != 0) - name = dir->name; - else - continue; - - strmakelower(name); - for (auto & yelem : m_swinfo) - if (yelem.shortname == name && yelem.listname == elem) - { - yelem.available = true; - break; - } - } - } - } - - // sort array - std::stable_sort(m_swinfo.begin() + 1, m_swinfo.end(), compare_software); - m_filter_data.finalise(); -} - //------------------------------------------------- // handle select key event //------------------------------------------------- -void menu_select_software::inkey_select(const event *menu_event) +bool menu_select_software::inkey_select(const event *menu_event) { ui_software_info *ui_swinfo = (ui_software_info *)menu_event->itemref; + driver_enumerator drivlist(machine().options(), *ui_swinfo->driver); + media_auditor auditor(drivlist); + drivlist.next(); - if (ui_swinfo->startempty == 1) + // audit the system ROMs first to see if we're going to work + media_auditor::summary const sysaudit = auditor.audit_media(AUDIT_VALIDATE_FAST); + if (!audit_passed(sysaudit)) + { + set_error(reset_options::REMEMBER_REF, make_system_audit_fail_text(auditor, sysaudit)); + return true; + } + else if (ui_swinfo->startempty == 1) { if (!select_bios(*ui_swinfo->driver, true)) { reselect_last::reselect(true); launch_system(*ui_swinfo->driver, *ui_swinfo); } + return false; } else { - // first validate - driver_enumerator drivlist(machine().options(), *ui_swinfo->driver); - media_auditor auditor(drivlist); - drivlist.next(); - software_list_device *swlist = software_list_device::find_by_name(*drivlist.config(), ui_swinfo->listname.c_str()); - const software_info *swinfo = swlist->find(ui_swinfo->shortname.c_str()); + // now audit the software + software_list_device *swlist = software_list_device::find_by_name(*drivlist.config(), ui_swinfo->listname); + const software_info *swinfo = swlist->find(ui_swinfo->shortname); + media_auditor::summary const swaudit = auditor.audit_software(*swlist, *swinfo, AUDIT_VALIDATE_FAST); - media_auditor::summary const summary = auditor.audit_software(swlist->list_name(), swinfo, AUDIT_VALIDATE_FAST); - - if (summary == media_auditor::CORRECT || summary == media_auditor::BEST_AVAILABLE || summary == media_auditor::NONE_NEEDED) + if (audit_passed(swaudit)) { if (!select_bios(*ui_swinfo, false) && !select_part(*swinfo, *ui_swinfo)) { reselect_last::reselect(true); launch_system(drivlist.driver(), *ui_swinfo); } + return false; } else { // otherwise, display an error - std::ostringstream str; - str << _("The selected software is missing one or more required files. Please select a different software.\n\n"); - if (media_auditor::NOTFOUND != summary) - { - auditor.summarize(nullptr, &str); - str << "\n"; - } - str << _("Press any key to continue."), - set_error(reset_options::REMEMBER_POSITION, str.str()); + set_error(reset_options::REMEMBER_REF, make_software_audit_fail_text(auditor, swaudit)); + return true; } } } //------------------------------------------------- -// load custom filters info from file -//------------------------------------------------- - -void menu_select_software::load_sw_custom_filters() -{ - // attempt to open the output file - emu_file file(ui().options().ui_path(), OPEN_FLAG_READ); - if (file.open("custom_", m_driver.name, "_filter.ini") == osd_file::error::NONE) - { - software_filter::ptr flt(software_filter::create(file, m_filter_data)); - if (flt) - m_filters.emplace(flt->get_type(), std::move(flt)); - file.close(); - } -} - -//------------------------------------------------- -// find approximate matches -//------------------------------------------------- - -void menu_select_software::find_matches() -{ - // ensure search list is populated - if (m_searchlist.empty()) - { - m_searchlist.reserve(m_swinfo.size()); - std::copy(m_swinfo.begin(), m_swinfo.end(), std::back_inserter(m_searchlist)); - } - - // update search - const std::u32string ucs_search(ustr_from_utf8(normalize_unicode(m_search, unicode_normalization_form::D, true))); - for (search_item &entry : m_searchlist) - entry.set_penalty(ucs_search); - - // sort according to edit distance - std::stable_sort( - m_searchlist.begin(), - m_searchlist.end(), - [] (search_item const &lhs, search_item const &rhs) { return lhs.penalty < rhs.penalty; }); -} - -//------------------------------------------------- // draw left box //------------------------------------------------- -float menu_select_software::draw_left_panel(float x1, float y1, float x2, float y2) +void menu_select_software::draw_left_panel(u32 flags) { - return menu_select_launch::draw_left_panel<software_filter>(m_filter_type, m_filters, x1, y1, x2, y2); + return menu_select_launch::draw_left_panel<software_filter>(flags, m_data->filter_type(), m_data->filters()); } @@ -581,17 +663,17 @@ render_texture *menu_select_software::get_icon_texture(int linenum, void *select if (swinfo->startempty) return nullptr; - icon_cache::iterator icon(m_icons.find(swinfo)); - if ((m_icons.end() == icon) || !icon->second.texture) + icon_cache::iterator icon(m_data->icons().find(swinfo)); + if ((m_data->icons().end() == icon) || !icon->second.texture) { std::map<std::string, std::string>::iterator paths(m_icon_paths.find(swinfo->listname)); if (m_icon_paths.end() == paths) paths = m_icon_paths.emplace(swinfo->listname, make_icon_paths(swinfo->listname.c_str())).first; // allocate an entry or allocate a texture on forced redraw - if (m_icons.end() == icon) + if (m_data->icons().end() == icon) { - icon = m_icons.emplace(swinfo, texture_ptr(machine().render().texture_alloc(), machine().render())).first; + icon = m_data->icons().emplace(swinfo, texture_ptr(machine().render().texture_alloc(), machine().render())).first; } else { @@ -601,12 +683,12 @@ render_texture *menu_select_software::get_icon_texture(int linenum, void *select bitmap_argb32 tmp; emu_file snapfile(std::string(paths->second), OPEN_FLAG_READ); - if (snapfile.open(std::string(swinfo->shortname), ".ico") == osd_file::error::NONE) + if (!snapfile.open(std::string(swinfo->shortname) + ".ico")) { render_load_ico_highest_detail(snapfile, tmp); snapfile.close(); } - if (!tmp.valid() && !swinfo->parentname.empty() && (snapfile.open(std::string(swinfo->parentname), ".ico") == osd_file::error::NONE)) + if (!tmp.valid() && !swinfo->parentname.empty() && !snapfile.open(std::string(swinfo->parentname) + ".ico")) { render_load_ico_highest_detail(snapfile, tmp); snapfile.close(); @@ -623,69 +705,64 @@ render_texture *menu_select_software::get_icon_texture(int linenum, void *select // get selected software and/or driver //------------------------------------------------- -void menu_select_software::get_selection(ui_software_info const *&software, game_driver const *&driver) const +void menu_select_software::get_selection(ui_software_info const *&software, ui_system_info const *&system) const { software = reinterpret_cast<ui_software_info const *>(get_selection_ptr()); - driver = software ? software->driver : nullptr; + system = &m_system; +} + + +void menu_select_software::show_config_menu(int index) +{ + menu::stack_push<menu_machine_configure>(ui(), container(), m_system, nullptr); } void menu_select_software::make_topbox_text(std::string &line0, std::string &line1, std::string &line2) const { // determine the text for the header - int vis_item = !m_search.empty() ? visible_items : (m_has_empty_start ? visible_items - 1 : visible_items); - line0 = string_format(_("%1$s %2$s ( %3$d / %4$d software packages )"), emulator_info::get_appname(), bare_build_version, vis_item, m_swinfo.size() - 1); - line1 = string_format(_("Driver: \"%1$s\" software list "), m_driver.type.fullname()); + int vis_item = !m_search.empty() ? m_available_items : (m_available_items - 1); + line0 = string_format(_("%1$s %2$s ( %3$d / %4$d software packages )"), emulator_info::get_appname(), bare_build_version, vis_item, m_data->swinfo().size() - 1); + line1 = string_format(_("%1$s - select software"), m_system.description); - filter_map::const_iterator const it(m_filters.find(m_filter_type)); - char const *const filter((m_filters.end() != it) ? it->second->filter_text() : nullptr); + software_filter const *const it(m_data->current_filter()); + char const *const filter(it ? it->filter_text() : nullptr); if (filter) - line2 = string_format(_("%1$s: %2$s - Search: %3$s_"), it->second->display_name(), filter, m_search); + line2 = string_format(_("%1$s: %2$s - Search: %3$s_"), it->display_name(), filter, m_search); else line2 = string_format(_("Search: %1$s_"), m_search); } -std::string menu_select_software::make_driver_description(game_driver const &driver) const +std::string menu_select_software::make_software_description(ui_software_info const &software, ui_system_info const *system) const { - // first line is game description - return string_format(_("%1$-.100s"), driver.type.fullname()); + // show list/item to make it less confusing when there are multiple lists mixed + return string_format(_("Software list/item: %1$s:%2$s"), software.listname, software.shortname); } -std::string menu_select_software::make_software_description(ui_software_info const &software) const +void menu_select_software::filter_selected(int index) { - // first line is long name - return string_format(_("%1$-.100s"), software.longname); -} + assert((software_filter::FIRST <= index) && (software_filter::LAST >= index)); - -void menu_select_software::filter_selected() -{ - if ((software_filter::FIRST <= m_filter_highlight) && (software_filter::LAST >= m_filter_highlight)) - { - filter_map::const_iterator it(m_filters.find(software_filter::type(m_filter_highlight))); - if (m_filters.end() == it) - it = m_filters.emplace(software_filter::type(m_filter_highlight), software_filter::create(software_filter::type(m_filter_highlight), m_filter_data)).first; - it->second->show_ui( - ui(), - container(), - [this] (software_filter &filter) + m_data->get_filter(software_filter::type(index)).show_ui( + ui(), + container(), + [this] (software_filter &filter) + { + software_filter::type const new_type(filter.get_type()); + if (software_filter::CUSTOM == new_type) { - software_filter::type const new_type(filter.get_type()); - if (software_filter::CUSTOM == new_type) + emu_file file(ui().options().ui_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); + if (!file.open(util::string_format("custom_%s_filter.ini", m_system.driver->name))) { - emu_file file(ui().options().ui_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); - if (file.open("custom_", m_driver.name, "_filter.ini") == osd_file::error::NONE) - { - filter.save_ini(file, 0); - file.close(); - } + filter.save_ini(file, 0); + file.close(); } - m_filter_type = new_type; - reset(reset_options::SELECT_FIRST); - }); - } + } + m_data->set_filter_type(new_type); + reset(reset_options::REMEMBER_REF); + }); } } // namespace ui diff --git a/src/frontend/mame/ui/selsoft.h b/src/frontend/mame/ui/selsoft.h index 1bfdd9c1e48..e250a608c08 100644 --- a/src/frontend/mame/ui/selsoft.h +++ b/src/frontend/mame/ui/selsoft.h @@ -15,7 +15,10 @@ #include "ui/selmenu.h" #include "ui/utils.h" +#include "lrucache.h" + #include <map> +#include <memory> #include <string> #include <vector> @@ -26,68 +29,49 @@ namespace ui { class menu_select_software : public menu_select_launch { public: - menu_select_software(mame_ui_manager &mui, render_container &container, game_driver const &driver); + menu_select_software(mame_ui_manager &mui, render_container &container, ui_system_info const &system); virtual ~menu_select_software() override; +protected: + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + + virtual void menu_deactivated() override; + private: using filter_map = std::map<software_filter::type, software_filter::ptr>; using icon_cache = texture_lru<ui_software_info const *>; - struct search_item - { - search_item(ui_software_info const &s); - search_item(search_item const &) = default; - search_item(search_item &&) = default; - search_item &operator=(search_item const &) = default; - search_item &operator=(search_item &&) = default; - void set_penalty(std::u32string const &search); - - std::reference_wrapper<ui_software_info const> software; - std::u32string ucs_shortname; - std::u32string ucs_longname; - double penalty; - }; + struct search_item; + class machine_data; - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; + virtual bool handle(event const *ev) override; // drawing - virtual float draw_left_panel(float x1, float y1, float x2, float y2) override; + virtual void draw_left_panel(u32 flags) override; virtual render_texture *get_icon_texture(int linenum, void *selectedref) override; // get selected software and/or driver - virtual void get_selection(ui_software_info const *&software, game_driver const *&driver) const override; + virtual void get_selection(ui_software_info const *&software, ui_system_info const *&system) const override; + virtual void show_config_menu(int index) override; // text for main top/bottom panels virtual void make_topbox_text(std::string &line0, std::string &line1, std::string &line2) const override; - virtual std::string make_driver_description(game_driver const &driver) const override; - virtual std::string make_software_description(ui_software_info const &software) const override; + virtual std::string make_software_description(ui_software_info const &software, ui_system_info const *system) const override; // filter navigation - virtual void filter_selected() override; + virtual void filter_selected(int index) override; // toolbar virtual void inkey_export() override { throw false; } - void build_software_list(); - void find_matches(); - void load_sw_custom_filters(); - // handlers - void inkey_select(const event *menu_event); - - virtual void general_info(const game_driver *driver, std::string &buffer) override { } + bool inkey_select(const event *menu_event); std::map<std::string, std::string> m_icon_paths; - icon_cache m_icons; - game_driver const &m_driver; - bool m_has_empty_start; - software_filter_data m_filter_data; - filter_map m_filters; - software_filter::type m_filter_type; - - std::vector<ui_software_info> m_swinfo; - std::vector<search_item> m_searchlist; + ui_system_info const &m_system; + std::shared_ptr<machine_data> m_data; + std::vector<std::reference_wrapper<ui_software_info const> > m_displaylist; }; diff --git a/src/frontend/mame/ui/simpleselgame.cpp b/src/frontend/mame/ui/simpleselgame.cpp index a2334a3485d..1034a368f1b 100644 --- a/src/frontend/mame/ui/simpleselgame.cpp +++ b/src/frontend/mame/ui/simpleselgame.cpp @@ -13,18 +13,21 @@ #include "ui/simpleselgame.h" #include "ui/info.h" -#include "ui/miscmenu.h" #include "ui/optsmenu.h" #include "ui/ui.h" #include "ui/utils.h" +#include "infoxml.h" + #include "audit.h" #include "drivenum.h" #include "emuopts.h" +#include "fileio.h" #include "mame.h" #include "uiinput.h" -#include <ctype.h> +#include <cctype> +#include <string_view> namespace ui { @@ -35,15 +38,18 @@ namespace ui { simple_menu_select_game::simple_menu_select_game(mame_ui_manager &mui, render_container &container, const char *gamename) : menu(mui, container) - , m_error(false), m_rerandomize(false) + , m_nomatch(false), m_error(false), m_rerandomize(false) , m_search() , m_driverlist(driver_list::total() + 1) , m_drivlist() , m_cached_driver(nullptr) - , m_cached_flags(machine_flags::NOT_WORKING) + , m_cached_machine_flags(machine_flags::ROT0) + , m_cached_emulation_flags(device_t::flags::NOT_WORKING) , m_cached_unemulated(device_t::feature::NONE), m_cached_imperfect(device_t::feature::NONE) , m_cached_color(ui().colors().background_color()) { + set_process_flags(PROCESS_IGNOREPAUSE); + set_needs_prev_menu_item(false); build_driver_list(); if (gamename) m_search.assign(gamename); @@ -83,7 +89,7 @@ void simple_menu_select_game::build_driver_list() const char *src; // build a name for it - for (src = dir->name; *src != 0 && *src != '.' && dst < &drivername[ARRAY_LENGTH(drivername) - 1]; src++) + for (src = dir->name; *src != 0 && *src != '.' && dst < &drivername[std::size(drivername) - 1]; src++) *dst++ = tolower((uint8_t)*src); *dst = 0; @@ -108,48 +114,38 @@ void simple_menu_select_game::build_driver_list() // handle - handle the game select menu //------------------------------------------------- -void simple_menu_select_game::handle() +bool simple_menu_select_game::handle(event const *ev) { - // ignore pause keys by swallowing them before we process the menu - machine().ui_input().pressed(IPT_UI_PAUSE); + if (!ev) + return false; - // process the menu - const event *menu_event = process(0); - if (menu_event && menu_event->itemref) + if (m_error) { - if (m_error) - { - // reset the error on any future menu_event - m_error = false; - machine().ui_input().reset(); - } - else - { - // handle selections - switch(menu_event->iptkey) - { - case IPT_UI_SELECT: - inkey_select(menu_event); - break; - case IPT_UI_CANCEL: - inkey_cancel(); - break; - case IPT_SPECIAL: - inkey_special(menu_event); - break; - } - } + // reset the error on any subsequent menu event + m_error = false; + machine().ui_input().reset(); + return true; } - // if we're in an error state, overlay an error message - if (m_error) + // handle selections + bool changed = false; + switch (ev->iptkey) { - ui().draw_text_box( - container(), - _("The selected game is missing one or more required ROM or CHD images. " - "Please select a different game.\n\nPress any key to continue."), - ui::text_layout::CENTER, 0.5f, 0.5f, UI_RED_COLOR); + case IPT_UI_SELECT: + changed = inkey_select(*ev); + break; + case IPT_UI_CANCEL: + inkey_cancel(); + break; + case IPT_UI_PASTE: + if (paste_text(m_search, uchar_is_printable)) + reset(reset_options::SELECT_FIRST); + break; + case IPT_SPECIAL: + inkey_special(*ev); + break; } + return changed; } @@ -157,21 +153,24 @@ void simple_menu_select_game::handle() // inkey_select //------------------------------------------------- -void simple_menu_select_game::inkey_select(const event *menu_event) +bool simple_menu_select_game::inkey_select(const event &menu_event) { - const game_driver *driver = (const game_driver *)menu_event->itemref; + const game_driver *driver = (const game_driver *)menu_event.itemref; - // special case for configure inputs - if ((uintptr_t)driver == 1) + if ((uintptr_t)driver == 1) // special case for configure inputs { menu::stack_push<menu_simple_game_options>( ui(), container(), [this] () { reset(reset_options::SELECT_FIRST); }); + return false; } - - // anything else is a driver - else + else if (!driver) // special case for previous menu + { + stack_pop(); + return false; + } + else // anything else is a driver { // audit the game first to see if we're going to work driver_enumerator enumerator(machine().options(), *driver); @@ -185,12 +184,14 @@ void simple_menu_select_game::inkey_select(const event *menu_event) mame_machine_manager::instance()->schedule_new_driver(*driver); machine().schedule_hard_reset(); stack_reset(); + return false; } else { // otherwise, display an error reset(reset_options::REMEMBER_REF); m_error = true; + return true; } } } @@ -206,6 +207,7 @@ void simple_menu_select_game::inkey_cancel() if (!m_search.empty()) { m_search.clear(); + m_rerandomize = true; reset(reset_options::SELECT_FIRST); } } @@ -215,11 +217,11 @@ void simple_menu_select_game::inkey_cancel() // inkey_special - typed characters append to the buffer //------------------------------------------------- -void simple_menu_select_game::inkey_special(const event *menu_event) +void simple_menu_select_game::inkey_special(const event &menu_event) { // typed characters append to the buffer - size_t old_size = m_search.size(); - if (input_character(m_search, menu_event->unichar, uchar_is_printable)) + size_t const old_size = m_search.size(); + if (input_character(m_search, menu_event.unichar, uchar_is_printable)) { if (m_search.size() < old_size) m_rerandomize = true; @@ -232,7 +234,7 @@ void simple_menu_select_game::inkey_special(const event *menu_event) // populate - populate the game select menu //------------------------------------------------- -void simple_menu_select_game::populate(float &customtop, float &custombottom) +void simple_menu_select_game::populate() { int matchcount; int curitem; @@ -241,46 +243,56 @@ void simple_menu_select_game::populate(float &customtop, float &custombottom) matchcount++; // if nothing there, add a single multiline item and return - if (matchcount == 0) - { - std::string txt = string_format( - _("No machines found. Please check the rompath specified in the %1$s.ini file.\n\n" - "If this is your first time using %2$s, please see the config.txt file in " - "the docs directory for information on configuring %2$s."), - emulator_info::get_configname(), - emulator_info::get_appname()); - item_append(txt, "", FLAG_MULTILINE | FLAG_REDTEXT, nullptr); - return; - } + m_nomatch = !matchcount; // otherwise, rebuild the match list - assert(m_drivlist != nullptr); - if (!m_search.empty() || m_matchlist[0] == -1 || m_rerandomize) - m_drivlist->find_approximate_matches(m_search, matchcount, m_matchlist); - m_rerandomize = false; - - // iterate over entries - for (curitem = 0; curitem < matchcount; curitem++) + if (matchcount) { - int curmatch = m_matchlist[curitem]; - if (curmatch != -1) + assert(m_drivlist != nullptr); + if (!m_search.empty() || m_matchlist[0] == -1 || m_rerandomize) + m_drivlist->find_approximate_matches(m_search, matchcount, m_matchlist); + m_rerandomize = false; + + // iterate over entries + for (curitem = 0; curitem < matchcount; curitem++) { - int cloneof = m_drivlist->non_bios_clone(curmatch); - item_append(m_drivlist->driver(curmatch).name, m_drivlist->driver(curmatch).type.fullname(), (cloneof == -1) ? 0 : FLAG_INVERT, (void *)&m_drivlist->driver(curmatch)); + int curmatch = m_matchlist[curitem]; + if (curmatch != -1) + { + int cloneof = m_drivlist->non_bios_clone(curmatch); + item_append( + m_drivlist->driver(curmatch).type.fullname(), + m_drivlist->driver(curmatch).name, + (cloneof == -1) ? 0 : FLAG_INVERT, + (void *)&m_drivlist->driver(curmatch)); + } } + item_append(menu_item_type::SEPARATOR); } // if we're forced into this, allow general input configuration as well if (stack_has_special_main_menu()) { - item_append(menu_item_type::SEPARATOR); - item_append(_("Configure Options"), "", 0, (void *)1); - skip_main_items = 1; + item_append(_("Configure Options"), 0, (void *)1); + item_append(_("Exit"), 0, nullptr); } + else + { + item_append(_("Return to Previous Menu"), 0, nullptr); + } +} + + +//------------------------------------------------- +// recompute_metrics - recompute metrics +//------------------------------------------------- + +void simple_menu_select_game::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + menu::recompute_metrics(width, height, aspect); // configure the custom rendering - customtop = ui().get_line_height() + 3.0f * ui().box_tb_border(); - custombottom = 4.0f * ui().get_line_height() + 3.0f * ui().box_tb_border(); + set_custom_space(line_height() + 3.0f * tb_border(), 5.0f * line_height() + 3.0f * tb_border()); } @@ -288,105 +300,152 @@ void simple_menu_select_game::populate(float &customtop, float &custombottom) // custom_render - perform our special rendering //------------------------------------------------- -void simple_menu_select_game::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void simple_menu_select_game::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { - const game_driver *driver; - std::string tempbuf[5]; - - // display the current typeahead - if (!m_search.empty()) - tempbuf[0] = string_format(_("Type name or select: %1$s_"), m_search); + if (m_nomatch) + { + // if no matches, display the error message + ui().draw_text_box( + container(), + string_format( + _("No system ROMs found. Please check the rompath setting specified in the %1$s.ini file.\n\n" + "If this is your first time using %2$s, please see the %2$s.pdf file in " + "the docs folder for information on setting up and using %2$s."), + emulator_info::get_configname(), + emulator_info::get_appname()), + text_layout::text_justify::CENTER, + 0.5f, origy2 + tb_border() + (0.5f * (bottom - tb_border())), + UI_RED_COLOR); + return; + } else - tempbuf[0] = _("Type name or select: (random)"); - - // draw the top box - draw_text_box( - tempbuf, tempbuf + 1, - origx1, origx2, origy1 - top, origy1 - ui().box_tb_border(), - ui::text_layout::CENTER, ui::text_layout::TRUNCATE, false, - ui().colors().text_color(), ui().colors().background_color(), 1.0f); - - // determine the text to render below - driver = ((uintptr_t)selectedref > skip_main_items) ? (const game_driver *)selectedref : nullptr; - if (driver) { - // first line is game name - tempbuf[0] = string_format(_("%1$-.100s"), driver->type.fullname()); - - // next line is year, manufacturer - tempbuf[1] = string_format(_("%1$s, %2$-.100s"), driver->year, driver->manufacturer); - - // next line source path - tempbuf[2] = string_format(_("Driver: %1$-.100s"), core_filename_extract_base(driver->type.source())); + std::string tempbuf[5]; - // update cached values if selection changed - if (driver != m_cached_driver) + // display the current typeahead + if (!m_search.empty()) + tempbuf[0] = string_format(_("Type name or select: %1$s_"), m_search); + else + tempbuf[0] = _("Type name or select: (random)"); + + // draw the top box + draw_text_box( + tempbuf, tempbuf + 1, + origx1, origx2, origy1 - top, origy1 - tb_border(), + text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, false, + ui().colors().text_color(), ui().colors().background_color()); + + // determine the text to render below + game_driver const *const driver = (uintptr_t(selectedref) > 1) ? (const game_driver *)selectedref : nullptr; + if (driver) { - emu_options clean_options; - machine_static_info const info(ui().options(), machine_config(*driver, clean_options)); - m_cached_driver = driver; - m_cached_flags = info.machine_flags(); - m_cached_unemulated = info.unemulated_features(); - m_cached_imperfect = info.imperfect_features(); - m_cached_color = info.status_color(); - } + // first line is game name + tempbuf[0] = string_format(_("%1$-.100s"), driver->type.fullname()); - // next line is overall driver status - if (m_cached_flags & machine_flags::NOT_WORKING) - tempbuf[3] = _("Overall: NOT WORKING"); - else if ((m_cached_unemulated | m_cached_imperfect) & device_t::feature::PROTECTION) - tempbuf[3] = _("Overall: Unemulated Protection"); - else - tempbuf[3] = _("Overall: Working"); + // next line is year, manufacturer + tempbuf[1] = string_format(_("%1$s, %2$-.100s"), driver->year, driver->manufacturer); - // next line is graphics, sound status - if (m_cached_unemulated & device_t::feature::GRAPHICS) - tempbuf[4] = _("Graphics: Unimplemented, "); - else if ((m_cached_unemulated | m_cached_imperfect) & (device_t::feature::GRAPHICS | device_t::feature::PALETTE)) - tempbuf[4] = _("Graphics: Imperfect, "); - else - tempbuf[4] = _("Graphics: OK, "); - - if (m_cached_flags & machine_flags::NO_SOUND_HW) - tempbuf[4].append(_("Sound: None")); - else if (m_cached_unemulated & device_t::feature::SOUND) - tempbuf[4].append(_("Sound: Unimplemented")); - else if (m_cached_imperfect & device_t::feature::SOUND) - tempbuf[4].append(_("Sound: Imperfect")); - else - tempbuf[4].append(_("Sound: OK")); - } - else - { - const char *s = emulator_info::get_copyright(); - unsigned line = 0; + // next line source path + tempbuf[2] = string_format(_("Source file: %1$s"), info_xml_creator::format_sourcefile(driver->type.source())); + + // update cached values if selection changed + if (driver != m_cached_driver) + { + emu_options clean_options; + machine_static_info const info(ui().options(), machine_config(*driver, clean_options)); + m_cached_driver = driver; + m_cached_machine_flags = info.machine_flags(); + m_cached_emulation_flags = info.emulation_flags(); + m_cached_unemulated = info.unemulated_features(); + m_cached_imperfect = info.imperfect_features(); + m_cached_color = info.status_color(); + } - // first line is version string - tempbuf[line++] = string_format("%s %s", emulator_info::get_appname(), build_version); + // next line is overall driver status + if (m_cached_emulation_flags & device_t::flags::NOT_WORKING) + tempbuf[3] = _("Status: NOT WORKING"); + else if ((m_cached_unemulated | m_cached_imperfect) & device_t::feature::PROTECTION) + tempbuf[3] = _("Status: Unemulated Protection"); + else + tempbuf[3] = _("Status: Working"); - // output message - while (line < ARRAY_LENGTH(tempbuf)) + // next line is graphics, sound status + if (m_cached_unemulated & device_t::feature::GRAPHICS) + tempbuf[4] = _("Graphics: Unimplemented, "); + else if ((m_cached_unemulated | m_cached_imperfect) & (device_t::feature::GRAPHICS | device_t::feature::PALETTE)) + tempbuf[4] = _("Graphics: Imperfect, "); + else + tempbuf[4] = _("Graphics: OK, "); + + if (m_cached_machine_flags & machine_flags::NO_SOUND_HW) + tempbuf[4].append(_("Sound: None")); + else if (m_cached_unemulated & device_t::feature::SOUND) + tempbuf[4].append(_("Sound: Unimplemented")); + else if (m_cached_imperfect & device_t::feature::SOUND) + tempbuf[4].append(_("Sound: Imperfect")); + else + tempbuf[4].append(_("Sound: OK")); + } + else { - if (!(*s == 0 || *s == '\n')) - tempbuf[line].push_back(*s); + std::string_view s = emulator_info::get_copyright(); + unsigned line = 0; + + // first line is version string + tempbuf[line++] = string_format("%s %s", emulator_info::get_appname(), build_version); - if (*s == '\n') + // output message + while (line < std::size(tempbuf)) { - line++; - s++; - } else if (*s != 0) - s++; - else - line++; + auto const found = s.find('\n'); + if (std::string::npos != found) + { + tempbuf[line++] = s.substr(0, found); + s.remove_prefix(found + 1); + } + else + { + tempbuf[line++] = s; + s = std::string_view(); + } + } } + + // draw the bottom box + draw_text_box( + std::begin(tempbuf), std::end(tempbuf), + origx1, origx2, origy2 + tb_border(), origy2 + bottom, + text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, true, + ui().colors().text_color(), driver ? m_cached_color : ui().colors().background_color()); } - // draw the bottom box - draw_text_box( - tempbuf, tempbuf + 4, - origx1, origx2, origy2 + ui().box_tb_border(), origy2 + bottom, - ui::text_layout::CENTER, ui::text_layout::TRUNCATE, true, - ui().colors().text_color(), driver ? m_cached_color : ui().colors().background_color(), 1.0f); + // if we're in an error state, overlay an error message + if (m_error) + { + ui().draw_text_box( + container(), + _("The selected system is missing one or more required ROMs/disk images. " + "Please select a different system.\n\nPress any key to continue."), + text_layout::text_justify::CENTER, 0.5f, 0.5f, UI_RED_COLOR); + } +} + + +//------------------------------------------------- +// custom_pointer_updated - override pointer +// handling +//------------------------------------------------- + +std::tuple<int, bool, bool> simple_menu_select_game::custom_pointer_updated(bool changed, ui_event const &uievt) +{ + // only override mouse handling when error message is visible + if (!m_error || !uievt.pointer_buttons) + return std::make_tuple(IPT_INVALID, false, false); + + // primary click dismisses the message + if ((uievt.pointer_pressed & 0x01) && !(uievt.pointer_buttons & ~u32(0x01))) + m_error = false; + return std::make_tuple(IPT_INVALID, true, !m_error); } @@ -400,13 +459,10 @@ void simple_menu_select_game::force_game_select(mame_ui_manager &mui, render_con char *gamename = (char *)mui.machine().options().system_name(); // reset the menu stack - menu::stack_reset(mui.machine()); - - // add the quit entry followed by the game select entry - menu::stack_push_special_main<menu_quit_game>(mui, container); - menu::stack_push<simple_menu_select_game>(mui, container, gamename); - // force the menus on + // drop any existing menus and start the system selection menu + menu::stack_reset(mui); + menu::stack_push_special_main<simple_menu_select_game>(mui, container, gamename); mui.show_menu(); // make sure MAME is paused diff --git a/src/frontend/mame/ui/simpleselgame.h b/src/frontend/mame/ui/simpleselgame.h index e56dac8185b..14e3b34d74b 100644 --- a/src/frontend/mame/ui/simpleselgame.h +++ b/src/frontend/mame/ui/simpleselgame.h @@ -15,8 +15,10 @@ #include "menu.h" + class driver_enumerator; + namespace ui { class simple_menu_select_game : public menu @@ -29,22 +31,25 @@ public: static void force_game_select(mame_ui_manager &mui, render_container &container); protected: - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; - virtual bool menu_has_search_active() override { return !m_search.empty(); } + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) override; + virtual bool custom_ui_back() override { return !m_search.empty(); } + virtual std::tuple<int, bool, bool> custom_pointer_updated(bool changed, ui_event const &uievt) override; private: enum { VISIBLE_GAMES_IN_LIST = 15 }; - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; + virtual bool handle(event const *ev) override; // internal methods void build_driver_list(); - void inkey_select(const event *menu_event); + bool inkey_select(const event &menu_event); void inkey_cancel(); - void inkey_special(const event *menu_event); + void inkey_special(const event &menu_event); // internal state + bool m_nomatch; bool m_error; bool m_rerandomize; std::string m_search; @@ -54,7 +59,8 @@ private: // cached driver flags const game_driver * m_cached_driver; - machine_flags::type m_cached_flags; + machine_flags::type m_cached_machine_flags; + device_t::flags_type m_cached_emulation_flags; device_t::feature_type m_cached_unemulated; device_t::feature_type m_cached_imperfect; rgb_t m_cached_color; diff --git a/src/frontend/mame/ui/slider.h b/src/frontend/mame/ui/slider.h index 7785518d093..f39b3dde7ce 100644 --- a/src/frontend/mame/ui/slider.h +++ b/src/frontend/mame/ui/slider.h @@ -14,23 +14,30 @@ #pragma once -#include "sliderchangednotifier.h" - #include <functional> +#include <string> #define SLIDER_NOCHANGE 0x12345678 -typedef std::function<std::int32_t (running_machine &, void *, int, std::string *, std::int32_t)> slider_update; +typedef std::function<std::int32_t (std::string *, std::int32_t)> slider_update; struct slider_state { + slider_state(const std::string &title, std::int32_t min, std::int32_t def, std::int32_t max, std::int32_t inc, slider_update func) + : update(func), minval(min), defval(def), maxval(max), incval(inc), description(title) + { + } + + slider_state(std::string &&title, std::int32_t min, std::int32_t def, std::int32_t max, std::int32_t inc, slider_update func) + : update(func), minval(min), defval(def), maxval(max), incval(inc), description(std::move(title)) + { + } + slider_update update; // callback - void * arg; // argument std::int32_t minval; // minimum value std::int32_t defval; // default value std::int32_t maxval; // maximum value std::int32_t incval; // increment value - int id; std::string description; // textual description }; diff --git a/src/frontend/mame/ui/sliderchangednotifier.h b/src/frontend/mame/ui/sliderchangednotifier.h deleted file mode 100644 index eedb39684f3..00000000000 --- a/src/frontend/mame/ui/sliderchangednotifier.h +++ /dev/null @@ -1,27 +0,0 @@ -// license:BSD-3-Clause -// copyright-holders:Ryan Holtz -//====================================================================== -// -// sliderchangednotifier.cpp - Interface for a slider-changed callback -// -//====================================================================== - -#ifndef MAME_FRONTEND_MAME_UI_SLIDERCHANGEDNOTIFIER_H -#define MAME_FRONTEND_MAME_UI_SLIDERCHANGEDNOTIFIER_H - -#pragma once - -#include <cstdint> -#include <string> - -class running_machine; - -class slider_changed_notifier -{ -public: - virtual ~slider_changed_notifier() { } - - virtual std::int32_t slider_changed(running_machine &machine, void *arg, int id, std::string *str, std::int32_t newval) = 0; -}; - -#endif // MAME_FRONTEND_MAME_UI_SLIDERCHANGEDNOTIFIER_H diff --git a/src/frontend/mame/ui/sliders.cpp b/src/frontend/mame/ui/sliders.cpp index 50c74c46497..2f92fe9e4fa 100644 --- a/src/frontend/mame/ui/sliders.cpp +++ b/src/frontend/mame/ui/sliders.cpp @@ -9,134 +9,164 @@ *********************************************************************/ #include "emu.h" +#include "ui/sliders.h" + +#include "ui/slider.h" +#include "ui/ui.h" #include "osdepend.h" -#include "ui/ui.h" -#include "ui/sliders.h" -#include "ui/slider.h" namespace ui { -menu_sliders::menu_sliders(mame_ui_manager &mui, render_container &container, bool menuless_mode) : menu(mui, container) + +menu_sliders::menu_sliders(mame_ui_manager &mui, render_container &container, bool menuless_mode) + : menu(mui, container) + , m_menuless_mode(menuless_mode) + , m_hidden(menuless_mode) { - m_menuless_mode = m_hidden = menuless_mode; + set_one_shot(menuless_mode); + set_needs_prev_menu_item(!menuless_mode); + set_process_flags(PROCESS_LR_REPEAT | (m_hidden ? PROCESS_CUSTOM_ONLY : 0)); + set_heading(_("Slider Controls")); } menu_sliders::~menu_sliders() { } + //------------------------------------------------- // menu_sliders - handle the sliders menu //------------------------------------------------- -void menu_sliders::handle() +bool menu_sliders::handle(event const *ev) { - const event *menu_event; + if (!ev) + return false; - // process the menu - menu_event = process(PROCESS_LR_REPEAT | (m_hidden ? PROCESS_CUSTOM_ONLY : 0)); - if (menu_event != nullptr) + if (ev->iptkey == IPT_UI_ON_SCREEN_DISPLAY) { - // handle keys if there is a valid item selected - if (menu_event->itemref != nullptr && menu_event->type == menu_item_type::SLIDER) + // toggle visibility + if (m_menuless_mode) { - const slider_state *slider = (const slider_state *)menu_event->itemref; - int32_t curvalue = slider->update(machine(), slider->arg, slider->id, nullptr, SLIDER_NOCHANGE); - int32_t increment = 0; - bool alt_pressed = machine().input().code_pressed(KEYCODE_LALT) || machine().input().code_pressed(KEYCODE_RALT); - bool ctrl_pressed = machine().input().code_pressed(KEYCODE_LCONTROL) || machine().input().code_pressed(KEYCODE_RCONTROL); - bool shift_pressed = machine().input().code_pressed(KEYCODE_LSHIFT) || machine().input().code_pressed(KEYCODE_RSHIFT); - - switch (menu_event->iptkey) - { - // toggle visibility - case IPT_UI_ON_SCREEN_DISPLAY: - if (m_menuless_mode) - stack_pop(); - else - m_hidden = !m_hidden; - break; - - // decrease value - case IPT_UI_LEFT: - if (alt_pressed && shift_pressed) - increment = -1; - if (alt_pressed) - increment = -(curvalue - slider->minval); - else if (shift_pressed) - increment = (slider->incval > 10) ? -(slider->incval / 10) : -1; - else if (ctrl_pressed) - increment = -slider->incval * 10; - else - increment = -slider->incval; - break; - - // increase value - case IPT_UI_RIGHT: - if (alt_pressed && shift_pressed) - increment = 1; - if (alt_pressed) - increment = slider->maxval - curvalue; - else if (shift_pressed) - increment = (slider->incval > 10) ? (slider->incval / 10) : 1; - else if (ctrl_pressed) - increment = slider->incval * 10; - else - increment = slider->incval; - break; - - // restore default - case IPT_UI_SELECT: - increment = slider->defval - curvalue; - break; - } + stack_pop(); + return false; + } + else + { + m_hidden = !m_hidden; + set_process_flags(PROCESS_LR_REPEAT | (m_hidden ? PROCESS_CUSTOM_ONLY : 0)); + return true; + } + } - // handle any changes - if (increment != 0) - { - int32_t newvalue = curvalue + increment; + // handle keys if there is a valid item selected + if (ev->itemref && (ev->item->type() == menu_item_type::SLIDER)) + { + const slider_state *slider = (const slider_state *)ev->itemref; + int32_t curvalue = slider->update(nullptr, SLIDER_NOCHANGE); + int32_t increment = 0; + bool const alt_pressed = machine().input().code_pressed(KEYCODE_LALT) || machine().input().code_pressed(KEYCODE_RALT); + bool const ctrl_pressed = machine().input().code_pressed(KEYCODE_LCONTROL) || machine().input().code_pressed(KEYCODE_RCONTROL); + bool const shift_pressed = machine().input().code_pressed(KEYCODE_LSHIFT) || machine().input().code_pressed(KEYCODE_RSHIFT); + + switch (ev->iptkey) + { + // decrease value + case IPT_UI_LEFT: + if (alt_pressed && shift_pressed) + increment = -1; + else if (alt_pressed) + increment = -(curvalue - slider->minval); + else if (shift_pressed) + increment = (slider->incval > 10) ? -(slider->incval / 10) : -1; + else if (ctrl_pressed) + increment = -slider->incval * 10; + else + increment = -slider->incval; + break; + + // increase value + case IPT_UI_RIGHT: + if (alt_pressed && shift_pressed) + increment = 1; + else if (alt_pressed) + increment = slider->maxval - curvalue; + else if (shift_pressed) + increment = (slider->incval > 10) ? (slider->incval / 10) : 1; + else if (ctrl_pressed) + increment = slider->incval * 10; + else + increment = slider->incval; + break; - // clamp within bounds - if (newvalue < slider->minval) - newvalue = slider->minval; - if (newvalue > slider->maxval) - newvalue = slider->maxval; + // restore default + case IPT_UI_CLEAR: + increment = slider->defval - curvalue; + break; + } - // update the slider and recompute the menu - slider->update(machine(), slider->arg, slider->id, nullptr, newvalue); - reset(reset_options::REMEMBER_REF); - } + // handle any changes + if (increment != 0) + { + int32_t newvalue = curvalue + increment; + + // clamp within bounds + if (newvalue < slider->minval) + newvalue = slider->minval; + if (newvalue > slider->maxval) + newvalue = slider->maxval; + + // update the slider and recompute the menu + slider->update(nullptr, newvalue); + if (m_menuless_mode) + ui().get_session_data<menu_sliders, void *>(nullptr) = ev->itemref; + reset(reset_options::REMEMBER_REF); } - // if we are selecting an invalid item and we are hidden, skip to the next one - else if (m_hidden) + // slider changes trigger an item reset as they can change the available sliders + return false; + } + + // when highlighting an item that isn't a slider with the menu is hidden, skip to the next one + if (m_hidden) + { + if (ev->iptkey == IPT_UI_UP || ev->iptkey == IPT_UI_PAGE_UP) { // if we got here via up or page up, select the previous item - if (menu_event->iptkey == IPT_UI_UP || menu_event->iptkey == IPT_UI_PAGE_UP) + if (is_first_selected()) { - if (is_first_selected()) - select_last_item(); - else - { - set_selected_index(selected_index() - 1); - validate_selection(-1); - } + select_last_item(); } - + else + { + set_selected_index(selected_index() - 1); + validate_selection(-1); + } + return true; + } + else if (ev->iptkey == IPT_UI_DOWN || ev->iptkey == IPT_UI_PAGE_DOWN) + { // otherwise select the next item - else if (menu_event->iptkey == IPT_UI_DOWN || menu_event->iptkey == IPT_UI_PAGE_DOWN) + if (is_last_selected()) { - if (is_last_selected()) - select_first_item(); - else - { - set_selected_index(selected_index() + 1); - validate_selection(1); - } + select_first_item(); + } + else + { + set_selected_index(selected_index() + 1); + validate_selection(1); } + return true; + } + else + { + return false; } } + + // didn't do anything + return false; } @@ -145,24 +175,33 @@ void menu_sliders::handle() // menu //------------------------------------------------- -void menu_sliders::populate(float &customtop, float &custombottom) +void menu_sliders::populate() { std::string tempstring; // add UI sliders std::vector<menu_item> ui_sliders = ui().get_slider_list(); - for (menu_item item : ui_sliders) + for (const menu_item &item : ui_sliders) { - if (item.type == menu_item_type::SLIDER) + if (item.type() == menu_item_type::SLIDER) { - slider_state* slider = reinterpret_cast<slider_state *>(item.ref); - int32_t curval = slider->update(machine(), slider->arg, slider->id, &tempstring, SLIDER_NOCHANGE); - uint32_t flags = 0; - if (curval > slider->minval) - flags |= FLAG_LEFT_ARROW; - if (curval < slider->maxval) - flags |= FLAG_RIGHT_ARROW; - item_append(slider->description, tempstring, flags, (void *)slider, menu_item_type::SLIDER); + slider_state *const slider = reinterpret_cast<slider_state *>(item.ref()); + bool display(true); +#if 0 + // FIXME: this test should be reimplemented in a dedicated menu + if (slider->id >= SLIDER_ID_ADJUSTER && slider->id <= SLIDER_ID_ADJUSTER_LAST) + display = reinterpret_cast<ioport_field *>(slider->arg)->enabled(); +#endif + if (display) + { + int32_t curval = slider->update(&tempstring, SLIDER_NOCHANGE); + uint32_t flags = 0; + if (curval > slider->minval) + flags |= FLAG_LEFT_ARROW; + if (curval < slider->maxval) + flags |= FLAG_RIGHT_ARROW; + item_append(slider->description, tempstring, flags, (void *)slider, menu_item_type::SLIDER); + } } else { @@ -174,12 +213,12 @@ void menu_sliders::populate(float &customtop, float &custombottom) // add OSD options std::vector<menu_item> osd_sliders = machine().osd().get_slider_list(); - for (menu_item item : osd_sliders) + for (const menu_item &item : osd_sliders) { - if (item.type == menu_item_type::SLIDER) + if (item.type() == menu_item_type::SLIDER) { - slider_state* slider = reinterpret_cast<slider_state *>(item.ref); - int32_t curval = slider->update(machine(), slider->arg, slider->id, &tempstring, SLIDER_NOCHANGE); + slider_state* slider = reinterpret_cast<slider_state *>(item.ref()); + int32_t curval = slider->update(&tempstring, SLIDER_NOCHANGE); uint32_t flags = 0; if (curval > slider->minval) flags |= FLAG_LEFT_ARROW; @@ -193,28 +232,45 @@ void menu_sliders::populate(float &customtop, float &custombottom) } } - custombottom = 2.0f * ui().get_line_height() + 2.0f * ui().box_tb_border(); + // reselect last slider used in menuless mode + if (m_menuless_mode) + { + auto const ref = ui().get_session_data<menu_sliders, void *>(nullptr); + if (ref) + set_selection(ref); + } +} + + +//------------------------------------------------- +// recompute_metrics - recompute metrics +//------------------------------------------------- + +void menu_sliders::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + menu::recompute_metrics(width, height, aspect); + + set_custom_space(0.0f, 2.0f * line_height() + 2.0f * tb_border()); } + //------------------------------------------------- -// menu_sliders_custom_render - perform our special -// rendering +// custom_render - perform our special rendering //------------------------------------------------- -void menu_sliders::custom_render(void *selectedref, float top, float bottom, float x1, float y1, float x2, float y2) +void menu_sliders::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { const slider_state *curslider = (const slider_state *)selectedref; if (curslider != nullptr) { float bar_left, bar_area_top, bar_width, bar_area_height, bar_top, bar_bottom, default_x, current_x; - float line_height = ui().get_line_height(); float percentage, default_percentage; std::string tempstring; float text_height; int32_t curval; // determine the current value and text - curval = curslider->update(machine(), curslider->arg, curslider->id, &tempstring, SLIDER_NOCHANGE); + curval = curslider->update(&tempstring, SLIDER_NOCHANGE); // compute the current and default percentages percentage = (float)(curval - curslider->minval) / (float)(curslider->maxval - curslider->minval); @@ -224,24 +280,30 @@ void menu_sliders::custom_render(void *selectedref, float top, float bottom, flo tempstring.insert(0, " ").insert(0, curslider->description); // move us to the bottom of the screen, and expand to full width - y2 = 1.0f - ui().box_tb_border(); - y1 = y2 - bottom; - x1 = ui().box_lr_border(); - x2 = 1.0f - ui().box_lr_border(); + float y2 = 1.0f - tb_border(); + float y1 = y2 - bottom; + float x1 = lr_border(); + float x2 = 1.0f - lr_border(); // draw extra menu area ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color()); - y1 += ui().box_tb_border(); + y1 += tb_border(); // determine the text height - ui().draw_text_full(container(), tempstring.c_str(), 0, 0, x2 - x1 - 2.0f * ui().box_lr_border(), - ui::text_layout::CENTER, ui::text_layout::TRUNCATE, mame_ui_manager::NONE, rgb_t::white(), rgb_t::black(), nullptr, &text_height); + ui().draw_text_full( + container(), + tempstring, + 0, 0, x2 - x1 - 2.0f * lr_border(), + text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, + mame_ui_manager::NONE, rgb_t::white(), rgb_t::black(), + nullptr, &text_height, + line_height()); // draw the thermometer - bar_left = x1 + ui().box_lr_border(); + bar_left = x1 + lr_border(); bar_area_top = y1; - bar_width = x2 - x1 - 2.0f * ui().box_lr_border(); - bar_area_height = line_height; + bar_width = x2 - x1 - 2.0f * lr_border(); + bar_area_height = line_height(); // compute positions bar_top = bar_area_top + 0.125f * bar_area_height; @@ -261,35 +323,35 @@ void menu_sliders::custom_render(void *selectedref, float top, float bottom, flo container().add_line(default_x, bar_bottom, default_x, bar_area_top + bar_area_height, UI_LINE_WIDTH, ui().colors().border_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); // draw the actual text - ui().draw_text_full(container(), tempstring.c_str(), x1 + ui().box_lr_border(), y1 + line_height, x2 - x1 - 2.0f * ui().box_lr_border(), - ui::text_layout::CENTER, ui::text_layout::WORD, mame_ui_manager::NORMAL, ui().colors().text_color(), ui().colors().text_bg_color(), nullptr, &text_height); + draw_text_normal( + tempstring, + x1 + lr_border(), y1 + line_height(), x2 - x1 - 2.0f * lr_border(), + text_layout::text_justify::CENTER, text_layout::word_wrapping::WORD, + ui().colors().text_color()); } } //------------------------------------------------- -// slider_ui_handler - pushes the slider -// menu on the stack and hands off to the -// standard menu handler +// menu_activated - handle menu gaining focus //------------------------------------------------- -uint32_t menu_sliders::ui_handler(render_container &container, mame_ui_manager &mui) +void menu_sliders::menu_activated() { - uint32_t result; - - // if this is the first call, push the sliders menu - if (topmost_menu<menu_sliders>(mui.machine()) == nullptr) - menu::stack_push<menu_sliders>(mui, container, true); + // scripts or the other form of the menu could have changed something in the mean time + reset(reset_options::REMEMBER_POSITION); +} - // handle standard menus - result = menu::ui_handler(container, mui); - // if we are cancelled, pop the sliders menu - if (result == UI_HANDLER_CANCEL) - menu::stack_pop(mui.machine()); +//------------------------------------------------- +// menu_deactivated - handle menu losing focus +//------------------------------------------------- - menu_sliders *uim = topmost_menu<menu_sliders>(mui.machine()); - return uim && uim->m_menuless_mode ? 0 : UI_HANDLER_CANCEL; +void menu_sliders::menu_deactivated() +{ + // save active slider for next time in menuless mode + if (m_menuless_mode) + ui().get_session_data<menu_sliders, void *>(nullptr) = get_selection_ref(); } } // namespace ui diff --git a/src/frontend/mame/ui/sliders.h b/src/frontend/mame/ui/sliders.h index 4ef399173e3..a705204d082 100644 --- a/src/frontend/mame/ui/sliders.h +++ b/src/frontend/mame/ui/sliders.h @@ -2,44 +2,42 @@ // copyright-holders:Nicola Salmoria, Aaron Giles, Nathan Woods /*************************************************************************** - ui/miscmenu.h + ui/sliders.h Internal MAME menus for the user interface. ***************************************************************************/ -#pragma once - #ifndef MAME_FRONTEND_UI_SLIDERS_H #define MAME_FRONTEND_UI_SLIDERS_H +#pragma once + #include "ui/menu.h" + namespace ui { + class menu_sliders : public menu { public: menu_sliders(mame_ui_manager &mui, render_container &container, bool menuless_mode = false); virtual ~menu_sliders() override; - static uint32_t ui_handler(render_container &container, mame_ui_manager &mui); - protected: - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) override; + virtual void menu_activated() override; + virtual void menu_deactivated() override; private: - enum { - INPUT_GROUPS, - INPUT_SPECIFIC, - }; - - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; + virtual bool handle(event const *ev) override; - bool m_menuless_mode; + bool const m_menuless_mode; bool m_hidden; }; } // namespace ui -#endif /* MAME_FRONTEND_UI_SLIDERS_H */ +#endif // MAME_FRONTEND_UI_SLIDERS_H diff --git a/src/frontend/mame/ui/slotopt.cpp b/src/frontend/mame/ui/slotopt.cpp index 01493173731..10fbeed03c6 100644 --- a/src/frontend/mame/ui/slotopt.cpp +++ b/src/frontend/mame/ui/slotopt.cpp @@ -45,6 +45,7 @@ namespace ui { menu_slot_devices::menu_slot_devices(mame_ui_manager &mui, render_container &container) : menu(mui, container) { + set_heading(_("Slot Devices")); } //------------------------------------------------- @@ -85,7 +86,7 @@ device_slot_interface::slot_option const *menu_slot_devices::get_current_option( // set_slot_device //------------------------------------------------- -void menu_slot_devices::set_slot_device(device_slot_interface &slot, const char *val) +void menu_slot_devices::set_slot_device(device_slot_interface &slot, std::string_view val) { // we might change slot options; in the spirit of user friendliness, we should record all current // options @@ -115,7 +116,7 @@ void menu_slot_devices::set_slot_device(device_slot_interface &slot, const char void menu_slot_devices::record_current_options() { - for (device_slot_interface &slot : slot_interface_iterator(m_config->root_device())) + for (device_slot_interface &slot : slot_interface_enumerator(m_config->root_device())) { // we're doing this out of a desire to honor user-selectable options; therefore it only // makes sense to record values for selectable options @@ -170,14 +171,14 @@ bool menu_slot_devices::try_refresh_current_options() // populate //------------------------------------------------- -void menu_slot_devices::populate(float &customtop, float &custombottom) +void menu_slot_devices::populate() { // we need to keep our own copy of the machine_config because we // can change this out from under the caller m_config = std::make_unique<machine_config>(machine().system(), machine().options()); // cycle through all devices for this system - for (device_slot_interface &slot : slot_interface_iterator(m_config->root_device())) + for (device_slot_interface &slot : slot_interface_enumerator(m_config->root_device())) { // does this slot have any selectable options? bool has_selectable_options = slot.has_selectable_options(); @@ -200,10 +201,20 @@ void menu_slot_devices::populate(float &customtop, float &custombottom) item_append(slot.slot_name(), opt_name, item_flags, (void *)&slot); } item_append(menu_item_type::SEPARATOR); - item_append(_("Reset"), "", 0, ITEMREF_RESET); + item_append(_("Reset System"), 0, ITEMREF_RESET); +} + + +//------------------------------------------------- +// recompute metrics +//------------------------------------------------- + +void menu_slot_devices::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + menu::recompute_metrics(width, height, aspect); // leave space for the name of the current option at the bottom - custombottom = ui().get_line_height() + 3.0f * ui().box_tb_border(); + set_custom_space(0.0F, line_height() + 3.0F * tb_border()); } @@ -211,7 +222,7 @@ void menu_slot_devices::populate(float &customtop, float &custombottom) // custom_render - draw extra menu content //------------------------------------------------- -void menu_slot_devices::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_slot_devices::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { if (selectedref && (ITEMREF_RESET != selectedref)) { @@ -220,9 +231,9 @@ void menu_slot_devices::custom_render(void *selectedref, float top, float bottom char const *const text[] = { option ? option->devtype().fullname() : _("[empty slot]") }; draw_text_box( std::begin(text), std::end(text), - origx1, origx2, origy2 + ui().box_tb_border(), origy2 + bottom, - ui::text_layout::CENTER, ui::text_layout::TRUNCATE, false, - ui().colors().text_color(), ui().colors().background_color(), 1.0f); + origx1, origx2, origy2 + tb_border(), origy2 + bottom, + text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, false, + ui().colors().text_color(), ui().colors().background_color()); } } @@ -231,31 +242,30 @@ void menu_slot_devices::custom_render(void *selectedref, float top, float bottom // handle - process an input event //------------------------------------------------- -void menu_slot_devices::handle() +bool menu_slot_devices::handle(event const *ev) { - // process the menu - event const *const menu_event(process(0)); + if (!ev || !ev->itemref) + return false; - if (menu_event && menu_event->itemref != nullptr) + if (ev->itemref == ITEMREF_RESET) { - if (menu_event->itemref == ITEMREF_RESET) - { - if (menu_event->iptkey == IPT_UI_SELECT) - machine().schedule_hard_reset(); - } - else if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) - { - device_slot_interface *slot = (device_slot_interface *)menu_event->itemref; - rotate_slot_device(*slot, menu_event->iptkey == IPT_UI_LEFT ? step_t::PREVIOUS : step_t::NEXT); - } - else if (menu_event->iptkey == IPT_UI_SELECT) - { - device_slot_interface *slot = (device_slot_interface *)menu_event->itemref; - device_slot_interface::slot_option const *const option = get_current_option(*slot); - if (option) - menu::stack_push<menu_device_config>(ui(), container(), slot, option); - } + if (ev->iptkey == IPT_UI_SELECT) + machine().schedule_hard_reset(); + } + else if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT) + { + device_slot_interface *slot = (device_slot_interface *)ev->itemref; + rotate_slot_device(*slot, ev->iptkey == IPT_UI_LEFT ? step_t::PREVIOUS : step_t::NEXT); } + else if (ev->iptkey == IPT_UI_SELECT) + { + device_slot_interface *slot = (device_slot_interface *)ev->itemref; + device_slot_interface::slot_option const *const option = get_current_option(*slot); + if (option) + menu::stack_push<menu_device_config>(ui(), container(), slot, option); + } + + return false; // any changes require the menu to be rebuilt } @@ -320,7 +330,7 @@ void menu_slot_devices::rotate_slot_device(device_slot_interface &slot, menu_slo throw false; } - set_slot_device(slot, m_current_option_list_iter->c_str()); + set_slot_device(slot, *m_current_option_list_iter); } } // namespace ui diff --git a/src/frontend/mame/ui/slotopt.h b/src/frontend/mame/ui/slotopt.h index 703b9b16f23..91c78de7d1e 100644 --- a/src/frontend/mame/ui/slotopt.h +++ b/src/frontend/mame/ui/slotopt.h @@ -16,13 +16,19 @@ #include <unordered_map> + namespace ui { + class menu_slot_devices : public menu { public: menu_slot_devices(mame_ui_manager &mui, render_container &container); virtual ~menu_slot_devices() override; +protected: + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) override; + private: enum class step_t { @@ -30,12 +36,11 @@ private: PREVIOUS }; - virtual void populate(float &customtop, float &custombottom) override; - virtual void custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) override; - virtual void handle() override; + virtual void populate() override; + virtual bool handle(event const *ev) override; device_slot_interface::slot_option const *get_current_option(device_slot_interface &slot) const; - void set_slot_device(device_slot_interface &slot, const char *val); + void set_slot_device(device_slot_interface &slot, std::string_view val); void record_current_options(); bool try_refresh_current_options(); void rotate_slot_device(device_slot_interface &slot, step_t step); diff --git a/src/frontend/mame/ui/sndmenu.cpp b/src/frontend/mame/ui/sndmenu.cpp index 01397bd8f74..32a590b9730 100644 --- a/src/frontend/mame/ui/sndmenu.cpp +++ b/src/frontend/mame/ui/sndmenu.cpp @@ -9,12 +9,16 @@ *********************************************************************/ #include "emu.h" -#include "ui/ui.h" #include "ui/sndmenu.h" + #include "ui/selector.h" +#include "ui/ui.h" + #include "../osd/modules/lib/osdobj_common.h" // TODO: remove + namespace ui { + const int menu_sound_options::m_sound_rate[] = { 11025, 22050, 44100, 48000 }; //------------------------------------------------- @@ -23,13 +27,16 @@ const int menu_sound_options::m_sound_rate[] = { 11025, 22050, 44100, 48000 }; menu_sound_options::menu_sound_options(mame_ui_manager &mui, render_container &container) : menu(mui, container) { + set_heading(_("Sound Options")); + osd_options &options = downcast<osd_options &>(mui.machine().options()); m_sample_rate = mui.machine().options().sample_rate(); m_sound = (strcmp(options.sound(), OSDOPTVAL_NONE) && strcmp(options.sound(), "0")); m_samples = mui.machine().options().samples(); + m_compressor = mui.machine().options().compressor(); - int total = ARRAY_LENGTH(m_sound_rate); + int total = std::size(m_sound_rate); for (m_cur_rates = 0; m_cur_rates < total; m_cur_rates++) if (m_sample_rate == m_sound_rate[m_cur_rates]) @@ -40,65 +47,70 @@ menu_sound_options::menu_sound_options(mame_ui_manager &mui, render_container &c } //------------------------------------------------- -// dtor +// menu_dismissed //------------------------------------------------- -menu_sound_options::~menu_sound_options() +void menu_sound_options::menu_dismissed() { emu_options &moptions = machine().options(); - if (strcmp(moptions.value(OSDOPTION_SOUND),m_sound ? OSDOPTVAL_AUTO : OSDOPTVAL_NONE)!=0) - { + if (strcmp(moptions.value(OSDOPTION_SOUND), m_sound ? OSDOPTVAL_AUTO : OSDOPTVAL_NONE)) moptions.set_value(OSDOPTION_SOUND, m_sound ? OSDOPTVAL_AUTO : OSDOPTVAL_NONE, OPTION_PRIORITY_CMDLINE); - } - if (moptions.int_value(OPTION_SAMPLERATE)!=m_sound_rate[m_cur_rates]) - { + + if (moptions.bool_value(OPTION_COMPRESSOR) != m_compressor) + moptions.set_value(OPTION_COMPRESSOR, m_compressor, OPTION_PRIORITY_CMDLINE); + + if (moptions.int_value(OPTION_SAMPLERATE) != m_sound_rate[m_cur_rates]) moptions.set_value(OPTION_SAMPLERATE, m_sound_rate[m_cur_rates], OPTION_PRIORITY_CMDLINE); - } - if (moptions.bool_value(OPTION_SAMPLES)!=m_samples) - { + + if (moptions.bool_value(OPTION_SAMPLES) != m_samples) moptions.set_value(OPTION_SAMPLES, m_samples, OPTION_PRIORITY_CMDLINE); - } } //------------------------------------------------- // handle //------------------------------------------------- -void menu_sound_options::handle() +bool menu_sound_options::handle(event const *ev) { bool changed = false; // process the menu - const event *menu_event = process(0); - - if (menu_event != nullptr && menu_event->itemref != nullptr) + if (ev && ev->itemref) { - switch ((uintptr_t)menu_event->itemref) + switch ((uintptr_t)ev->itemref) { case ENABLE_SOUND: - if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT || menu_event->iptkey == IPT_UI_SELECT) + if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT || ev->iptkey == IPT_UI_SELECT) { m_sound = !m_sound; changed = true; } break; + case ENABLE_COMPRESSOR: + if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT || ev->iptkey == IPT_UI_SELECT) + { + m_compressor = !m_compressor; + changed = true; + } + break; + case SAMPLE_RATE: - if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) + if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT) { - (menu_event->iptkey == IPT_UI_LEFT) ? m_cur_rates-- : m_cur_rates++; + (ev->iptkey == IPT_UI_LEFT) ? m_cur_rates-- : m_cur_rates++; changed = true; } - else if (menu_event->iptkey == IPT_UI_SELECT) + else if (ev->iptkey == IPT_UI_SELECT) { - int total = ARRAY_LENGTH(m_sound_rate); + int total = std::size(m_sound_rate); std::vector<std::string> s_sel(total); for (int index = 0; index < total; index++) s_sel[index] = std::to_string(m_sound_rate[index]); menu::stack_push<menu_selector>( - ui(), container(), std::move(s_sel), m_cur_rates, + ui(), container(), _("Sample Rate"), std::move(s_sel), m_cur_rates, [this] (int selection) { m_cur_rates = selection; @@ -108,7 +120,7 @@ void menu_sound_options::handle() break; case ENABLE_SAMPLES: - if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT || menu_event->iptkey == IPT_UI_SELECT) + if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT || ev->iptkey == IPT_UI_SELECT) { m_samples = !m_samples; changed = true; @@ -117,8 +129,9 @@ void menu_sound_options::handle() } } - if (changed) + if (changed) // FIXME: most changes only require the item sub text to be updated reset(reset_options::REMEMBER_REF); + return false; } @@ -126,32 +139,17 @@ void menu_sound_options::handle() // populate //------------------------------------------------- -void menu_sound_options::populate(float &customtop, float &custombottom) +void menu_sound_options::populate() { - uint32_t arrow_flags = get_arrow_flags(uint16_t(0), uint16_t(ARRAY_LENGTH(m_sound_rate) - 1), m_cur_rates); + uint32_t arrow_flags = get_arrow_flags(uint16_t(0), uint16_t(std::size(m_sound_rate) - 1), m_cur_rates); m_sample_rate = m_sound_rate[m_cur_rates]; // add options items item_append_on_off(_("Sound"), m_sound, 0, (void *)(uintptr_t)ENABLE_SOUND); + item_append_on_off(_("Compressor"), m_compressor, 0, (void *)(uintptr_t)ENABLE_COMPRESSOR); item_append(_("Sample Rate"), string_format("%d", m_sample_rate), arrow_flags, (void *)(uintptr_t)SAMPLE_RATE); item_append_on_off(_("Use External Samples"), m_samples, 0, (void *)(uintptr_t)ENABLE_SAMPLES); item_append(menu_item_type::SEPARATOR); - - customtop = ui().get_line_height() + (3.0f * ui().box_tb_border()); -} - -//------------------------------------------------- -// perform our special rendering -//------------------------------------------------- - -void menu_sound_options::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) -{ - char const *const toptext[] = { _("Sound Options") }; - draw_text_box( - std::begin(toptext), std::end(toptext), - 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); } } // namespace ui diff --git a/src/frontend/mame/ui/sndmenu.h b/src/frontend/mame/ui/sndmenu.h index 4cecc5dc165..01d69f6d8ff 100644 --- a/src/frontend/mame/ui/sndmenu.h +++ b/src/frontend/mame/ui/sndmenu.h @@ -16,35 +16,37 @@ #include "ui/menu.h" namespace ui { + //------------------------------------------------- // class sound options menu //------------------------------------------------- + class menu_sound_options : public menu { public: menu_sound_options(mame_ui_manager &mui, render_container &container); - virtual ~menu_sound_options() override; protected: - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void menu_dismissed() override; private: enum { ENABLE_SOUND = 1, + ENABLE_COMPRESSOR, SAMPLE_RATE, ENABLE_SAMPLES }; - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; + virtual bool handle(event const *ev) override; uint16_t m_cur_rates; static const int m_sound_rate[]; int m_sample_rate; - bool m_samples, m_sound; + bool m_samples, m_sound, m_compressor; }; } // namespace ui -#endif /* MAME_FRONTEND_UI_SNDMENU_H */ +#endif // MAME_FRONTEND_UI_SNDMENU_H diff --git a/src/frontend/mame/ui/starimg.ipp b/src/frontend/mame/ui/starimg.ipp deleted file mode 100644 index 7f7f84e2c9a..00000000000 --- a/src/frontend/mame/ui/starimg.ipp +++ /dev/null @@ -1,49 +0,0 @@ -// license:BSD-3-Clause -// copyright-holders:Maurizio Petrarota -#ifndef MAME_FRONTEND_UI_STARIMG_IPP -#define MAME_FRONTEND_UI_STARIMG_IPP -#pragma once - -namespace ui { -namespace { -// TODO: move this to an external image file and zlib compress it into a souce file as part of the build process - -uint32_t const favorite_star_bmp[] = { - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x02D07A00, 0x15D07A00, 0x0FD07A00, 0x00D07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x76D27F04, 0xBFDA9714, 0xB9D78F0E, 0x4DD17B01, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3BD07A00, 0xFFE8B228, 0xFFFDEB50, 0xFFFBE34A, 0xD0E1A11C, 0x13D07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x0BD07A00, 0xA0D48306, 0xFFFACE42, 0xFFFBCE45, 0xFFFCD146, 0xFFF2BD34, 0x67D07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x49D27E03, 0xE9EAAB26, 0xFFFDD044, 0xFFF9C741, 0xFFFAC942, 0xFFFED245, 0xD1DF9716, 0x27D07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xA2DB8D0F, 0xFFF6C236, 0xFFFAC740, 0xFFF8C53F, 0xFFF8C53F, 0xFFFDCB41, 0xF7F0B62E, 0x71D68308, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x31D07A00, 0xFFE7A420, 0xFFFDCA3F, 0xFFF8C23D, 0xFFF8C23D, 0xFFF8C23D, 0xFFF8C23D, 0xFFFCC83D, 0xE0E19818, 0x11D07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x08D07A00, 0x99D38004, 0xFFF9C237, 0xFFFAC43C, 0xFFF8BF3A, 0xFFF8BF3A, 0xFFF8BF3A, 0xFFF8BF3A, 0xFFFBC53C, 0xFFF1B32B, 0x63D07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x01D07A00, 0x15D07A00, 0x24D07A00, 0x39D07A00, 0x4AD07A00, 0x79D48205, 0xE6E9A820, 0xFFFDC539, 0xFFF8BC37, 0xFFF8BC37, 0xFFF8BC37, 0xFFF8BC37, 0xFFF8BC37, 0xFFF9BD37, 0xFFFEC63A, 0xD8DF9613, 0x64D17C01, 0x3FD07A00, 0x2FD07A00, 0x1CD07A00, 0x0AD07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x04D07A00, 0x3BD07A00, 0x8BD07A00, 0xA5D17B01, 0xBFDA940F, 0xCEE1A317, 0xE2E7B622, 0xF4EDC229, 0xFFF1C62D, 0xFFFAC735, 0xFFFABC35, 0xFFF8B934, 0xFFF8B934, 0xFFF8B934, 0xFFF8B934, 0xFFF8B934, 0xFFF8B934, 0xFFFCBF36, 0xFFF7C733, 0xFCEFC52C, 0xE9EABB24, 0xD8E4AE1D, 0xC6DD9C13, 0xB4D58608, 0x99D07A00, 0x75D07A00, 0x20D07A00, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x01D07A00, 0xBBD78608, 0xFFE9AE1F, 0xFFF9D133, 0xFFFCD839, 0xFFFCD338, 0xFFFCCC36, 0xFFFCC333, 0xFFFCBB32, 0xFFF7B630, 0xFFF7B630, 0xFFF7B630, 0xFFF7B630, 0xFFF7B630, 0xFFF7B630, 0xFFF7B630, 0xFFF7B630, 0xFFF7B630, 0xFFF7B630, 0xFFF7B630, 0xFFFAB831, 0xFFFCC033, 0xFFFCC735, 0xFFFCD037, 0xFFFCD739, 0xFFFBD536, 0xFFF5C92F, 0xE8E4A318, 0x55D78507, 0x00000000, 0x00000000, - 0x00000000, 0x13D07A00, 0xFFDF9212, 0xFFFABC2F, 0xFFF9B72F, 0xFFF8B32E, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B32D, 0xFFF9B52E, 0xFFF9B92F, 0xFFF6B52A, 0xC1DB8B0D, 0x00000000, 0x00000000, - 0x00000000, 0x07D07A00, 0xE6DC8B0E, 0xFFF4AB27, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFEFA421, 0xAAD9860A, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x5ED58005, 0xE8E39213, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF4A925, 0xE2DC890C, 0x45D27C02, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x41D07A00, 0xE7E18F11, 0xFFF3A420, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFEFA11D, 0xE0DB880A, 0x35D07A00, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x5DD47E03, 0xE6E08D0D, 0xFFF5A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF3A11D, 0xDFDB8609, 0x4FD27C01, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x40D07A00, 0xE6E08A0C, 0xFFF29D19, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFEE9917, 0xDDDA8407, 0x30D07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x5BD37D02, 0xE6DF880A, 0xFFF59C18, 0xFFF69D18, 0xFFF69D18, 0xFFF69D18, 0xFFF69D18, 0xFFF69D18, 0xFFF69D18, 0xFFF69D18, 0xFFF69D18, 0xFFF69D18, 0xFFF69D18, 0xFFF69D18, 0xFFF69D18, 0xFFF69D18, 0xFFF69D18, 0xFFF29A16, 0xDCD98306, 0x49D17B01, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x7BD07A00, 0xFFEF9311, 0xFFF69A15, 0xFFF59915, 0xFFF59915, 0xFFF59915, 0xFFF59915, 0xFFF59915, 0xFFF59915, 0xFFF59915, 0xFFF59915, 0xFFF59915, 0xFFF59915, 0xFFF59915, 0xFFF59915, 0xFFF69915, 0xFFE2890A, 0x3BD07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x01D07A00, 0xA2D17B00, 0xFFF59612, 0xFFF69713, 0xFFF59612, 0xFFF59612, 0xFFF59612, 0xFFF59612, 0xFFF59612, 0xFFF59612, 0xFFF59612, 0xFFF59612, 0xFFF59612, 0xFFF59612, 0xFFF59612, 0xFFF59612, 0xFFF79712, 0xFFE98D0B, 0x4BD07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x14D07A00, 0xBED87F03, 0xFFF6940E, 0xFFF5930E, 0xFFF5930E, 0xFFF5930E, 0xFFF5930E, 0xFFF5930E, 0xFFF5930E, 0xFFF5930E, 0xFFF5930E, 0xFFF5930E, 0xFFF5930E, 0xFFF5930E, 0xFFF5930E, 0xFFF5930E, 0xFFF7940E, 0xFFF1900B, 0x7ED07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x27D07A00, 0xD1DE8205, 0xFFF8920C, 0xFFF5900C, 0xFFF5900C, 0xFFF5900C, 0xFFF5900C, 0xFFF5900C, 0xFFF5900C, 0xFFF5900C, 0xFFF5900C, 0xFFF5900C, 0xFFF5900C, 0xFFF5900C, 0xFFF5900C, 0xFFF5900C, 0xFFF6910C, 0xFFF5910C, 0xA5D27B01, 0x03D07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x40D07A00, 0xEAE48505, 0xFFFA9009, 0xFFF58D09, 0xFFF58D09, 0xFFF58D09, 0xFFF58D09, 0xFFF58D09, 0xFFF58D09, 0xFFF58D09, 0xFFF58D09, 0xFFF58D09, 0xFFF58D09, 0xFFF58D09, 0xFFF58D09, 0xFFF58D09, 0xFFF58D09, 0xFFF78E09, 0xC1D97F02, 0x17D07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x57D17B00, 0xFBE88504, 0xFFF78D06, 0xFFF48B06, 0xFFF48B06, 0xFFF48B06, 0xFFF48B06, 0xFFF48B06, 0xFFF38B06, 0xFFEC8705, 0xFFF18A06, 0xFFF48B06, 0xFFF48B06, 0xFFF48B06, 0xFFF48B06, 0xFFF48B06, 0xFFF48B06, 0xFFF88E06, 0xD6DF8102, 0x2CD07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x83D67D01, 0xFFED8503, 0xFFF48804, 0xFFF48804, 0xFFF48804, 0xFFF48804, 0xFFF28804, 0xFFEA8503, 0xCDDC7F02, 0x79D17B00, 0xA1D47C01, 0xEFE18102, 0xFFEE8604, 0xFFF38804, 0xFFF48804, 0xFFF48804, 0xFFF48804, 0xFFF88B04, 0xEFE58203, 0x46D07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xA0D87D01, 0xFFED8401, 0xFFF48602, 0xFFF48602, 0xFFF48602, 0xFFEF8501, 0xE9DE7F01, 0x8FD67D00, 0x23D07A00, 0x04D07A00, 0x0DD07A00, 0x46D07A00, 0xC3D97D01, 0xFFE28001, 0xFFF38602, 0xFFF48602, 0xFFF48602, 0xFFF58702, 0xFDE88201, 0x59D17A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x5FD47B00, 0xF3E58000, 0xFFF18400, 0xFFED8200, 0xDEE07F01, 0x90D37B00, 0x1FD07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x01D07A00, 0x3BD07A00, 0xBDD67C00, 0xF2E48000, 0xFFEF8300, 0xFFF08300, 0xDEDF7E01, 0x34D07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10D07A00, 0x71D57C00, 0xD2DB7D00, 0x9AD87C00, 0x34D07A00, 0x0AD07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x13D07A00, 0x52D27B00, 0xBBD97D00, 0xCBDA7D00, 0x5DD27B00, 0x0AD07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 -}; - -} // anonymous namespace -} // namespace ui - -#endif // MAME_FRONTEND_UI_STAR_IPP diff --git a/src/frontend/mame/ui/state.cpp b/src/frontend/mame/ui/state.cpp index f66b5af4a9a..efbc9e67e98 100644 --- a/src/frontend/mame/ui/state.cpp +++ b/src/frontend/mame/ui/state.cpp @@ -10,7 +10,12 @@ #include "emu.h" #include "ui/state.h" + #include "emuopts.h" +#include "inputdev.h" +#include "uiinput.h" + +#include "path.h" namespace ui { @@ -21,20 +26,6 @@ namespace ui { namespace { -const int MAX_SAVED_STATE_JOYSTICK = 4; - -//------------------------------------------------- -// keyboard_code -//------------------------------------------------- - -input_code keyboard_code(input_item_id id) -{ - // only supported for A-Z|0-9 - assert((id >= ITEM_ID_A && id <= ITEM_ID_Z) || (id >= ITEM_ID_0 && id <= ITEM_ID_9)); - return input_code(DEVICE_CLASS_KEYBOARD, 0, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, id); -} - - //------------------------------------------------- // keyboard_input_item_name //------------------------------------------------- @@ -57,20 +48,15 @@ std::string keyboard_input_item_name(input_item_id id) std::pair<std::string, std::string> code_item_pair(const running_machine &machine, input_item_id id) { - // identify the input code name (translated appropriately) - input_code code = keyboard_code(id); - std::string code_name = machine.input().code_name(code); - strmakelower(code_name); - - // identify the keyboard item name - std::string input_item_name = keyboard_input_item_name(id); + // only supported for A-Z|0-9 + assert((id >= ITEM_ID_A && id <= ITEM_ID_Z) || (id >= ITEM_ID_0 && id <= ITEM_ID_9)); + input_code const code = input_code(DEVICE_CLASS_KEYBOARD, 0, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, id); - // return them - return std::make_pair(code_name, input_item_name); + return std::make_pair(keyboard_input_item_name(id), machine.input().code_name(code)); } +} // anonymous namespace -}; /*************************************************************************** FILE ENTRY @@ -99,14 +85,24 @@ menu_load_save_state_base::file_entry::file_entry(std::string &&file_name, std:: // ctor //------------------------------------------------- -menu_load_save_state_base::menu_load_save_state_base(mame_ui_manager &mui, render_container &container, const char *header, const char *footer, bool must_exist) - : menu(mui, container) - , m_header(header) +menu_load_save_state_base::menu_load_save_state_base( + mame_ui_manager &mui, + render_container &container, + std::string_view header, + std::string_view footer, + bool must_exist, + bool one_shot) + : autopause_menu<>(mui, container) + , m_switch_poller(machine().input()) , m_footer(footer) + , m_confirm_delete(nullptr) , m_must_exist(must_exist) - , m_pause_checked(false) - , m_was_paused(false) + , m_keys_released(false) + , m_slot_selected(INPUT_CODE_INVALID) { + set_one_shot(one_shot); + set_needs_prev_menu_item(!one_shot); + set_heading(header); } @@ -116,10 +112,6 @@ menu_load_save_state_base::menu_load_save_state_base(mame_ui_manager &mui, rende menu_load_save_state_base::~menu_load_save_state_base() { - // resume if appropriate (is the destructor really the right place - // to do this sort of activity?) - if (!m_was_paused) - machine().resume(); } @@ -127,7 +119,7 @@ menu_load_save_state_base::~menu_load_save_state_base() // populate //------------------------------------------------- -void menu_load_save_state_base::populate(float &customtop, float &custombottom) +void menu_load_save_state_base::populate() { // build the "filename to code" map, if we have not already (if it were not for the // possibility that the system keyboard can be changed at runtime, I would put this @@ -139,11 +131,33 @@ void menu_load_save_state_base::populate(float &customtop, float &custombottom) m_filename_to_code_map.emplace(code_item_pair(machine(), id)); for (input_item_id id = ITEM_ID_0; id <= ITEM_ID_9; id++) m_filename_to_code_map.emplace(code_item_pair(machine(), id)); + + // do joysticks + input_class const &sticks = machine().input().device_class(DEVICE_CLASS_JOYSTICK); + if (sticks.enabled()) + { + for (int i = 0; sticks.maxindex() >= i; ++i) + { + input_device const *const stick = sticks.device(i); + if (stick) + { + for (input_item_id j = ITEM_ID_BUTTON1; (ITEM_ID_BUTTON32 >= j) && (stick->maxitem() >= j); ++j) + { + input_device_item const *const item = stick->item(j); + if (item && (item->itemclass() == ITEM_CLASS_SWITCH)) + { + m_filename_to_code_map.emplace( + util::string_format("joy%i-%i", i, j - ITEM_ID_BUTTON1 + 1), + machine().input().code_name(item->code())); + } + } + } + } + } } // open the state directory - std::string statedir = state_directory(); - osd::directory::ptr dir = osd::directory::open(statedir); + osd::directory::ptr dir = osd::directory::open(state_directory()); // create a separate vector, so we can add sorted entries to the menu std::vector<const file_entry *> m_entries_vec; @@ -158,7 +172,7 @@ void menu_load_save_state_base::populate(float &customtop, float &custombottom) if (core_filename_ends_with(entry->name, ".sta")) { // get the file name of the entry - std::string file_name = core_filename_extract_base(entry->name, true); + std::string file_name(core_filename_extract_base(entry->name, true)); // try translating it std::string visible_name = get_visible_name(file_name); @@ -173,12 +187,12 @@ void menu_load_save_state_base::populate(float &customtop, float &custombottom) // sort the vector; put recently modified state files at the top std::sort( - m_entries_vec.begin(), - m_entries_vec.end(), - [](const file_entry *a, const file_entry *b) - { - return a->last_modified() > b->last_modified(); - }); + m_entries_vec.begin(), + m_entries_vec.end(), + [] (const file_entry *a, const file_entry *b) + { + return a->last_modified() > b->last_modified(); + }); // add the entries for (const file_entry *entry : m_entries_vec) @@ -190,30 +204,30 @@ void menu_load_save_state_base::populate(float &customtop, float &custombottom) // format the text std::string text = util::string_format("%s: %s", - entry->visible_name(), - time_string); + entry->visible_name(), + time_string); // append the menu item - void *itemref = itemref_from_file_entry(*entry); - item_append(std::move(text), std::string(), 0, itemref); + void *const itemref = itemref_from_file_entry(*entry); + item_append(std::move(text), 0, itemref); // is this item selected? if (entry->file_name() == s_last_file_selected) set_selection(itemref); } - // set up custom render proc - customtop = ui().get_line_height() + 3.0f * ui().box_tb_border(); - custombottom = ui().get_line_height() + 3.0f * ui().box_tb_border(); - - // pause if appropriate - if (!m_pause_checked) + if (m_entries_vec.empty()) { - m_was_paused = machine().paused(); - if (!m_was_paused) - machine().pause(); - m_pause_checked = true; + item_append(_("[no saved states found]"), FLAG_DISABLE, nullptr); + set_selection(nullptr); } + item_append(menu_item_type::SEPARATOR); + if (is_one_shot()) + item_append(_("Cancel"), 0, nullptr); + + // get ready to poll inputs + m_switch_poller.reset(); + m_keys_released = false; } @@ -221,24 +235,59 @@ void menu_load_save_state_base::populate(float &customtop, float &custombottom) // handle //------------------------------------------------- -void menu_load_save_state_base::handle() +bool menu_load_save_state_base::handle(event const *ev) { - // process the menu - const event *event = process(0); - // process the event - if (event && (event->iptkey == IPT_UI_SELECT)) + if (INPUT_CODE_INVALID != m_slot_selected) { - // user selected one of the entries - const file_entry &entry = file_entry_from_itemref(event->itemref); - slot_selected(std::string(entry.file_name())); + if (!machine().input().code_pressed(m_slot_selected)) + stack_pop(); + return false; } - else + else if (ev && (ev->iptkey == IPT_UI_SELECT)) + { + if (ev->itemref) + { + // user selected one of the entries + file_entry const &entry = file_entry_from_itemref(ev->itemref); + slot_selected(std::string(entry.file_name())); + } + stack_pop(); + return false; + } + else if (ev && (ev->iptkey == IPT_UI_CLEAR)) + { + if (ev->itemref) + { + // prompt to confirm delete + m_confirm_delete = &file_entry_from_itemref(ev->itemref); + m_confirm_prompt = util::string_format( + _("Delete saved state %1$s?\nPress %2$s to delete\nPress %3$s to cancel"), + m_confirm_delete->visible_name(), + ui().get_general_input_setting(IPT_UI_SELECT), + ui().get_general_input_setting(IPT_UI_BACK)); + return true; + } + else + { + return false; + } + } + else if (!m_confirm_delete) { // poll inputs - std::string name = poll_inputs(); - if (!name.empty()) - try_select_slot(std::move(name)); + input_code code; + std::string name = poll_inputs(code); + if (!name.empty() && try_select_slot(std::move(name))) + { + m_switch_poller.reset(); + m_slot_selected = code; + } + return false; + } + else + { + return false; } } @@ -249,12 +298,9 @@ void menu_load_save_state_base::handle() std::string menu_load_save_state_base::get_visible_name(const std::string &file_name) { - if (file_name.size() == 1) - { - auto iter = m_filename_to_code_map.find(file_name); - if (iter != m_filename_to_code_map.end()) - return iter->second; - } + auto const iter = m_filename_to_code_map.find(file_name); + if (iter != m_filename_to_code_map.end()) + return iter->second; // otherwise these are the same return file_name; @@ -265,31 +311,32 @@ std::string menu_load_save_state_base::get_visible_name(const std::string &file_ // poll_inputs //------------------------------------------------- -std::string menu_load_save_state_base::poll_inputs() +std::string menu_load_save_state_base::poll_inputs(input_code &code) { - // poll A-Z - for (input_item_id id = ITEM_ID_A; id <= ITEM_ID_Z; id++) + input_code const result = m_switch_poller.poll(); + if (INPUT_CODE_INVALID == result) { - if (machine().input().code_pressed_once(keyboard_code(id))) - return keyboard_input_item_name(id); + m_keys_released = true; } - - // poll 0-9 - for (input_item_id id = ITEM_ID_0; id <= ITEM_ID_9; id++) + else if (m_keys_released) { - if (machine().input().code_pressed_once(keyboard_code(id))) + input_item_id const id = result.item_id(); + + // keyboard A-Z and 0-9 + if (((ITEM_ID_A <= id) && (ITEM_ID_Z >= id)) || ((ITEM_ID_0 <= id) && (ITEM_ID_9 >= id))) + { + code = result; return keyboard_input_item_name(id); - } + } - // poll joysticks - for (int joy_index = 0; joy_index <= MAX_SAVED_STATE_JOYSTICK; joy_index++) - { - for (input_item_id id = ITEM_ID_BUTTON1; id <= ITEM_ID_BUTTON32; ++id) + // joystick buttons + if ((DEVICE_CLASS_JOYSTICK == result.device_class()) && (ITEM_CLASS_SWITCH == result.item_class()) && (ITEM_MODIFIER_NONE == result.item_modifier()) && (ITEM_ID_BUTTON1 <= id) && (ITEM_ID_BUTTON32 >= id)) { - if (machine().input().code_pressed_once(input_code(DEVICE_CLASS_JOYSTICK, joy_index, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, id))) - return util::string_format("joy%i-%i", joy_index, id - ITEM_ID_BUTTON1 + 1); + code = result; + return util::string_format("joy%i-%i", result.device_index(), id - ITEM_ID_BUTTON1 + 1); } } + code = INPUT_CODE_INVALID; return ""; } @@ -298,10 +345,17 @@ std::string menu_load_save_state_base::poll_inputs() // try_select_slot //------------------------------------------------- -void menu_load_save_state_base::try_select_slot(std::string &&name) +bool menu_load_save_state_base::try_select_slot(std::string &&name) { if (!m_must_exist || is_present(name)) + { slot_selected(std::move(name)); + return true; + } + else + { + return false; + } } @@ -316,9 +370,96 @@ void menu_load_save_state_base::slot_selected(std::string &&name) // record the last slot touched s_last_file_selected = std::move(name); +} + + +//------------------------------------------------- +// handle_keys - override key handling +//------------------------------------------------- + +bool menu_load_save_state_base::handle_keys(uint32_t flags, int &iptkey) +{ + if (m_confirm_delete) + { + bool updated(false); + if (exclusive_input_pressed(iptkey, IPT_UI_SELECT, 0)) + { + // try to remove the file + std::string const filename(util::path_concat( + machine().options().state_directory(), + machine().get_statename(machine().options().state_name()), + m_confirm_delete->file_name() + ".sta")); + std::error_condition const err(osd_file::remove(filename)); + if (err) + { + osd_printf_error( + "Error removing file %s for state %s (%s:%d %s)\n", + filename, + m_confirm_delete->visible_name(), + err.category().name(), + err.value(), + err.message()); + machine().popmessage(_("Error removing saved state file %1$s"), filename); + } - // no matter what, pop out - menu::stack_pop(machine()); + // repopulate the menu + // reset switch poller here to avoid bogus save/load if confirmed with joystick button + m_switch_poller.reset(); + m_confirm_prompt.clear(); + m_confirm_delete = nullptr; + m_keys_released = false; + reset(reset_options::REMEMBER_POSITION); + } + else if (exclusive_input_pressed(iptkey, IPT_UI_BACK, 0)) + { + // don't delete it - dismiss the prompt + m_switch_poller.reset(); + m_confirm_prompt.clear(); + m_confirm_delete = nullptr; + m_keys_released = false; + updated = true; + } + iptkey = IPT_INVALID; + return updated; + } + else if (INPUT_CODE_INVALID != m_slot_selected) + { + iptkey = IPT_INVALID; + return false; + } + else + { + return autopause_menu<>::handle_keys(flags, iptkey); + } +} + + +//------------------------------------------------- +// custom_pointer_updated - override pointer +// handling +//------------------------------------------------- + +std::tuple<int, bool, bool> menu_load_save_state_base::custom_pointer_updated(bool changed, ui_event const &uievt) +{ + // suppress clicks on the menu while the delete prompt is visible + if (m_confirm_delete && uievt.pointer_buttons) + return std::make_tuple(IPT_INVALID, true, false); + else + return autopause_menu<>::custom_pointer_updated(changed, uievt); + +} + + +//------------------------------------------------- +// recompute_metrics - recompute metrics +//------------------------------------------------- + +void menu_load_save_state_base::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + autopause_menu<>::recompute_metrics(width, height, aspect); + + // set up custom render proc + set_custom_space(0.0F, (2.0F * line_height()) + (3.0F * tb_border())); } @@ -326,9 +467,36 @@ void menu_load_save_state_base::slot_selected(std::string &&name) // custom_render - perform our special rendering //------------------------------------------------- -void menu_load_save_state_base::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void menu_load_save_state_base::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) { - extra_text_render(top, bottom, origx1, origy1, origx2, origy2, m_header, m_footer); + std::string_view text[2]; + unsigned count(0U); + + // add fixed footer if supplied + if (!m_footer.empty()) + text[count++] = m_footer; + + // provide a prompt to delete if a state is selected + if (selected_item().ref()) + { + if (m_delete_prompt.empty()) + m_delete_prompt = util::string_format(_("Press %1$s to delete"), ui().get_general_input_setting(IPT_UI_CLEAR)); + text[count++] = m_delete_prompt; + } + + // draw the footer box if necessary + if (count) + { + draw_text_box( + std::begin(text), std::next(std::begin(text), count), + origx1, origx2, origy2 + tb_border(), origy2 + (count * line_height()) + (3.0F * tb_border()), + text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, false, + ui().colors().text_color(), ui().colors().background_color()); + } + + // draw the confirmation prompt if necessary + if (!m_confirm_prompt.empty()) + ui().draw_text_box(container(), m_confirm_prompt, text_layout::text_justify::CENTER, 0.5F, 0.5F, ui().colors().background_color()); } @@ -358,11 +526,9 @@ const menu_load_save_state_base::file_entry &menu_load_save_state_base::file_ent std::string menu_load_save_state_base::state_directory() const { - const char *stateopt = machine().options().state_name(); - return util::string_format("%s%s%s", - machine().options().state_directory(), - PATH_SEPARATOR, - machine().get_statename(stateopt)); + return util::path_concat( + machine().options().state_directory(), + machine().get_statename(machine().options().state_name())); } @@ -384,8 +550,8 @@ bool menu_load_save_state_base::is_present(const std::string &name) const // ctor //------------------------------------------------- -menu_load_state::menu_load_state(mame_ui_manager &mui, render_container &container) - : menu_load_save_state_base(mui, container, _("Load State"), _("Select position to load from"), true) +menu_load_state::menu_load_state(mame_ui_manager &mui, render_container &container, bool one_shot) + : menu_load_save_state_base(mui, container, _("Load State"), _("Select state to load"), true, one_shot) { } @@ -408,8 +574,8 @@ void menu_load_state::process_file(std::string &&file_name) // ctor //------------------------------------------------- -menu_save_state::menu_save_state(mame_ui_manager &mui, render_container &container) - : menu_load_save_state_base(mui, container, _("Save State"), _("Select position to save to"), false) +menu_save_state::menu_save_state(mame_ui_manager &mui, render_container &container, bool one_shot) + : menu_load_save_state_base(mui, container, _("Save State"), _("Press a key or joystick button, or select state to overwrite"), false, one_shot) { } diff --git a/src/frontend/mame/ui/state.h b/src/frontend/mame/ui/state.h index 0d7bdd2fea9..cff9f3013ff 100644 --- a/src/frontend/mame/ui/state.h +++ b/src/frontend/mame/ui/state.h @@ -7,28 +7,43 @@ Menus for saving and loading state ***************************************************************************/ - -#pragma once - #ifndef MAME_FRONTEND_UI_STATE_H #define MAME_FRONTEND_UI_STATE_H +#pragma once + #include "ui/menu.h" -namespace ui { +#include "iptseqpoll.h" -// ======================> menu_load_save_state_base +#include <chrono> +#include <tuple> +#include <unordered_map> -class menu_load_save_state_base : public menu + +namespace ui { + +class menu_load_save_state_base : public autopause_menu<> { public: virtual ~menu_load_save_state_base() override; - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; protected: - menu_load_save_state_base(mame_ui_manager &mui, render_container &container, const char *header, const char *footer, bool must_exist); + menu_load_save_state_base( + mame_ui_manager &mui, + render_container &container, + std::string_view header, + std::string_view footer, + bool must_exist, + bool one_shot); + + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) override; + virtual bool handle_keys(uint32_t flags, int &iptkey) override; + virtual std::tuple<int, bool, bool> custom_pointer_updated(bool changed, ui_event const &uievt) override; + virtual void populate() override; + virtual bool handle(event const *ev) override; + virtual void process_file(std::string &&file_name) = 0; private: @@ -52,46 +67,48 @@ private: static std::string s_last_file_selected; + switch_code_poller m_switch_poller; std::unordered_map<std::string, file_entry> m_file_entries; std::unordered_map<std::string, std::string> m_filename_to_code_map; - const char * m_header; - const char * m_footer; - bool m_must_exist; - bool m_pause_checked; - bool m_was_paused; + std::string_view const m_footer; + std::string m_delete_prompt; + std::string m_confirm_prompt; + file_entry const * m_confirm_delete; + bool const m_must_exist; + bool m_keys_released; + input_code m_slot_selected; static void *itemref_from_file_entry(const file_entry &entry); static const file_entry &file_entry_from_itemref(void *itemref); - void try_select_slot(std::string &&name); + + bool try_select_slot(std::string &&name); void slot_selected(std::string &&name); std::string state_directory() const; bool is_present(const std::string &name) const; - std::string poll_inputs(); + std::string poll_inputs(input_code &code); std::string get_visible_name(const std::string &file_name); }; -// ======================> menu_load_state class menu_load_state : public menu_load_save_state_base { public: - menu_load_state(mame_ui_manager &mui, render_container &container); + menu_load_state(mame_ui_manager &mui, render_container &container, bool one_shot); protected: virtual void process_file(std::string &&file_name) override; }; -// ======================> menu_save_state class menu_save_state : public menu_load_save_state_base { public: - menu_save_state(mame_ui_manager &mui, render_container &container); + menu_save_state(mame_ui_manager &mui, render_container &container, bool one_shot); protected: virtual void process_file(std::string &&file_name) override; }; -}; +} // namespace ui #endif // MAME_FRONTEND_UI_STATE_H diff --git a/src/frontend/mame/ui/submenu.cpp b/src/frontend/mame/ui/submenu.cpp index f9cff799c1f..cce1f28eeb2 100644 --- a/src/frontend/mame/ui/submenu.cpp +++ b/src/frontend/mame/ui/submenu.cpp @@ -9,111 +9,139 @@ ***************************************************************************/ #include "emu.h" -#include "ui/ui.h" #include "ui/submenu.h" -#include "ui/utils.h" + #include "ui/menuitem.h" +#include "ui/ui.h" +#include "ui/utils.h" + +#if defined(UI_WINDOWS) && !defined(UI_SDL) +#include "../osd/windows/winmain.h" +#else +#include "../osd/modules/lib/osdobj_common.h" +#endif +#include "../osd/modules/input/input_module.h" #include <limits> namespace ui { -std::vector<submenu::option> const submenu::misc_options = { - { submenu::option_type::HEAD, __("Miscellaneous Options") }, - { submenu::option_type::UI, __("Re-select last machine played"), OPTION_REMEMBER_LAST }, - { submenu::option_type::UI, __("Enlarge images in the right panel"), OPTION_ENLARGE_SNAPS }, - { submenu::option_type::EMU, __("Cheats"), OPTION_CHEAT }, - { submenu::option_type::EMU, __("Show mouse pointer"), OPTION_UI_MOUSE }, - { submenu::option_type::EMU, __("Confirm quit from machines"), OPTION_CONFIRM_QUIT }, - { submenu::option_type::EMU, __("Skip information screen at startup"), OPTION_SKIP_GAMEINFO }, - { submenu::option_type::UI, __("Force 4:3 aspect for snapshot display"), OPTION_FORCED4X3 }, - { submenu::option_type::UI, __("Use image as background"), OPTION_USE_BACKGROUND }, - { submenu::option_type::UI, __("Skip BIOS selection menu"), OPTION_SKIP_BIOS_MENU }, - { submenu::option_type::UI, __("Skip software parts selection menu"), OPTION_SKIP_PARTS_MENU }, - { submenu::option_type::UI, __("Info auto audit"), OPTION_INFO_AUTO_AUDIT }, - { submenu::option_type::UI, __("Hide romless machine from available list"),OPTION_HIDE_ROMLESS }, -}; - -std::vector<submenu::option> const submenu::advanced_options = { - { submenu::option_type::HEAD, __("Advanced Options") }, - { submenu::option_type::HEAD, __("Performance Options") }, - { submenu::option_type::EMU, __("Auto frame skip"), OPTION_AUTOFRAMESKIP }, - { submenu::option_type::EMU, __("Frame skip"), OPTION_FRAMESKIP }, - { submenu::option_type::EMU, __("Throttle"), OPTION_THROTTLE }, - { submenu::option_type::EMU, __("Sleep"), OPTION_SLEEP }, - { submenu::option_type::EMU, __("Speed"), OPTION_SPEED }, - { submenu::option_type::EMU, __("Refresh speed"), OPTION_REFRESHSPEED }, - - { submenu::option_type::HEAD, __("Rotation Options") }, - { submenu::option_type::EMU, __("Rotate"), OPTION_ROTATE }, - { submenu::option_type::EMU, __("Rotate right"), OPTION_ROR }, - { submenu::option_type::EMU, __("Rotate left"), OPTION_ROL }, - { submenu::option_type::EMU, __("Auto rotate right"), OPTION_AUTOROR }, - { submenu::option_type::EMU, __("Auto rotate left"), OPTION_AUTOROL }, - { submenu::option_type::EMU, __("Flip X"), OPTION_FLIPX }, - { submenu::option_type::EMU, __("Flip Y"), OPTION_FLIPY }, - - { submenu::option_type::HEAD, __("Artwork Options") }, - { submenu::option_type::EMU, __("Artwork Crop"), OPTION_ARTWORK_CROP }, - - { submenu::option_type::HEAD, __("State/Playback Options") }, - { submenu::option_type::EMU, __("Automatic save/restore"), OPTION_AUTOSAVE }, - { submenu::option_type::EMU, __("Rewind"), OPTION_REWIND }, - { submenu::option_type::EMU, __("Rewind capacity"), OPTION_REWIND_CAPACITY }, - { submenu::option_type::EMU, __("Bilinear snapshot"), OPTION_SNAPBILINEAR }, - { submenu::option_type::EMU, __("Burn-in"), OPTION_BURNIN }, - - { submenu::option_type::HEAD, __("Input Options") }, - { submenu::option_type::EMU, __("Coin lockout"), OPTION_COIN_LOCKOUT }, - { submenu::option_type::EMU, __("Mouse"), OPTION_MOUSE }, - { submenu::option_type::EMU, __("Joystick"), OPTION_JOYSTICK }, - { submenu::option_type::EMU, __("Lightgun"), OPTION_LIGHTGUN }, - { submenu::option_type::EMU, __("Multi-keyboard"), OPTION_MULTIKEYBOARD }, - { submenu::option_type::EMU, __("Multi-mouse"), OPTION_MULTIMOUSE }, - { submenu::option_type::EMU, __("Steadykey"), OPTION_STEADYKEY }, - { submenu::option_type::EMU, __("UI active"), OPTION_UI_ACTIVE }, - { submenu::option_type::EMU, __("Offscreen reload"), OPTION_OFFSCREEN_RELOAD }, - { submenu::option_type::EMU, __("Joystick deadzone"), OPTION_JOYSTICK_DEADZONE }, - { submenu::option_type::EMU, __("Joystick saturation"), OPTION_JOYSTICK_SATURATION }, - { submenu::option_type::EMU, __("Natural keyboard"), OPTION_NATURAL_KEYBOARD }, - { submenu::option_type::EMU, __("Simultaneous contradictory"), OPTION_JOYSTICK_CONTRADICTORY }, - { submenu::option_type::EMU, __("Coin impulse"), OPTION_COIN_IMPULSE }, -}; - -std::vector<submenu::option> const submenu::control_options = { - { submenu::option_type::HEAD, __("Device Mapping") }, - { submenu::option_type::EMU, __("Lightgun Device Assignment"), OPTION_LIGHTGUN_DEVICE }, - { submenu::option_type::EMU, __("Trackball Device Assignment"), OPTION_TRACKBALL_DEVICE }, - { submenu::option_type::EMU, __("Pedal Device Assignment"), OPTION_PEDAL_DEVICE }, - { submenu::option_type::EMU, __("Adstick Device Assignment"), OPTION_ADSTICK_DEVICE }, - { submenu::option_type::EMU, __("Paddle Device Assignment"), OPTION_PADDLE_DEVICE }, - { submenu::option_type::EMU, __("Dial Device Assignment"), OPTION_DIAL_DEVICE }, - { submenu::option_type::EMU, __("Positional Device Assignment"), OPTION_POSITIONAL_DEVICE }, - { submenu::option_type::EMU, __("Mouse Device Assignment"), OPTION_MOUSE_DEVICE } -}; - -std::vector<submenu::option> const submenu::video_options = { - { submenu::option_type::HEAD, __("Video Options") }, - { submenu::option_type::OSD, __("Video Mode"), OSDOPTION_VIDEO }, - { submenu::option_type::OSD, __("Number Of Screens"), OSDOPTION_NUMSCREENS }, + +std::vector<submenu::option> submenu::misc_options() +{ + return std::vector<option>{ + { option_type::HEAD, N_("Miscellaneous Options") }, + { option_type::UI, N_("Skip imperfect emulation warnings"), OPTION_SKIP_WARNINGS }, + { option_type::UI, N_("Re-select last system launched"), OPTION_REMEMBER_LAST }, + { option_type::UI, N_("Enlarge images in the right panel"), OPTION_ENLARGE_SNAPS }, + { option_type::EMU, N_("Cheats"), OPTION_CHEAT }, + { option_type::EMU, N_("Show mouse pointer"), OPTION_UI_MOUSE }, + { option_type::EMU, N_("Confirm quit from emulation"), OPTION_CONFIRM_QUIT }, + { option_type::EMU, N_("Skip system information screen"), OPTION_SKIP_GAMEINFO }, + { option_type::UI, N_("Force 4:3 aspect for snapshot display"), OPTION_FORCED4X3 }, + { option_type::UI, N_("Use image as background"), OPTION_USE_BACKGROUND }, + { option_type::UI, N_("Skip BIOS selection menu"), OPTION_SKIP_BIOS_MENU }, + { option_type::UI, N_("Skip software part selection menu"), OPTION_SKIP_PARTS_MENU }, + { option_type::UI, N_("Info auto audit"), OPTION_INFO_AUTO_AUDIT }, + { option_type::UI, N_("Hide romless machine from available list"),OPTION_HIDE_ROMLESS } }; +} + +std::vector<submenu::option> submenu::advanced_options() +{ + return std::vector<option>{ + { option_type::HEAD, N_("Advanced Options") }, + { option_type::HEAD, N_("Performance Options") }, + { option_type::EMU, N_("Auto frame skip"), OPTION_AUTOFRAMESKIP }, + { option_type::EMU, N_("Frame skip"), OPTION_FRAMESKIP }, + { option_type::EMU, N_("Throttle"), OPTION_THROTTLE }, + { option_type::UI, N_("Mute when unthrottled"), OPTION_UNTHROTTLE_MUTE }, + { option_type::EMU, N_("Sleep"), OPTION_SLEEP }, + { option_type::EMU, N_("Speed"), OPTION_SPEED }, + { option_type::EMU, N_("Adjust speed to match refresh rate"), OPTION_REFRESHSPEED }, + { option_type::EMU, N_("Low latency"), OPTION_LOWLATENCY }, + + { option_type::HEAD, N_("Rotation Options") }, + { option_type::EMU, N_("Rotate"), OPTION_ROTATE }, + { option_type::EMU, N_("Rotate right"), OPTION_ROR }, + { option_type::EMU, N_("Rotate left"), OPTION_ROL }, + { option_type::EMU, N_("Auto rotate right"), OPTION_AUTOROR }, + { option_type::EMU, N_("Auto rotate left"), OPTION_AUTOROL }, + { option_type::EMU, N_("Flip X"), OPTION_FLIPX }, + { option_type::EMU, N_("Flip Y"), OPTION_FLIPY }, + + { option_type::HEAD, N_("Artwork Options") }, + { option_type::EMU, N_("Zoom to screen area"), OPTION_ARTWORK_CROP }, + + { option_type::HEAD, N_("State/Playback Options") }, + { option_type::EMU, N_("Automatic save/restore"), OPTION_AUTOSAVE }, + { option_type::EMU, N_("Allow rewind"), OPTION_REWIND }, + { option_type::EMU, N_("Rewind capacity"), OPTION_REWIND_CAPACITY }, + { option_type::EMU, N_("Bilinear filtering for snapshots"), OPTION_SNAPBILINEAR }, + { option_type::EMU, N_("Burn-in"), OPTION_BURNIN }, + + { option_type::HEAD, N_("Input Options") }, + { option_type::EMU, N_("Coin lockout"), OPTION_COIN_LOCKOUT }, + { option_type::EMU, N_("Mouse"), OPTION_MOUSE }, + { option_type::EMU, N_("Joystick"), OPTION_JOYSTICK }, + { option_type::EMU, N_("Lightgun"), OPTION_LIGHTGUN }, + { option_type::EMU, N_("Multi-keyboard"), OPTION_MULTIKEYBOARD }, + { option_type::EMU, N_("Multi-mouse"), OPTION_MULTIMOUSE }, + { option_type::EMU, N_("Steadykey"), OPTION_STEADYKEY }, + { option_type::EMU, N_("UI active"), OPTION_UI_ACTIVE }, + { option_type::EMU, N_("Off-screen reload"), OPTION_OFFSCREEN_RELOAD }, + { option_type::EMU, N_("Joystick deadzone"), OPTION_JOYSTICK_DEADZONE }, + { option_type::EMU, N_("Joystick saturation"), OPTION_JOYSTICK_SATURATION }, + { option_type::EMU, N_("Joystick threshold"), OPTION_JOYSTICK_THRESHOLD }, + { option_type::EMU, N_("Natural keyboard"), OPTION_NATURAL_KEYBOARD }, + { option_type::EMU, N_("Allow contradictory joystick inputs"), OPTION_JOYSTICK_CONTRADICTORY }, + { option_type::EMU, N_("Coin impulse"), OPTION_COIN_IMPULSE } }; +} + +std::vector<submenu::option> submenu::control_options() +{ + return std::vector<option>{ + { option_type::HEAD, N_("Input Device Options") }, + { option_type::EMU, N_("Lightgun Device Assignment"), OPTION_LIGHTGUN_DEVICE }, + { option_type::EMU, N_("Trackball Device Assignment"), OPTION_TRACKBALL_DEVICE }, + { option_type::EMU, N_("Pedal Device Assignment"), OPTION_PEDAL_DEVICE }, + { option_type::EMU, N_("AD Stick Device Assignment"), OPTION_ADSTICK_DEVICE }, + { option_type::EMU, N_("Paddle Device Assignment"), OPTION_PADDLE_DEVICE }, + { option_type::EMU, N_("Dial Device Assignment"), OPTION_DIAL_DEVICE }, + { option_type::EMU, N_("Positional Device Assignment"), OPTION_POSITIONAL_DEVICE }, + { option_type::EMU, N_("Mouse Device Assignment"), OPTION_MOUSE_DEVICE }, + { option_type::SEP }, + { option_type::OSD, N_("Keyboard Input Provider"), OSD_KEYBOARDINPUT_PROVIDER }, + { option_type::OSD, N_("Mouse Input Provider"), OSD_MOUSEINPUT_PROVIDER }, + { option_type::OSD, N_("Lightgun Input Provider"), OSD_LIGHTGUNINPUT_PROVIDER }, + { option_type::OSD, N_("Joystick Input Provider"), OSD_JOYSTICKINPUT_PROVIDER } }; +} + +std::vector<submenu::option> submenu::video_options() +{ + return std::vector<option>{ + { option_type::HEAD, N_("Video Options") }, + { option_type::OSD, N_("Video Mode"), OSDOPTION_VIDEO }, + { option_type::OSD, N_("Number Of Screens"), OSDOPTION_NUMSCREENS }, #if defined(UI_WINDOWS) && !defined(UI_SDL) - { submenu::option_type::OSD, __("Triple Buffering"), WINOPTION_TRIPLEBUFFER }, - { submenu::option_type::OSD, __("HLSL"), WINOPTION_HLSL_ENABLE }, + { option_type::OSD, N_("Triple Buffering"), WINOPTION_TRIPLEBUFFER }, + { option_type::OSD, N_("HLSL"), WINOPTION_HLSL_ENABLE }, #endif - { submenu::option_type::OSD, __("GLSL"), OSDOPTION_GL_GLSL }, - { submenu::option_type::OSD, __("Bilinear Filtering"), OSDOPTION_FILTER }, - { submenu::option_type::OSD, __("Bitmap Prescaling"), OSDOPTION_PRESCALE }, - { submenu::option_type::OSD, __("Window Mode"), OSDOPTION_WINDOW }, - { submenu::option_type::EMU, __("Enforce Aspect Ratio"), OPTION_KEEPASPECT }, - { submenu::option_type::OSD, __("Start Out Maximized"), OSDOPTION_MAXIMIZE }, - { submenu::option_type::OSD, __("Synchronized Refresh"), OSDOPTION_SYNCREFRESH }, - { submenu::option_type::OSD, __("Wait Vertical Sync"), OSDOPTION_WAITVSYNC } -}; - -//std::vector<submenu::option> const submenu::export_options = { -// { ui_submenu::option_type::COMMAND, __("Export XML format (like -listxml)"), "exportxml" }, -// { ui_submenu::option_type::COMMAND, __("Export TXT format (like -listfull)"), "exporttxt" }, -//}; + { option_type::OSD, N_("GLSL"), OSDOPTION_GL_GLSL }, + { option_type::OSD, N_("Bilinear Filtering"), OSDOPTION_FILTER }, + { option_type::OSD, N_("Bitmap Prescaling"), OSDOPTION_PRESCALE }, + { option_type::OSD, N_("Window Mode"), OSDOPTION_WINDOW }, + { option_type::EMU, N_("Enforce Aspect Ratio"), OPTION_KEEPASPECT }, + { option_type::OSD, N_("Start Out Maximized"), OSDOPTION_MAXIMIZE }, + { option_type::OSD, N_("Synchronized Refresh"), OSDOPTION_SYNCREFRESH }, + { option_type::OSD, N_("Wait Vertical Sync"), OSDOPTION_WAITVSYNC } }; +} + +//std::vector<submenu::option> submenu::export_options() +//{ +// return std::vector<option>{ +// { option_type::COMMAND, N_("Export XML format (like -listxml)"), "exportxml" }, +// { option_type::COMMAND, N_("Export TXT format (like -listfull)"), "exporttxt" } }; +//} //------------------------------------------------- @@ -130,20 +158,23 @@ submenu::submenu(mame_ui_manager &mui, render_container &container, std::vector< , m_options(std::move(suboptions)) , m_driver(drv) { + set_process_flags(PROCESS_LR_REPEAT); + set_heading(_(m_options[0].description)); + core_options *opts = nullptr; if (m_driver == nullptr) - opts = dynamic_cast<core_options*>(&mui.machine().options()); + opts = dynamic_cast<core_options *>(&mui.machine().options()); else - opts = dynamic_cast<core_options*>(options); + opts = dynamic_cast<core_options *>(options); - for (option & sm_option : m_options) + for (option &sm_option : m_options) { switch (sm_option.type) { case option_type::EMU: sm_option.entry = opts->get_entry(sm_option.name); sm_option.options = opts; - if (sm_option.entry->type() == OPTION_STRING) + if ((sm_option.entry->type() == core_options::option_type::STRING) || (sm_option.entry->type() == core_options::option_type::PATH) || (sm_option.entry->type() == core_options::option_type::MULTIPATH)) { sm_option.value.clear(); std::string namestr(sm_option.entry->description()); @@ -167,10 +198,10 @@ submenu::submenu(mame_ui_manager &mui, render_container &container, std::vector< case option_type::OSD: sm_option.entry = opts->get_entry(sm_option.name); sm_option.options = opts; - if (sm_option.entry->type() == OPTION_STRING) + if ((sm_option.entry->type() == core_options::option_type::STRING) || (sm_option.entry->type() == core_options::option_type::PATH) || (sm_option.entry->type() == core_options::option_type::MULTIPATH)) { sm_option.value.clear(); - std::string descr(sm_option.entry->description()), delim(", "); + std::string descr(machine().options().get_entry(sm_option.name)->description()), delim(", "); descr.erase(0, descr.find(":") + 2); std::string default_value(sm_option.entry->default_value()); @@ -211,22 +242,19 @@ submenu::~submenu() } //------------------------------------------------- -// handlethe options menu +// handle the options menu //------------------------------------------------- -void submenu::handle() +bool submenu::handle(event const *ev) { bool changed = false; std::string error_string, tmptxt; float f_cur, f_step; // process the menu - const event *menu_event = process(PROCESS_LR_REPEAT); - - if (menu_event != nullptr && menu_event->itemref != nullptr && - (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT || menu_event->iptkey == IPT_UI_SELECT)) + if (ev && ev->itemref && (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT || ev->iptkey == IPT_UI_SELECT)) { - option &sm_option = *reinterpret_cast<option *>(menu_event->itemref); + option &sm_option = *reinterpret_cast<option *>(ev->itemref); switch (sm_option.type) { @@ -235,21 +263,21 @@ void submenu::handle() case option_type::OSD: switch (sm_option.entry->type()) { - case OPTION_BOOLEAN: + case core_options::option_type::BOOLEAN: changed = true; sm_option.options->set_value(sm_option.name, !strcmp(sm_option.entry->value(),"1") ? "0" : "1", OPTION_PRIORITY_CMDLINE); break; - case OPTION_INTEGER: - if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) + case core_options::option_type::INTEGER: + if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT) { changed = true; int i_cur = atoi(sm_option.entry->value()); - (menu_event->iptkey == IPT_UI_LEFT) ? i_cur-- : i_cur++; + (ev->iptkey == IPT_UI_LEFT) ? i_cur-- : i_cur++; sm_option.options->set_value(sm_option.name, i_cur, OPTION_PRIORITY_CMDLINE); } break; - case OPTION_FLOAT: - if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) + case core_options::option_type::FLOAT: + if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT) { changed = true; f_cur = atof(sm_option.entry->value()); @@ -258,7 +286,7 @@ void submenu::handle() const char *minimum = sm_option.entry->minimum(); const char *maximum = sm_option.entry->maximum(); f_step = atof(minimum); - if (f_step <= 0.0f) { + if (f_step <= 0.0F) { int pmin = getprecisionchr(minimum); int pmax = getprecisionchr(maximum); tmptxt = '1' + std::string((pmin > pmax) ? pmin : pmax, '0'); @@ -271,7 +299,7 @@ void submenu::handle() tmptxt = '1' + std::string(precision, '0'); f_step = 1 / atof(tmptxt.c_str()); } - if (menu_event->iptkey == IPT_UI_LEFT) + if (ev->iptkey == IPT_UI_LEFT) f_cur -= f_step; else f_cur += f_step; @@ -279,13 +307,15 @@ void submenu::handle() sm_option.options->set_value(sm_option.name, tmptxt.c_str(), OPTION_PRIORITY_CMDLINE); } break; - case OPTION_STRING: - if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) + case core_options::option_type::STRING: + case core_options::option_type::PATH: + case core_options::option_type::MULTIPATH: + if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT) { changed = true; std::string v_cur(sm_option.entry->value()); int cur_value = std::distance(sm_option.value.begin(), std::find(sm_option.value.begin(), sm_option.value.end(), v_cur)); - if (menu_event->iptkey == IPT_UI_LEFT) + if (ev->iptkey == IPT_UI_LEFT) v_cur = sm_option.value[--cur_value]; else v_cur = sm_option.value[++cur_value]; @@ -302,15 +332,16 @@ void submenu::handle() } } - if (changed) + if (changed) // FIXME: most changes should only require updating the item's subtext reset(reset_options::REMEMBER_REF); + return false; } //------------------------------------------------- // populate //------------------------------------------------- -void submenu::populate(float &customtop, float &custombottom) +void submenu::populate() { // add options for (auto sm_option = m_options.begin(); sm_option < m_options.end(); ++sm_option) @@ -323,26 +354,27 @@ void submenu::populate(float &customtop, float &custombottom) switch (sm_option->type) { case option_type::HEAD: - item_append(_(sm_option->description), "", FLAG_DISABLE | FLAG_UI_HEADING, nullptr); + item_append(_(sm_option->description), FLAG_DISABLE | FLAG_UI_HEADING, nullptr); break; case option_type::SEP: item_append(menu_item_type::SEPARATOR); break; case option_type::CMD: - item_append(_(sm_option->description), "", 0, static_cast<void*>(&(*sm_option))); + item_append(_(sm_option->description), 0, reinterpret_cast<void *>(&*sm_option)); break; case option_type::EMU: case option_type::UI: case option_type::OSD: switch (sm_option->entry->type()) { - case OPTION_BOOLEAN: - item_append_on_off(_(sm_option->description), - sm_option->options->bool_value(sm_option->name), - 0, - static_cast<void*>(&(*sm_option))); + case core_options::option_type::BOOLEAN: + item_append_on_off( + _(sm_option->description), + sm_option->options->bool_value(sm_option->name), + 0, + static_cast<void*>(&(*sm_option))); break; - case OPTION_INTEGER: + case core_options::option_type::INTEGER: { int i_min, i_max; int i_cur = atoi(sm_option->entry->value()); @@ -357,13 +389,14 @@ void submenu::populate(float &customtop, float &custombottom) i_max = std::numeric_limits<int>::max(); } arrow_flags = get_arrow_flags(i_min, i_max, i_cur); - item_append(_(sm_option->description), - sm_option->entry->value(), - arrow_flags, - static_cast<void*>(&(*sm_option))); + item_append( + _(sm_option->description), + sm_option->entry->value(), + arrow_flags, + reinterpret_cast<void *>(&*sm_option)); } break; - case OPTION_FLOAT: + case core_options::option_type::FLOAT: { float f_min, f_max; float f_cur = atof(sm_option->entry->value()); @@ -374,32 +407,37 @@ void submenu::populate(float &customtop, float &custombottom) } else { - f_min = 0.0f; + f_min = 0.0F; f_max = std::numeric_limits<float>::max(); } arrow_flags = get_arrow_flags(f_min, f_max, f_cur); std::string tmptxt = string_format("%g", f_cur); - item_append(_(sm_option->description), - tmptxt.c_str(), - arrow_flags, - static_cast<void*>(&(*sm_option))); + item_append( + _(sm_option->description), + tmptxt, + arrow_flags, + reinterpret_cast<void *>(&*sm_option)); } break; - case OPTION_STRING: + case core_options::option_type::STRING: { std::string v_cur(sm_option->entry->value()); int const cur_value = std::distance(sm_option->value.begin(), std::find(sm_option->value.begin(), sm_option->value.end(), v_cur)); arrow_flags = get_arrow_flags(0, int(unsigned(sm_option->value.size() - 1)), cur_value); - item_append(_(sm_option->description), + item_append( + _(sm_option->description), sm_option->options->value(sm_option->name), - arrow_flags, static_cast<void*>(&(*sm_option))); + arrow_flags, + reinterpret_cast<void *>(&*sm_option)); } break; default: arrow_flags = FLAG_RIGHT_ARROW; - item_append(_(sm_option->description), - sm_option->options->value(sm_option->name), - arrow_flags, static_cast<void*>(&(*sm_option))); + item_append( + _(sm_option->description), + sm_option->options->value(sm_option->name), + arrow_flags, + reinterpret_cast<void *>(&*sm_option)); break; } break; @@ -410,22 +448,25 @@ void submenu::populate(float &customtop, float &custombottom) } item_append(menu_item_type::SEPARATOR); - custombottom = customtop = ui().get_line_height() + (3.0f * ui().box_tb_border()); } //------------------------------------------------- -// perform our special rendering +// recompute metrics //------------------------------------------------- -void submenu::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +void submenu::recompute_metrics(uint32_t width, uint32_t height, float aspect) { - char const *const toptext[] = { _(m_options[0].description) }; - draw_text_box( - std::begin(toptext), std::end(toptext), - 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); + menu::recompute_metrics(width, height, aspect); + set_custom_space(0.0F, line_height() + (3.0F * tb_border())); +} + +//------------------------------------------------- +// perform our special rendering +//------------------------------------------------- + +void submenu::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +{ if (selectedref) { option &selected_sm_option(*reinterpret_cast<option *>(selectedref)); @@ -434,9 +475,9 @@ void submenu::custom_render(void *selectedref, float top, float bottom, float or char const *const bottomtext[] = { selected_sm_option.entry->description() }; draw_text_box( std::begin(bottomtext), std::end(bottomtext), - origx1, origx2, origy2 + ui().box_tb_border(), origy2 + bottom, - ui::text_layout::CENTER, ui::text_layout::TRUNCATE, false, - ui().colors().text_color(), UI_RED_COLOR, 1.0f); + origx1, origx2, origy2 + tb_border(), origy2 + bottom, + text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, false, + ui().colors().text_color(), ui().colors().background_color()); } } } diff --git a/src/frontend/mame/ui/submenu.h b/src/frontend/mame/ui/submenu.h index f17b6fe53ff..7ad92467d46 100644 --- a/src/frontend/mame/ui/submenu.h +++ b/src/frontend/mame/ui/submenu.h @@ -12,20 +12,16 @@ #pragma once -#include "emuopts.h" #include "ui/menu.h" -#if defined(UI_WINDOWS) && !defined(UI_SDL) -#include "../osd/windows/winmain.h" -#else -#include "../osd/modules/lib/osdobj_common.h" -#endif +#include "emuopts.h" #include <string> #include <vector> namespace ui { + //------------------------------------------------- // class ui menu //------------------------------------------------- @@ -46,10 +42,10 @@ public: struct option { option_type type; - const char *description; - const char *name; + const char *description = nullptr; + const char *name = nullptr; core_options::entry::shared_ptr entry; - core_options *options; + core_options *options = nullptr; std::vector<std::string> value; }; @@ -57,23 +53,24 @@ public: submenu(mame_ui_manager &mui, render_container &container, std::vector<option> &&suboptions, const game_driver *drv = nullptr, emu_options *options = nullptr); virtual ~submenu(); - static std::vector<option> const misc_options; - static std::vector<option> const advanced_options; - static std::vector<option> const control_options; - static std::vector<option> const video_options; - //static std::vector<option> const export_options; + static std::vector<option> misc_options(); + static std::vector<option> advanced_options(); + static std::vector<option> control_options(); + static std::vector<option> video_options(); + //static std::vector<option> export_options(); protected: - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) 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; std::vector<option> m_options; - game_driver const *m_driver; + game_driver const *const m_driver; }; } // namespace ui -#endif /* MAME_FRONTEND_UI_SUBMENU_H */ +#endif // MAME_FRONTEND_UI_SUBMENU_H diff --git a/src/frontend/mame/ui/swlist.cpp b/src/frontend/mame/ui/swlist.cpp index dc0e98407fe..989147f3bb4 100644 --- a/src/frontend/mame/ui/swlist.cpp +++ b/src/frontend/mame/ui/swlist.cpp @@ -14,10 +14,14 @@ #include "ui/swlist.h" #include "ui/utils.h" +#include "corestr.h" #include "softlist_dev.h" +#include <locale> + namespace ui { + /*************************************************************************** CONSTANTS ***************************************************************************/ @@ -50,8 +54,8 @@ static bool is_valid_softlist_part_char(char32_t ch) //------------------------------------------------- menu_software_parts::menu_software_parts(mame_ui_manager &mui, render_container &container, const software_info *info, const char *interface, const software_part **part, bool other_opt, result &result) - : menu(mui, container), - m_result(result) + : menu(mui, container) + , m_result(result) { m_info = info; m_interface = interface; @@ -73,42 +77,46 @@ menu_software_parts::~menu_software_parts() // populate //------------------------------------------------- -void menu_software_parts::populate(float &customtop, float &custombottom) +void menu_software_parts::populate() { + m_entries.clear(); + if (m_other_opt) { - software_part_menu_entry *entry1 = (software_part_menu_entry *) m_pool_alloc(sizeof(*entry1)); - entry1->type = result::EMPTY; - entry1->part = nullptr; - item_append(_("[empty slot]"), "", 0, entry1); + software_part_menu_entry &entry1(*m_entries.emplace(m_entries.end())); + entry1.type = result::EMPTY; + entry1.part = nullptr; + item_append(_("[empty slot]"), 0, &entry1); - software_part_menu_entry *entry2 = (software_part_menu_entry *) m_pool_alloc(sizeof(*entry2)); - entry2->type = result::FMGR; - entry2->part = nullptr; - item_append(_("[file manager]"), "", 0, entry2); + software_part_menu_entry &entry2(*m_entries.emplace(m_entries.end())); + entry2.type = result::FMGR; + entry2.part = nullptr; + item_append(_("[file manager]"), 0, &entry2); - software_part_menu_entry *entry3 = (software_part_menu_entry *) m_pool_alloc(sizeof(*entry3)); - entry3->type = result::SWLIST; - entry3->part = nullptr; - item_append(_("[software list]"), "", 0, entry3); + software_part_menu_entry &entry3(*m_entries.emplace(m_entries.end())); + entry3.type = result::SWLIST; + entry3.part = nullptr; + item_append(_("[software list]"), 0, &entry3); } for (const software_part &swpart : m_info->parts()) { if (swpart.matches_interface(m_interface)) { - software_part_menu_entry *entry = (software_part_menu_entry *) m_pool_alloc(sizeof(*entry)); + software_part_menu_entry &entry(*m_entries.emplace(m_entries.end())); // check if the available parts have specific part_id to be displayed (e.g. "Map Disc", "Bonus Disc", etc.) // if not, we simply display "part_name"; if yes we display "part_name (part_id)" std::string menu_part_name(swpart.name()); if (swpart.feature("part_id") != nullptr) menu_part_name.append(" (").append(swpart.feature("part_id")).append(")"); - entry->type = result::ENTRY; - entry->part = &swpart; - item_append(m_info->shortname(), menu_part_name, 0, entry); + entry.type = result::ENTRY; + entry.part = &swpart; + item_append(m_info->shortname(), menu_part_name, 0, &entry); } } + + item_append(menu_item_type::SEPARATOR); } @@ -116,18 +124,17 @@ void menu_software_parts::populate(float &customtop, float &custombottom) // handle //------------------------------------------------- -void menu_software_parts::handle() +bool menu_software_parts::handle(event const *ev) { - // process the menu - const event *event = process(0); - - if (event != nullptr && event->iptkey == IPT_UI_SELECT && event->itemref != nullptr) + if (ev && (ev->iptkey == IPT_UI_SELECT) && ev->itemref) { - software_part_menu_entry *entry = (software_part_menu_entry *) event->itemref; + software_part_menu_entry *entry = (software_part_menu_entry *)ev->itemref; m_result = entry->type; *m_selected_part = entry->part; stack_pop(); } + + return false; } @@ -142,9 +149,12 @@ void menu_software_parts::handle() menu_software_list::menu_software_list(mame_ui_manager &mui, render_container &container, software_list_device *swlist, const char *interface, std::string &result) : menu(mui, container), m_result(result) { + set_heading(swlist->description()); + + set_process_flags(PROCESS_IGNOREPAUSE); m_swlist = swlist; m_interface = interface; - m_ordered_by_shortname = true; + m_ordered_by_shortname = false; } @@ -158,26 +168,6 @@ menu_software_list::~menu_software_list() //------------------------------------------------- -// compare_entries -//------------------------------------------------- - -int menu_software_list::compare_entries(const entry_info &e1, const entry_info &e2, bool shortname) -{ - int result; - const char *e1_basename = shortname ? e1.short_name.c_str() : e1.long_name.c_str(); - const char *e2_basename = shortname ? e2.short_name.c_str() : e2.long_name.c_str(); - - result = core_stricmp(e1_basename, e2_basename); - if (result == 0) - { - result = strcmp(e1_basename, e2_basename); - } - - return result; -} - - -//------------------------------------------------- // append_software_entry - populate a specific list //------------------------------------------------- @@ -200,13 +190,54 @@ void menu_software_list::append_software_entry(const software_info &swinfo) // skip this if no new entry has been allocated (e.g. if the software has no matching interface for this image device) if (entry_updated) + m_entrylist.emplace_back(std::move(entry)); +} + + +//------------------------------------------------- +// update_search - update meunu for new search text +//------------------------------------------------- + +void menu_software_list::update_search(void *selectedref) +{ + // display the popup + ui().popup_time(ERROR_MESSAGE_TIME, "%s", m_search); + + // identify the selected entry + entry_info const *const cur_selected = (uintptr_t(selectedref) != 1) + ? reinterpret_cast<entry_info const *>(get_selection_ref()) + : nullptr; + + // if it's a perfect match for the current selection, don't move it + if (!cur_selected || core_strnicmp((m_ordered_by_shortname ? cur_selected->short_name : cur_selected->long_name).c_str(), m_search.c_str(), m_search.size())) { - // find the end of the list - auto iter = m_entrylist.begin(); - while (iter != m_entrylist.end() && compare_entries(entry, *iter, m_ordered_by_shortname) >= 0) - ++iter; + std::string::size_type bestmatch(0); + entry_info const *selected_entry(cur_selected); + for (auto &entry : m_entrylist) + { + // TODO: more efficient "common prefix" code + auto const &compare_name = m_ordered_by_shortname ? entry.short_name : entry.long_name; + std::string::size_type match(0); + for (std::string::size_type i = 1; m_search.size() >= i; ++i) + { + if (!core_strnicmp(compare_name.c_str(), m_search.c_str(), i)) + match = i; + else + break; + } + + if (match > bestmatch) + { + bestmatch = match; + selected_entry = &entry; + } + } - m_entrylist.emplace(iter, std::move(entry)); + if (selected_entry && (selected_entry != cur_selected)) + { + set_selection((void *)selected_entry); + centre_selection(); + } } } @@ -215,21 +246,53 @@ void menu_software_list::append_software_entry(const software_info &swinfo) // populate //------------------------------------------------- -void menu_software_list::populate(float &customtop, float &custombottom) +void menu_software_list::populate() { - // clear all entries before populating - m_entrylist.clear(); - // build up the list of entries for the menu - for (const software_info &swinfo : m_swlist->get_info()) - append_software_entry(swinfo); + if (m_entrylist.empty()) + for (const software_info &swinfo : m_swlist->get_info()) + append_software_entry(swinfo); + + if (m_entrylist.size() > 1) + { + // add an entry to change ordering + item_append(_("Switch Item Ordering"), 0, ITEMREF_SWITCH_ITEM_ORDERING); + + // initial cursor to first entry in the list + set_selected_index(1); + } + + if (m_ordered_by_shortname) + { + // short names are restricted to lowercase ASCII anyway, a dumb compare works + m_entrylist.sort([] (entry_info const &e1, entry_info const &e2) { return e1.short_name < e2.short_name; }); - // add an entry to change ordering - item_append(_("Switch Item Ordering"), "", 0, ITEMREF_SWITCH_ITEM_ORDERING); + // append all of the menu entries + for (auto &entry : m_entrylist) + item_append(entry.short_name, entry.long_name, 0, &entry); + } + else + { + std::locale const lcl; + std::collate<wchar_t> const &coll = std::use_facet<std::collate<wchar_t> >(lcl); + m_entrylist.sort( + [&coll] (entry_info const &e1, entry_info const &e2) -> bool + { + std::wstring const xstr = wstring_from_utf8(e1.long_name); + std::wstring const ystr = wstring_from_utf8(e2.long_name); + auto const cmp = coll.compare(xstr.data(), xstr.data() + xstr.size(), ystr.data(), ystr.data() + ystr.size()); + if (cmp) + return cmp < 0; + else + return e1.short_name < e2.short_name; + }); + + // append all of the menu entries + for (auto &entry : m_entrylist) + item_append(entry.long_name, entry.short_name, 0, &entry); + } - // append all of the menu entries - for (auto &entry : m_entrylist) - item_append(entry.short_name, entry.long_name, 0, &entry); + item_append(menu_item_type::SEPARATOR); } @@ -237,84 +300,79 @@ void menu_software_list::populate(float &customtop, float &custombottom) // handle //------------------------------------------------- -void menu_software_list::handle() +bool menu_software_list::handle(event const *ev) { - const entry_info *selected_entry = nullptr; - int bestmatch = 0; - - // process the menu - const event *event = process(0); - - if (event && event->itemref) + if (!ev) { - if (event->itemref == ITEMREF_SWITCH_ITEM_ORDERING && event->iptkey == IPT_UI_SELECT) + return false; + } + else if (ev->iptkey == IPT_UI_SELECT) + { + if (ev->itemref == ITEMREF_SWITCH_ITEM_ORDERING) { m_ordered_by_shortname = !m_ordered_by_shortname; // reset the char buffer if we change ordering criterion - m_filename_buffer.clear(); + m_search.clear(); // reload the menu with the new order reset(reset_options::REMEMBER_REF); - machine().popmessage(_("Switched Order: entries now ordered by %s"), m_ordered_by_shortname ? _("shortname") : _("description")); + machine().popmessage( + m_ordered_by_shortname + ? _("Switched Order: entries now ordered by shortname") + : _("Switched Order: entries now ordered by description")); } - // handle selections - else if (event->iptkey == IPT_UI_SELECT) + else if (ev->itemref) { - entry_info *info = (entry_info *) event->itemref; + // handle selections + entry_info *info = (entry_info *)ev->itemref; m_result = info->short_name; stack_pop(); } - else if (event->iptkey == IPT_SPECIAL) + return false; + } + else if (ev->iptkey == IPT_UI_PASTE) + { + if (paste_text(m_search, m_ordered_by_shortname ? is_valid_softlist_part_char : uchar_is_printable)) { - if (input_character(m_filename_buffer, event->unichar, &is_valid_softlist_part_char)) - { - // display the popup - ui().popup_time(ERROR_MESSAGE_TIME, "%s", m_filename_buffer); - - // identify the selected entry - entry_info const *const cur_selected = (uintptr_t(event->itemref) != 1) - ? reinterpret_cast<entry_info const *>(get_selection_ref()) - : nullptr; - - // loop through all entries - for (auto &entry : m_entrylist) - { - // is this entry the selected entry? - if (cur_selected != &entry) - { - auto &compare_name = m_ordered_by_shortname ? entry.short_name : entry.long_name; - - int match = 0; - for (int i = 0; i < m_filename_buffer.size() + 1; i++) - { - if (core_strnicmp(compare_name.c_str(), m_filename_buffer.c_str(), i) == 0) - match = i; - } - - if (match > bestmatch) - { - bestmatch = match; - selected_entry = &entry; - } - } - } - - if (selected_entry != nullptr && selected_entry != cur_selected) - { - set_selection((void *)selected_entry); - centre_selection(); - } - } + update_search(ev->itemref); + return true; } - else if (event->iptkey == IPT_UI_CANCEL) + else { - // reset the char buffer also in this case - m_filename_buffer.clear(); - m_result = m_filename_buffer; - stack_pop(); + return false; + } + } + else if (ev->iptkey == IPT_SPECIAL) + { + if (input_character(m_search, ev->unichar, m_ordered_by_shortname ? is_valid_softlist_part_char : uchar_is_printable)) + { + update_search(ev->itemref); + return true; + } + else + { + return false; + } + } + else if (ev->iptkey == IPT_UI_CANCEL) + { + // reset the char buffer also in this case + if (!m_search.empty()) + { + m_search.clear(); + ui().popup_time(ERROR_MESSAGE_TIME, "%s", m_search); + return true; + } + else + { + return false; } } + else + { + return false; + } } @@ -330,6 +388,8 @@ void menu_software_list::handle() menu_software::menu_software(mame_ui_manager &mui, render_container &container, const char *interface, software_list_device **result) : menu(mui, container) { + set_heading(_("Software List")); + m_interface = interface; m_result = result; } @@ -348,14 +408,14 @@ menu_software::~menu_software() // populate //------------------------------------------------- -void menu_software::populate(float &customtop, float &custombottom) +void menu_software::populate() { bool have_compatible = false; // Add original software lists for this system - software_list_device_iterator iter(machine().config().root_device()); + software_list_device_enumerator iter(machine().config().root_device()); for (software_list_device &swlistdev : iter) - if (swlistdev.list_type() == SOFTWARE_LIST_ORIGINAL_SYSTEM) + if (swlistdev.is_original()) if (!swlistdev.get_info().empty() && m_interface != nullptr) { bool found = false; @@ -367,12 +427,12 @@ void menu_software::populate(float &customtop, float &custombottom) break; } if (found) - item_append(swlistdev.description(), "", 0, (void *)&swlistdev); + item_append(swlistdev.description(), 0, (void *)&swlistdev); } // add compatible software lists for this system for (software_list_device &swlistdev : iter) - if (swlistdev.list_type() == SOFTWARE_LIST_COMPATIBLE_SYSTEM) + if (swlistdev.is_compatible()) if (!swlistdev.get_info().empty() && m_interface != nullptr) { bool found = false; @@ -386,11 +446,13 @@ void menu_software::populate(float &customtop, float &custombottom) if (found) { if (!have_compatible) - item_append(_("[compatible lists]"), "", FLAG_DISABLE, nullptr); - item_append(swlistdev.description(), "", 0, (void *)&swlistdev); + item_append(_("[compatible lists]"), FLAG_UI_HEADING | FLAG_DISABLE, nullptr); + item_append(swlistdev.description(), 0, (void *)&swlistdev); } have_compatible = true; } + + item_append(menu_item_type::SEPARATOR); } @@ -398,17 +460,16 @@ void menu_software::populate(float &customtop, float &custombottom) // handle //------------------------------------------------- -void menu_software::handle() +bool menu_software::handle(event const *ev) { - // process the menu - const event *event = process(0); - - if (event != nullptr && event->iptkey == IPT_UI_SELECT) + if (ev && (ev->iptkey == IPT_UI_SELECT)) { - //menu::stack_push<menu_software_list>(ui(), container(), (software_list_config *)event->itemref, image); - *m_result = reinterpret_cast<software_list_device *>(event->itemref); + //menu::stack_push<menu_software_list>(ui(), container(), (software_list_config *)ev->itemref, image); + *m_result = reinterpret_cast<software_list_device *>(ev->itemref); stack_pop(); } + + return false; } } // namespace ui diff --git a/src/frontend/mame/ui/swlist.h b/src/frontend/mame/ui/swlist.h index d0239c2c389..891d81e054b 100644 --- a/src/frontend/mame/ui/swlist.h +++ b/src/frontend/mame/ui/swlist.h @@ -13,8 +13,11 @@ #include "ui/menu.h" +#include <list> + namespace ui { + // ======================> menu_software_parts class menu_software_parts : public menu @@ -33,15 +36,18 @@ public: virtual ~menu_software_parts() override; private: - struct software_part_menu_entry { + struct software_part_menu_entry + { result type; const software_part *part; }; + using entry_list = std::list<software_part_menu_entry>; - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; + virtual bool handle(event const *ev) override; // variables + entry_list m_entries; const software_info * m_info; const char * m_interface; const software_part ** m_selected_part; @@ -57,8 +63,9 @@ class menu_software_list : public menu public: menu_software_list(mame_ui_manager &mui, render_container &container, software_list_device *swlist, const char *interface, std::string &result); virtual ~menu_software_list() override; - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + +protected: + virtual bool custom_ui_back() override { return !m_search.empty(); } private: struct entry_info @@ -78,12 +85,15 @@ private: const char * m_interface; std::string & m_result; std::list<entry_info> m_entrylist; - std::string m_filename_buffer; + std::string m_search; bool m_ordered_by_shortname; + virtual void populate() override; + virtual bool handle(event const *ev) override; + // functions - int compare_entries(const entry_info &e1, const entry_info &e2, bool shortname); void append_software_entry(const software_info &swinfo); + void update_search(void *selectedref); }; @@ -94,8 +104,8 @@ class menu_software : public menu public: menu_software(mame_ui_manager &mui, render_container &container, const char *interface, software_list_device **result); virtual ~menu_software() override; - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; + virtual bool handle(event const *ev) override; private: const char * m_interface; @@ -104,4 +114,4 @@ private: } // namespace ui -#endif /* MAME_FRONTEND_UI_SWLIST_H */ +#endif // MAME_FRONTEND_UI_SWLIST_H diff --git a/src/frontend/mame/ui/systemlist.cpp b/src/frontend/mame/ui/systemlist.cpp new file mode 100644 index 00000000000..d7450aac162 --- /dev/null +++ b/src/frontend/mame/ui/systemlist.cpp @@ -0,0 +1,422 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + ui/systemlist.h + + Persistent system list data. + +***************************************************************************/ + +#include "emu.h" +#include "ui/systemlist.h" + +#include "ui/moptions.h" + +#include "drivenum.h" +#include "fileio.h" + +#include "util/corestr.h" +#include "util/unicode.h" + +#include <algorithm> +#include <cassert> +#include <functional> +#include <locale> +#include <string_view> + + +namespace ui { + +void system_list::cache_data(ui_options const &options) +{ + std::unique_lock<std::mutex> lock(m_mutex); + if (!m_started) + { + m_started = true; +#if defined(__EMSCRIPTEN__) + std::invoke( +#else + m_thread = std::make_unique<std::thread>( +#endif + [this, datpath = std::string(options.history_path()), titles = std::string(options.system_names())] + { + do_cache_data(datpath, titles); + }); + } +} + + +void system_list::reset_cache() +{ + std::unique_lock<std::mutex> lock(m_mutex); + if (m_thread) + m_thread->join(); + m_thread.reset(); + m_started = false; + m_available = AVAIL_NONE; + m_systems.clear(); + m_sorted_list.clear(); + m_filter_data = machine_filter_data(); + m_bios_count = 0; +} + + +void system_list::wait_available(available desired) +{ + if (!is_available(desired)) + { + assert(m_started); + std::unique_lock<std::mutex> lock(m_mutex); + if (!is_available(desired)) + m_condition.wait(lock, [this, desired] () { return is_available(desired); }); + } +} + + +system_list &system_list::instance() +{ + static system_list data; + return data; +} + + +system_list::system_list() + : m_started(false) + , m_available(AVAIL_NONE) + , m_bios_count(0) +{ +} + + +system_list::~system_list() +{ + if (m_thread) + m_thread->join(); +} + + +void system_list::notify_available(available value) +{ + std::unique_lock<std::mutex> lock(m_mutex); + m_available.fetch_or(value, std::memory_order_release); + m_condition.notify_all(); +} + + +void system_list::do_cache_data(std::string const &datpath, std::string const &titles) +{ + // try to open the titles file for optimisation reasons + emu_file titles_file(datpath, OPEN_FLAG_READ); + bool const try_titles(!titles.empty() && !titles_file.open(titles)); + + // generate full list - initially ordered by shortname + populate_list(!try_titles); + + // notify that BIOS count is valie + notify_available(AVAIL_BIOS_COUNT); + + // try to load localised descriptions + if (try_titles) + { + load_titles(titles_file); + + // populate parent descriptions while still ordered by shortname + // already done on the first pass if built-in titles are used + populate_parents(); + } + + // system names are finalised now + notify_available(AVAIL_SYSTEM_NAMES); + + // get rid of the "empty" driver - we don't need positions to line up any more + m_sorted_list.reserve(m_systems.size() - 1); + auto const empty(driver_list::find(GAME_NAME(___empty))); + for (ui_system_info &info : m_systems) + { + if (info.index != empty) + m_sorted_list.emplace_back(info); + } + + // sort drivers and notify + std::locale const lcl; + std::collate<wchar_t> const &coll = std::use_facet<std::collate<wchar_t> >(lcl); + auto const compare_names = + [&coll] (std::wstring const &wx, std::wstring const &wy) -> bool + { + return 0 > coll.compare(wx.data(), wx.data() + wx.size(), wy.data(), wy.data() + wy.size()); + }; + std::stable_sort( + m_sorted_list.begin(), + m_sorted_list.end(), + [&compare_names] (ui_system_info const &lhs, ui_system_info const &rhs) + { + game_driver const &x(*lhs.driver); + game_driver const &y(*rhs.driver); + + if (!lhs.is_clone && !rhs.is_clone) + { + return compare_names( + lhs.reading_description.empty() ? wstring_from_utf8(lhs.description) : lhs.reading_description, + rhs.reading_description.empty() ? wstring_from_utf8(rhs.description) : rhs.reading_description); + } + else if (lhs.is_clone && rhs.is_clone) + { + if (!std::strcmp(x.parent, y.parent)) + { + return compare_names( + lhs.reading_description.empty() ? wstring_from_utf8(lhs.description) : lhs.reading_description, + rhs.reading_description.empty() ? wstring_from_utf8(rhs.description) : rhs.reading_description); + } + else + { + return compare_names( + lhs.reading_parent.empty() ? wstring_from_utf8(lhs.parent) : lhs.reading_parent, + rhs.reading_parent.empty() ? wstring_from_utf8(rhs.parent) : rhs.reading_parent); + } + } + else if (!lhs.is_clone && rhs.is_clone) + { + if (!std::strcmp(x.name, y.parent)) + { + return true; + } + else + { + return compare_names( + lhs.reading_description.empty() ? wstring_from_utf8(lhs.description) : lhs.reading_description, + rhs.reading_parent.empty() ? wstring_from_utf8(rhs.parent) : rhs.reading_parent); + } + } + else + { + if (!std::strcmp(x.parent, y.name)) + { + return false; + } + else + { + return compare_names( + lhs.reading_parent.empty() ? wstring_from_utf8(lhs.parent) : lhs.reading_parent, + rhs.reading_description.empty() ? wstring_from_utf8(rhs.description) : rhs.reading_description); + } + } + }); + notify_available(AVAIL_SORTED_LIST); + + // sort manufacturers and years + m_filter_data.finalise(); + notify_available(AVAIL_FILTER_DATA); + + // convert shortnames to UCS-4 + for (ui_system_info &info : m_sorted_list) + info.ucs_shortname = ustr_from_utf8(normalize_unicode(info.driver->name, unicode_normalization_form::D, true)); + notify_available(AVAIL_UCS_SHORTNAME); + + // convert descriptions to UCS-4 + for (ui_system_info &info : m_sorted_list) + info.ucs_description = ustr_from_utf8(normalize_unicode(info.description, unicode_normalization_form::D, true)); + notify_available(AVAIL_UCS_DESCRIPTION); + + // convert "<manufacturer> <description>" to UCS-4 + std::string buf; + for (ui_system_info &info : m_sorted_list) + { + buf.assign(info.driver->manufacturer); + buf.append(1, ' '); + buf.append(info.description); + info.ucs_manufacturer_description = ustr_from_utf8(normalize_unicode(buf, unicode_normalization_form::D, true)); + } + notify_available(AVAIL_UCS_MANUF_DESC); + + // convert default descriptions to UCS-4 + if (try_titles) + { + for (ui_system_info &info : m_sorted_list) + { + std::string_view const fullname(info.driver->type.fullname()); + if (info.description != fullname) + info.ucs_default_description = ustr_from_utf8(normalize_unicode(fullname, unicode_normalization_form::D, true)); + } + } + notify_available(AVAIL_UCS_DFLT_DESC); + + // convert "<manufacturer> <default description>" to UCS-4 + if (try_titles) + { + for (ui_system_info &info : m_sorted_list) + { + std::string_view const fullname(info.driver->type.fullname()); + if (info.description != fullname) + { + buf.assign(info.driver->manufacturer); + buf.append(1, ' '); + buf.append(fullname); + info.ucs_manufacturer_default_description = ustr_from_utf8(normalize_unicode(buf, unicode_normalization_form::D, true)); + } + } + } + notify_available(AVAIL_UCS_MANUF_DFLT_DESC); +} + + +void system_list::populate_list(bool copydesc) +{ + m_systems.reserve(driver_list::total()); + std::unordered_set<std::string> manufacturers, years; + for (int x = 0; x < driver_list::total(); ++x) + { + game_driver const &driver(driver_list::driver(x)); + ui_system_info &ins(m_systems.emplace_back(driver, x, false)); + if (&driver != &GAME_NAME(___empty)) + { + if (driver.flags & machine_flags::IS_BIOS_ROOT) + ++m_bios_count; + + if ((driver.parent[0] != '0') || driver.parent[1]) + { + auto const parentindex(driver_list::find(driver.parent)); + if (copydesc) + { + if (0 <= parentindex) + { + game_driver const &parentdriver(driver_list::driver(parentindex)); + ins.is_clone = !(parentdriver.flags & machine_flags::IS_BIOS_ROOT); + ins.parent = parentdriver.type.fullname(); + } + else + { + ins.is_clone = false; + ins.parent = driver.parent; + } + } + else + { + ins.is_clone = (0 <= parentindex) && !(driver_list::driver(parentindex).flags & machine_flags::IS_BIOS_ROOT); + } + } + + if (copydesc) + ins.description = driver.type.fullname(); + + m_filter_data.add_manufacturer(driver.manufacturer); + m_filter_data.add_year(driver.year); + m_filter_data.add_source_file(driver.type.source()); + } + } +} + + +void system_list::load_titles(util::core_file &file) +{ + char readbuf[1024]; + std::string convbuf; + while (file.gets(readbuf, std::size(readbuf))) + { + // shortname, description, and description reading separated by tab + auto const eoln( + std::find_if( + std::begin(readbuf), + std::end(readbuf), + [] (char ch) { return !ch || ('\n' == ch) || ('\r' == ch); })); + auto const split(std::find(std::begin(readbuf), eoln, '\t')); + if (eoln == split) + continue; + std::string_view const shortname(readbuf, split - readbuf); + + // find matching system - still sorted by shortname at this point + auto const found( + std::lower_bound( + m_systems.begin(), + m_systems.end(), + shortname, + [] (ui_system_info const &a, std::string_view const &b) + { + return a.driver->name < b; + })); + if ((m_systems.end() == found) || (shortname != found->driver->name)) + { + //osd_printf_verbose("System '%s' not found\n", shortname); very spammy for single-driver builds + continue; + } + + // find the end of the description + auto const descstart(std::next(split)); + auto const descend(std::find(descstart, eoln, '\t')); + auto const description(strtrimspace(std::string_view(descstart, descend - descstart))); + if (description.empty()) + { + osd_printf_warning("Empty translated description for system '%s'\n", shortname); + } + else if (!found->description.empty()) + { + osd_printf_warning( + "Multiple translated descriptions for system '%s' ('%s' and '%s')\n", + shortname, + found->description, + description); + } + else + { + found->description = description; + } + + // populate the reading if it's present + if (eoln == descend) + continue; + auto const readstart(std::next(descend)); + auto const readend(std::find(readstart, eoln, '\t')); + auto const reading(strtrimspace(std::string_view(readstart, readend - readstart))); + if (reading.empty()) + { + osd_printf_warning("Empty translated description reading for system '%s'\n", shortname); + } + else + { + found->reading_description = wstring_from_utf8(reading); + found->ucs_reading_description = ustr_from_utf8(normalize_unicode(reading, unicode_normalization_form::D, true)); + convbuf.assign(found->driver->manufacturer); + convbuf.append(1, ' '); + convbuf.append(reading); + found->ucs_manufacturer_reading_description = ustr_from_utf8(normalize_unicode(convbuf, unicode_normalization_form::D, true)); + } + } + + // fill in untranslated descriptions + for (ui_system_info &info : m_systems) + { + if (info.description.empty()) + info.description = info.driver->type.fullname(); + } +} + + +void system_list::populate_parents() +{ + for (ui_system_info &info : m_systems) + { + if ((info.driver->parent[0] != '0') || info.driver->parent[1]) + { + auto const found( + std::lower_bound( + m_systems.begin(), + m_systems.end(), + std::string_view(info.driver->parent), + [] (ui_system_info const &a, std::string_view const &b) + { + return a.driver->name < b; + })); + if (m_systems.end() != found) + { + info.parent = found->description; + info.reading_parent = found->reading_description; + } + else + { + info.parent = info.driver->parent; + } + } + } +} + +} // namespace ui diff --git a/src/frontend/mame/ui/systemlist.h b/src/frontend/mame/ui/systemlist.h new file mode 100644 index 00000000000..6c3ee17dad7 --- /dev/null +++ b/src/frontend/mame/ui/systemlist.h @@ -0,0 +1,122 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + ui/systemlist.h + + Persistent system list data. + +***************************************************************************/ +#ifndef MAME_FRONTEND_UI_SYSTEMLIST_H +#define MAME_FRONTEND_UI_SYSTEMLIST_H + +#pragma once + +#include "ui/utils.h" + +#include <atomic> +#include <condition_variable> +#include <functional> +#include <memory> +#include <mutex> +#include <string> +#include <thread> +#include <vector> + + +class ui_options; + + +namespace ui { + +class system_list +{ +public: + enum available : unsigned + { + AVAIL_NONE = 0U, + AVAIL_SYSTEM_NAMES = 1U << 0, + AVAIL_SORTED_LIST = 1U << 1, + AVAIL_BIOS_COUNT = 1U << 2, + AVAIL_UCS_SHORTNAME = 1U << 3, + AVAIL_UCS_DESCRIPTION = 1U << 4, + AVAIL_UCS_MANUF_DESC = 1U << 5, + AVAIL_UCS_DFLT_DESC = 1U << 6, + AVAIL_UCS_MANUF_DFLT_DESC = 1U << 7, + AVAIL_FILTER_DATA = 1U << 8 + }; + + using system_vector = std::vector<ui_system_info>; + using system_reference = std::reference_wrapper<ui_system_info>; + using system_reference_vector = std::vector<system_reference>; + + void cache_data(ui_options const &options); + + void reset_cache(); + + bool is_available(available desired) const + { + return (m_available.load(std::memory_order_acquire) & desired) == desired; + } + + void wait_available(available desired); + + system_vector const &systems() + { + wait_available(AVAIL_SYSTEM_NAMES); + return m_systems; + } + + system_reference_vector const &sorted_list() + { + wait_available(AVAIL_SORTED_LIST); + return m_sorted_list; + } + + int bios_count() + { + wait_available(AVAIL_BIOS_COUNT); + return m_bios_count; + } + + bool unavailable_systems() + { + wait_available(AVAIL_SORTED_LIST); + return std::find_if(m_sorted_list.begin(), m_sorted_list.end(), [] (ui_system_info const &info) { return !info.available; }) != m_sorted_list.end(); + } + + machine_filter_data &filter_data() + { + wait_available(AVAIL_FILTER_DATA); + return m_filter_data; + } + + static system_list &instance(); + +private: + system_list(); + ~system_list(); + + void notify_available(available value); + void do_cache_data(std::string const &datpath, std::string const &titles); + void populate_list(bool copydesc); + void load_titles(util::core_file &file); + void populate_parents(); + + // synchronisation + std::mutex m_mutex; + std::condition_variable m_condition; + std::unique_ptr<std::thread> m_thread; + std::atomic<bool> m_started; + std::atomic<unsigned> m_available; + + // data + system_vector m_systems; + system_reference_vector m_sorted_list; + machine_filter_data m_filter_data; + int m_bios_count; +}; + +} // namespace ui + +#endif // MAME_FRONTEND_UI_SYSTEMLIST_H diff --git a/src/frontend/mame/ui/tapectrl.cpp b/src/frontend/mame/ui/tapectrl.cpp index 0784ed72d26..a80e5856ed1 100644 --- a/src/frontend/mame/ui/tapectrl.cpp +++ b/src/frontend/mame/ui/tapectrl.cpp @@ -9,22 +9,47 @@ ***************************************************************************/ #include "emu.h" - #include "ui/tapectrl.h" +#include <string_view> + + namespace ui { + +namespace { + /*************************************************************************** CONSTANTS ***************************************************************************/ -#define TAPECMD_NULL ((void *) 0x0000) -#define TAPECMD_STOP ((void *) 0x0001) -#define TAPECMD_PLAY ((void *) 0x0002) -#define TAPECMD_RECORD ((void *) 0x0003) -#define TAPECMD_REWIND ((void *) 0x0004) -#define TAPECMD_FAST_FORWARD ((void *) 0x0005) -#define TAPECMD_SLIDER ((void *) 0x0006) -#define TAPECMD_SELECT ((void *) 0x0007) +#define TAPECMD_NULL ((void *)0x0000) +#define TAPECMD_STOP ((void *)0x0001) +#define TAPECMD_PLAY ((void *)0x0002) +#define TAPECMD_RECORD ((void *)0x0003) +#define TAPECMD_REWIND ((void *)0x0004) +#define TAPECMD_FAST_FORWARD ((void *)0x0005) +#define TAPECMD_SLIDER ((void *)0x0006) +#define TAPECMD_SELECT ((void *)0x0007) + + +inline std::string_view tape_state_string(cassette_image_device &device) +{ + cassette_state const state(device.get_state()); + if ((state & CASSETTE_MASK_UISTATE) == CASSETTE_STOPPED) + return _("stopped"); + else if ((state & CASSETTE_MASK_UISTATE) == CASSETTE_PLAY) + return ((state & CASSETTE_MASK_MOTOR) == CASSETTE_MOTOR_ENABLED) ? _("playing") : _("(playing)"); + else + return ((state & CASSETTE_MASK_MOTOR) == CASSETTE_MOTOR_ENABLED) ? _("recording") : _("(recording)"); +} + + +inline uint32_t tape_position_flags(double position, double length) +{ + return ((position > 0.0) ? menu::FLAG_LEFT_ARROW : 0U) | ((position < length) ? menu::FLAG_RIGHT_ARROW : 0U); +} + +} // anonymous namespace /*************************************************************************** @@ -37,7 +62,14 @@ namespace ui { menu_tape_control::menu_tape_control(mame_ui_manager &mui, render_container &container, cassette_image_device *device) : menu_device_control<cassette_image_device>(mui, container, device) + , m_slider_item_index(-1) { + set_process_flags(PROCESS_LR_REPEAT); + set_heading(_("Tape Control")); + + if (device) + { + } } @@ -54,58 +86,49 @@ menu_tape_control::~menu_tape_control() // populate - populates the main tape control menu //------------------------------------------------- -void menu_tape_control::populate(float &customtop, float &custombottom) +void menu_tape_control::populate() { + m_notifier.reset(); + m_slider_item_index = -1; if (current_device()) { + // repopulate the menu if an image is mounted or unmounted + m_notifier = current_device()->add_media_change_notifier( + [this] (device_image_interface::media_change_event ev) + { + reset(reset_options::REMEMBER_POSITION); + }); + // name of tape item_append(current_display_name(), current_device()->exists() ? current_device()->filename() : "No Tape Image loaded", current_display_flags(), TAPECMD_SELECT); if (current_device()->exists()) { - std::string timepos; - cassette_state state; - double t0 = current_device()->get_position(); - double t1 = current_device()->get_length(); - uint32_t tapeflags = 0; - - // state - if (t1 > 0) - { - if (t0 > 0) - tapeflags |= FLAG_LEFT_ARROW; - if (t0 < t1) - tapeflags |= FLAG_RIGHT_ARROW; - } - - get_time_string(timepos, current_device(), nullptr, nullptr); - state = current_device()->get_state(); - item_append( - (state & CASSETTE_MASK_UISTATE) == CASSETTE_STOPPED - ? _("stopped") - : ((state & CASSETTE_MASK_UISTATE) == CASSETTE_PLAY - ? ((state & CASSETTE_MASK_MOTOR) == CASSETTE_MOTOR_ENABLED ? _("playing") : _("(playing)")) - : ((state & CASSETTE_MASK_MOTOR) == CASSETTE_MOTOR_ENABLED ? _("recording") : _("(recording)")) - ), - timepos.c_str(), - tapeflags, - TAPECMD_SLIDER); + double const t0 = current_device()->get_position(); + double const t1 = current_device()->get_length(); + m_slider_item_index = item_append( + std::string(tape_state_string(*current_device())), + util::string_format("%04d/%04d", t1 ? int(t0) : 0, int(t1)), + tape_position_flags(t0, t1), + TAPECMD_SLIDER); // pause or stop - item_append(_("Pause/Stop"), "", 0, TAPECMD_STOP); + item_append(_("Pause/Stop"), 0, TAPECMD_STOP); // play - item_append(_("Play"), "", 0, TAPECMD_PLAY); + item_append(_("Play"), 0, TAPECMD_PLAY); // record - item_append(_("Record"), "", 0, TAPECMD_RECORD); + item_append(_("Record"), 0, TAPECMD_RECORD); // rewind - item_append(_("Rewind"), "", 0, TAPECMD_REWIND); + item_append(_("Rewind"), 0, TAPECMD_REWIND); // fast forward - item_append(_("Fast Forward"), "", 0, TAPECMD_FAST_FORWARD); + item_append(_("Fast Forward"), 0, TAPECMD_FAST_FORWARD); } + + item_append(menu_item_type::SEPARATOR); } } @@ -114,71 +137,66 @@ void menu_tape_control::populate(float &customtop, float &custombottom) // handle - main tape control menu //------------------------------------------------- -void menu_tape_control::handle() +bool menu_tape_control::handle(event const *ev) { - // rebuild the menu (so to update the selected device, if the user has pressed L or R, and the tape counter) - repopulate(reset_options::REMEMBER_POSITION); - // process the menu - const event *event = process(PROCESS_LR_REPEAT); - if (event != nullptr) + if (ev) { - switch(event->iptkey) + switch (ev->iptkey) { case IPT_UI_LEFT: - if (event->itemref == TAPECMD_SLIDER) + if (ev->itemref == TAPECMD_SLIDER) + { current_device()->seek(-1, SEEK_CUR); - else if (event->itemref == TAPECMD_SELECT) + } + else if (ev->itemref == TAPECMD_SELECT) + { + m_slider_item_index = -1; previous(); + } break; case IPT_UI_RIGHT: - if (event->itemref == TAPECMD_SLIDER) + if (ev->itemref == TAPECMD_SLIDER) + { current_device()->seek(+1, SEEK_CUR); - else if (event->itemref == TAPECMD_SELECT) + } + else if (ev->itemref == TAPECMD_SELECT) + { + m_slider_item_index = -1; next(); + } break; case IPT_UI_SELECT: - if (event->itemref == TAPECMD_STOP) + if (ev->itemref == TAPECMD_STOP) current_device()->change_state(CASSETTE_STOPPED, CASSETTE_MASK_UISTATE); - else if (event->itemref == TAPECMD_PLAY) + else if (ev->itemref == TAPECMD_PLAY) current_device()->change_state(CASSETTE_PLAY, CASSETTE_MASK_UISTATE); - else if (event->itemref == TAPECMD_RECORD) + else if (ev->itemref == TAPECMD_RECORD) current_device()->change_state(CASSETTE_RECORD, CASSETTE_MASK_UISTATE); - else if (event->itemref == TAPECMD_REWIND) + else if (ev->itemref == TAPECMD_REWIND) current_device()->seek(-30, SEEK_CUR); - else if (event->itemref == TAPECMD_FAST_FORWARD) + else if (ev->itemref == TAPECMD_FAST_FORWARD) current_device()->seek(30, SEEK_CUR); - else if (event->itemref == TAPECMD_SLIDER) + else if (ev->itemref == TAPECMD_SLIDER) current_device()->seek(0, SEEK_SET); break; } } -} + // uupdate counters + if ((0 <= m_slider_item_index) && current_device() && current_device()->exists()) + { + menu_item &slider_item(item(m_slider_item_index)); + double const t0(current_device()->get_position()); + double const t1(current_device()->get_length()); + slider_item.set_text(tape_state_string(*current_device())); + slider_item.set_subtext(util::string_format("%04d/%04d", t1 ? int(t0) : 0, int(t1))); + slider_item.set_flags(tape_position_flags(t0, t1)); + } -//------------------------------------------------- -// get_time_string - returns a textual -// representation of the time -//------------------------------------------------- - -void menu_tape_control::get_time_string(std::string &dest, cassette_image_device *cassette, int *curpos, int *endpos) -{ - double t0, t1; - - t0 = cassette->get_position(); - t1 = cassette->get_length(); - - if (t1) - dest = string_format("%04d/%04d", (int)t0, (int)t1); - else - dest = string_format("%04d/%04d", 0, (int)t1); - - if (curpos != nullptr) - *curpos = t0; - if (endpos != nullptr) - *endpos = t1; + return false; } } // namespace ui diff --git a/src/frontend/mame/ui/tapectrl.h b/src/frontend/mame/ui/tapectrl.h index 11ac3bbfc7d..a8eef4b3148 100644 --- a/src/frontend/mame/ui/tapectrl.h +++ b/src/frontend/mame/ui/tapectrl.h @@ -8,15 +8,20 @@ ***************************************************************************/ -#pragma once - #ifndef MAME_FRONTEND_UI_TAPECTRL_H #define MAME_FRONTEND_UI_TAPECTRL_H -#include "imagedev/cassette.h" +#pragma once + #include "ui/devctrl.h" +#include "imagedev/cassette.h" + +#include "notifier.h" + + namespace ui { + class menu_tape_control : public menu_device_control<cassette_image_device> { public: @@ -24,12 +29,13 @@ public: virtual ~menu_tape_control() 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; - static void get_time_string(std::string &dest, cassette_image_device *cassette, int *curpos, int *endpos); + util::notifier_subscription m_notifier; + int m_slider_item_index; }; } // namespace ui -#endif /* MAME_FRONTEND_UI_TAPECTRL_H */ +#endif // MAME_FRONTEND_UI_TAPECTRL_H diff --git a/src/frontend/mame/ui/text.cpp b/src/frontend/mame/ui/text.cpp index 6df9ff29049..ffbddad418f 100644 --- a/src/frontend/mame/ui/text.cpp +++ b/src/frontend/mame/ui/text.cpp @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:Nicola Salmoria, Aaron Giles, Nathan Woods +// copyright-holders:Nathan Woods, Vas Crabb /********************************************************************* text.cpp @@ -10,11 +10,15 @@ #include "emu.h" #include "text.h" -#include "rendfont.h" + #include "render.h" +#include "rendfont.h" + +#include "util/unicode.h" #include <cstddef> #include <cstring> +#include <utility> namespace ui { @@ -75,6 +79,193 @@ inline bool is_breakable_char(char32_t ch) /*************************************************************************** +CLASS TO REPRESENT A LINE +***************************************************************************/ + +// information about the "source" of a character - also in a struct +// to facilitate copying +struct text_layout::source_info +{ + size_t start; + size_t span; +}; + + +// this should really be "positioned glyph" as glyphs != characters, but +// we'll get there eventually +struct text_layout::positioned_char +{ + char32_t character; + char_style style; + source_info source; + float xoffset; + float xwidth; +}; + + +class text_layout::line +{ +public: + using size_type = size_t; + static constexpr size_type npos = ~size_type(0); + + line(float yoffset, float height) : m_yoffset(yoffset), m_height(height) + { + } + + // methods + void add_character(text_layout &layout, char32_t ch, char_style const &style, source_info const &source) + { + // get the width of this character + float const chwidth = layout.get_char_width(ch, style.size); + + // append the positioned character + m_characters.emplace_back(positioned_char{ ch, style, source, m_width, chwidth }); + m_width += chwidth; + + // we might be bigger + m_height = std::max(m_height, style.size * layout.yscale()); + } + + void truncate(size_t position) + { + assert(position <= m_characters.size()); + + // are we actually truncating? + if (position < m_characters.size()) + { + // set the width as appropriate + m_width = m_characters[position].xoffset; + + // and resize the array + m_characters.resize(position); + } + } + + void set_justification(text_justify justify) + { + switch (justify) + { + case text_justify::RIGHT: + if (npos == m_right_justify_start) + m_right_justify_start = m_characters.size(); + [[fallthrough]]; + case text_justify::CENTER: + if (npos == m_center_justify_start) + m_center_justify_start = m_characters.size(); + break; + case text_justify::LEFT: + break; + } + } + + void align_text(text_layout const &layout) + { + assert(m_right_justify_start >= m_center_justify_start); + + if (m_characters.empty() || m_center_justify_start) + { + // at least some of the text is left-justified - anchor to left + m_anchor_pos = 0.0f; + m_anchor_target = 0.0f; + if ((layout.width() > m_width) && (m_characters.size() > m_center_justify_start)) + { + // at least some text is not left-justified + if (m_right_justify_start == m_center_justify_start) + { + // all text that isn't left-justified is right-justified + float const right_offset = layout.width() - m_width; + for (size_t i = m_right_justify_start; m_characters.size() > i; ++i) + m_characters[i].xoffset += right_offset; + m_width = layout.width(); + } + else if (m_characters.size() <= m_right_justify_start) + { + // all text that isn't left-justified is center-justified + float const center_width = m_width - m_characters[m_center_justify_start].xoffset; + float const center_offset = ((layout.width() - center_width) * 0.5f) - m_characters[m_center_justify_start].xoffset; + if (0.0f < center_offset) + { + for (size_t i = m_center_justify_start; m_characters.size() > i; ++i) + m_characters[i].xoffset += center_offset; + m_width += center_offset; + } + } + else + { + // left, right and center-justified parts + float const center_width = m_characters[m_right_justify_start].xoffset - m_characters[m_center_justify_start].xoffset; + float const center_offset = ((layout.width() - center_width) * 0.5f) - m_characters[m_center_justify_start].xoffset; + float const right_offset = layout.width() - m_width; + if (center_offset > right_offset) + { + // right-justified text pushes centre-justified text to the left + for (size_t i = m_center_justify_start; m_right_justify_start > i; ++i) + m_characters[i].xoffset += right_offset; + } + else if (0.0f < center_offset) + { + // left-justified text doesn't push centre-justified text to the right + for (size_t i = m_center_justify_start; m_right_justify_start > i; ++i) + m_characters[i].xoffset += center_offset; + } + for (size_t i = m_right_justify_start; m_characters.size() > i; ++i) + m_characters[i].xoffset += right_offset; + m_width = layout.width(); + } + } + } + else if (m_characters.size() <= m_right_justify_start) + { + // all text is center-justified - anchor to center + m_anchor_pos = 0.5f; + m_anchor_target = 0.5f; + } + else + { + // at least some text is right-justified - anchor to right + m_anchor_pos = 1.0f; + m_anchor_target = 1.0f; + if ((layout.width() > m_width) && (m_right_justify_start > m_center_justify_start)) + { + // mixed center-justified and right-justified text + float const center_width = m_characters[m_right_justify_start].xoffset; + float const center_offset = (layout.width() - m_width + (center_width * 0.5f)) - (layout.width() * 0.5f); + if (0.0f < center_offset) + { + for (size_t i = m_right_justify_start; m_characters.size() > i; ++i) + m_characters[i].xoffset += center_offset; + m_width += center_offset; + } + } + } + } + + // accessors + float xoffset(text_layout const &layout) const { return (layout.width() * m_anchor_target) - (m_width * m_anchor_pos); } + float yoffset() const { return m_yoffset; } + float width() const { return m_width; } + float height() const { return m_height; } + size_t character_count() const { return m_characters.size(); } + size_t center_justify_start() const { return m_center_justify_start; } + size_t right_justify_start() const { return m_right_justify_start; } + const positioned_char &character(size_t index) const { return m_characters[index]; } + positioned_char &character(size_t index) { return m_characters[index]; } + +private: + std::vector<positioned_char> m_characters; + size_type m_center_justify_start = npos; + size_type m_right_justify_start = npos; + float m_yoffset; + float m_height; + float m_width = 0.0f; + float m_anchor_pos = 0.0f; + float m_anchor_target = 0.0f; +}; + + + +/*************************************************************************** CORE IMPLEMENTATION ***************************************************************************/ @@ -121,43 +312,24 @@ text_layout::~text_layout() // add_text //------------------------------------------------- -void text_layout::add_text(const char *text, const char_style &style) +void text_layout::add_text(std::string_view text, text_justify line_justify, char_style const &style) { - std::size_t position = 0; - std::size_t const text_length = std::strlen(text); - - while (position < text_length) + while (!text.empty()) { // adding a character - we might change the width invalidate_calculated_actual_width(); // do we need to create a new line? - if (m_current_line == nullptr) - { - // get the current character - char32_t schar; - int const scharcount = uchar_from_utf8(&schar, &text[position], text_length - position); - if (scharcount < 0) - break; - - // if the line starts with a tab character, center it regardless - text_justify line_justify = justify(); - if (schar == '\t') - { - position += unsigned(scharcount); - line_justify = text_layout::CENTER; - } - - // start a new line - start_new_line(line_justify, style.size); - } + if (!m_current_line) + start_new_line(style.size); + m_current_line->set_justification(line_justify); // get the current character char32_t ch; - int const scharcount = uchar_from_utf8(&ch, &text[position], text_length - position); + int const scharcount = uchar_from_utf8(&ch, text); if (scharcount < 0) break; - position += unsigned(scharcount); + text.remove_prefix(scharcount); // set up source information source_info source = { 0, }; @@ -168,45 +340,43 @@ void text_layout::add_text(const char *text, const char_style &style) // is this an endline? if (ch == '\n') { - // first, start a line if we have not already - if (m_current_line == nullptr) - start_new_line(LEFT, style.size); - - // and then close up the current line + // close up the current line + m_current_line->align_text(*this); m_current_line = nullptr; } else if (!m_truncating) { // if we hit a space, remember the location and width *without* the space - if (is_space_character(ch)) + bool const is_space = is_space_character(ch); + if (is_space) m_last_break = m_current_line->character_count(); // append the character - m_current_line->add_character(ch, style, source); + m_current_line->add_character(*this, ch, style, source); // do we have to wrap? - if (wrap() != NEVER && m_current_line->width() > m_width) + if ((wrap() != word_wrapping::NEVER) && (m_current_line->width() > m_width)) { switch (wrap()) { - case TRUNCATE: - truncate_wrap(); - break; + case word_wrapping::TRUNCATE: + truncate_wrap(); + break; - case WORD: - word_wrap(); - break; + case word_wrapping::WORD: + word_wrap(); + break; - default: - fatalerror("invalid word wrapping value"); - break; + case word_wrapping::NEVER: + // can't happen due to if condition, but compile warns about it + break; } } else { - // we didn't wrap - if we hit any non-space breakable character, remember the location and width - // *with* the breakable character - if (ch != ' ' && is_breakable_char(ch)) + // we didn't wrap - if we hit any non-space breakable character, + // remember the location and width *with* the breakable character + if (!is_space && is_breakable_char(ch)) m_last_break = m_current_line->character_count(); } } @@ -228,25 +398,23 @@ void text_layout::invalidate_calculated_actual_width() // actual_left //------------------------------------------------- -float text_layout::actual_left() const +float text_layout::actual_left() { - float result; - if (empty()) + if (m_current_line) { - // degenerate scenario - result = 0; + // TODO: is there a sane way to allow an open line to be temporarily finalised and rolled back? + m_current_line->align_text(*this); + m_current_line = nullptr; } - else - { - result = 1.0f; - for (const auto &line : m_lines) - { - result = std::min(result, line->xoffset()); - // take an opportunity to break out easily - if (result <= 0) - break; - } + if (empty()) // degenerate scenario + return 0.0f; + + float result = 1.0f; + for (auto const &line : m_lines) + { + if (line->width()) + result = std::min(result, line->xoffset(*this)); } return result; } @@ -256,8 +424,15 @@ float text_layout::actual_left() const // actual_width //------------------------------------------------- -float text_layout::actual_width() const +float text_layout::actual_width() { + if (m_current_line) + { + // TODO: is there a sane way to allow an open line to be temporarily finalised and rolled back? + m_current_line->align_text(*this); + m_current_line = nullptr; + } + // do we need to calculate the width? if (m_calculated_actual_width < 0) { @@ -265,7 +440,6 @@ float text_layout::actual_width() const m_calculated_actual_width = 0; for (const auto &line : m_lines) m_calculated_actual_width = std::max(m_calculated_actual_width, line->width()); - } // return it @@ -277,14 +451,12 @@ float text_layout::actual_width() const // actual_height //------------------------------------------------- -float text_layout::actual_height() const +float text_layout::actual_height() { - line *last_line = (m_lines.size() > 0) - ? m_lines[m_lines.size() - 1].get() - : nullptr; - return last_line - ? last_line->yoffset() + last_line->height() - : 0; + if (!m_lines.empty()) + return m_lines.back()->yoffset() + m_lines.back()->height(); + else + return 0.0f; } @@ -292,18 +464,12 @@ float text_layout::actual_height() const // start_new_line //------------------------------------------------- -void text_layout::start_new_line(text_layout::text_justify justify, float height) +void text_layout::start_new_line(float height) { - // create a new line - std::unique_ptr<line> new_line(global_alloc_clear<line>(*this, justify, actual_height(), height * yscale())); - // update the current line - m_current_line = new_line.get(); + m_current_line = m_lines.emplace_back(std::make_unique<line>(actual_height(), height * yscale())).get(); m_last_break = 0; m_truncating = false; - - // append it - m_lines.push_back(std::move(new_line)); } @@ -337,7 +503,7 @@ void text_layout::truncate_wrap() source.start = truncate_char.source.start + truncate_char.source.span; source.span = 0; - // figure out how wide an elipsis is + // figure out how wide an ellipsis is float elipsis_width = get_char_width(elipsis, style.size); // where should we really truncate from? @@ -347,10 +513,10 @@ void text_layout::truncate_wrap() // truncate!!! m_current_line->truncate(truncate_position); - // and append the elipsis - m_current_line->add_character(elipsis, style, source); + // and append the ellipsis + m_current_line->add_character(*this, elipsis, style, source); - // take note that we are truncating; supress new characters + // take note that we are truncating; suppress new characters m_truncating = true; } @@ -362,26 +528,37 @@ void text_layout::truncate_wrap() void text_layout::word_wrap() { // keep track of the last line and break - line *last_line = m_current_line; - size_t last_break = m_last_break; + line *const last_line = m_current_line; + size_t const last_break = m_last_break ? m_last_break : (last_line->character_count() - 1); // start a new line with the same justification - start_new_line(last_line->justify(), last_line->character(last_line->character_count() - 1).style.size); + start_new_line(last_line->character(last_line->character_count() - 1).style.size); - // find the begining of the word to wrap + // find the beginning of the word to wrap size_t position = last_break; - while (position + 1 < last_line->character_count() && is_space_character(last_line->character(position).character)) + while ((last_line->character_count() > position) && is_space_character(last_line->character(position).character)) position++; + // carry over justification + if (last_line->right_justify_start() <= position) + m_current_line->set_justification(text_justify::RIGHT); + else if (last_line->center_justify_start() <= position) + m_current_line->set_justification(text_justify::CENTER); + // transcribe the characters for (size_t i = position; i < last_line->character_count(); i++) { + if (last_line->right_justify_start() == i) + m_current_line->set_justification(text_justify::RIGHT); + else if (last_line->center_justify_start() == i) + m_current_line->set_justification(text_justify::CENTER); auto &ch = last_line->character(i); - m_current_line->add_character(ch.character, ch.style, ch.source); + m_current_line->add_character(*this, ch.character, ch.style, ch.source); } - // and finally, truncate the last line + // and finally, truncate the previous line and adjust spacing last_line->truncate(last_break); + last_line->align_text(*this); } @@ -389,13 +566,20 @@ void text_layout::word_wrap() // hit_test //------------------------------------------------- -bool text_layout::hit_test(float x, float y, size_t &start, size_t &span) const +bool text_layout::hit_test(float x, float y, size_t &start, size_t &span) { + if (m_current_line) + { + // TODO: is there a sane way to allow an open line to be temporarily finalised and rolled back? + m_current_line->align_text(*this); + m_current_line = nullptr; + } + for (const auto &line : m_lines) { if (y >= line->yoffset() && y < line->yoffset() + line->height()) { - float line_xoffset = line->xoffset(); + float line_xoffset = line->xoffset(*this); if (x >= line_xoffset && x < line_xoffset + line->width()) { for (size_t i = 0; i < line->character_count(); i++) @@ -421,18 +605,22 @@ bool text_layout::hit_test(float x, float y, size_t &start, size_t &span) const // restyle //------------------------------------------------- -void text_layout::restyle(size_t start, size_t span, rgb_t *fgcolor, rgb_t *bgcolor) +void text_layout::restyle(size_t start, size_t span, rgb_t const *fgcolor, rgb_t const *bgcolor) { for (const auto &line : m_lines) { for (size_t i = 0; i < line->character_count(); i++) { auto &ch = line->character(i); - if (ch.source.start >= start && ch.source.start + ch.source.span <= start + span) + if ((ch.source.start + ch.source.span) > (start + span)) + { + return; + } + else if (ch.source.start >= start) { - if (fgcolor != nullptr) + if (fgcolor) ch.style.fgcolor = *fgcolor; - if (bgcolor != nullptr) + if (bgcolor) ch.style.bgcolor = *bgcolor; } } @@ -441,56 +629,39 @@ void text_layout::restyle(size_t start, size_t span, rgb_t *fgcolor, rgb_t *bgco //------------------------------------------------- -// get_wrap_info +// emit //------------------------------------------------- -int text_layout::get_wrap_info(std::vector<int> &xstart, std::vector<int> &xend) const +void text_layout::emit(render_container &container, float x, float y) { - // this is a hacky method (tailored to the need to implement - // mame_ui_manager::wrap_text) but so be it - int line_count = 0; - for (const auto &line : m_lines) - { - int start_pos = 0; - int end_pos = 0; - - auto line_character_count = line->character_count(); - if (line_character_count > 0) - { - start_pos = line->character(0).source.start; - end_pos = line->character(line_character_count - 1).source.start - + line->character(line_character_count - 1).source.span; - } - - line_count++; - xstart.push_back(start_pos); - xend.push_back(end_pos); - } - return line_count; + emit(container, 0, m_lines.size(), x, y); } - -//------------------------------------------------- -// emit -//------------------------------------------------- - -void text_layout::emit(render_container &container, float x, float y) +void text_layout::emit(render_container &container, size_t start, size_t lines, float x, float y) { - for (const auto &line : m_lines) + if (m_current_line) + { + // TODO: is there a sane way to allow an open line to be temporarily finalised and rolled back? + m_current_line->align_text(*this); + m_current_line = nullptr; + } + + float const base_y = (m_lines.size() > start) ? m_lines[start]->yoffset() : 0.0f; + for (size_t l = start; ((start + lines) > l) && (m_lines.size() > l); ++l) { - float line_xoffset = line->xoffset(); + auto const &line = m_lines[l]; + float const line_xoffset = line->xoffset(*this); + float const char_y = y + line->yoffset() - base_y; + float const char_height = line->height(); // emit every single character for (auto i = 0; i < line->character_count(); i++) { auto &ch = line->character(i); - // position this specific character correctly (TODO - this doesn't - // handle differently sized text (yet) - float char_x = x + line_xoffset + ch.xoffset; - float char_y = y + line->yoffset(); - float char_width = ch.xwidth; - float char_height = line->height(); + // position this specific character correctly (TODO - this doesn't handle differently sized text (yet) + float const char_x = x + line_xoffset + ch.xoffset; + float const char_width = ch.xwidth; // render the background of the character (if present) if (ch.style.bgcolor.a() != 0) @@ -498,95 +669,15 @@ void text_layout::emit(render_container &container, float x, float y) // render the foreground container.add_char( - char_x, - char_y, - char_height, - xscale() / yscale(), - ch.style.fgcolor, - font(), - ch.character); + char_x, + char_y, + char_height, + xscale() / yscale(), + ch.style.fgcolor, + font(), + ch.character); } } } - -//------------------------------------------------- -// line::ctor -//------------------------------------------------- - -text_layout::line::line(text_layout &layout, text_justify justify, float yoffset, float height) - : m_layout(layout), m_justify(justify), m_yoffset(yoffset), m_width(0.0), m_height(height) -{ -} - - -//------------------------------------------------- -// line::add_character -//------------------------------------------------- - -void text_layout::line::add_character(char32_t ch, const char_style &style, const source_info &source) -{ - // get the width of this character - float chwidth = m_layout.get_char_width(ch, style.size); - - // create the positioned character - positioned_char positioned_char = { 0, }; - positioned_char.character = ch; - positioned_char.xoffset = m_width; - positioned_char.xwidth = chwidth; - positioned_char.style = style; - positioned_char.source = source; - - // append the character - m_characters.push_back(positioned_char); - m_width += chwidth; - - // we might be bigger - m_height = std::max(m_height, style.size * m_layout.yscale()); -} - - -//------------------------------------------------- -// line::xoffset -//------------------------------------------------- - -float text_layout::line::xoffset() const -{ - float result; - switch (justify()) - { - case LEFT: - default: - result = 0; - break; - case CENTER: - result = (m_layout.width() - width()) / 2; - break; - case RIGHT: - result = m_layout.width() - width(); - break; - } - return result; -} - - -//------------------------------------------------- -// line::truncate -//------------------------------------------------- - -void text_layout::line::truncate(size_t position) -{ - assert(position <= m_characters.size()); - - // are we actually truncating? - if (position < m_characters.size()) - { - // set the width as appropriate - m_width = m_characters[position].xoffset; - - // and resize the array - m_characters.resize(position); - } -} - } // namespace ui diff --git a/src/frontend/mame/ui/text.h b/src/frontend/mame/ui/text.h index 5852bbeb184..a9169faa186 100644 --- a/src/frontend/mame/ui/text.h +++ b/src/frontend/mame/ui/text.h @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:Nicola Salmoria, Aaron Giles, Nathan Woods +// copyright-holders:Nathan Woods, Vas Crabb /*************************************************************************** text.h @@ -7,19 +7,22 @@ Text functionality for MAME's crude user interface ***************************************************************************/ +#ifndef MAME_FRONTEND_UI_TEXT_H +#define MAME_FRONTEND_UI_TEXT_H #pragma once -#ifndef MAME_FRONTEND_UI_TEXT_H -#define MAME_FRONTEND_UI_TEXT_H +#include <memory> +#include <string_view> +#include <vector> -#include "palette.h" -#include "unicode.h" class render_font; class render_container; + namespace ui { + /*************************************************************************** TYPE DEFINITIONS ***************************************************************************/ @@ -28,7 +31,7 @@ class text_layout { public: // justification options for text - enum text_justify + enum class text_justify { LEFT = 0, CENTER, @@ -36,7 +39,7 @@ public: }; // word wrapping options - enum word_wrapping + enum class word_wrapping { NEVER, TRUNCATE, @@ -57,24 +60,22 @@ public: word_wrapping wrap() const { return m_wrap; } // methods - float actual_left() const; - float actual_width() const; - float actual_height() const; - bool empty() const { return m_lines.size() == 0; } - bool hit_test(float x, float y, size_t &start, size_t &span) const; - void restyle(size_t start, size_t span, rgb_t *fgcolor, rgb_t *bgcolor); - int get_wrap_info(std::vector<int> &xstart, std::vector<int> &xend) const; + float actual_left(); + float actual_width(); + float actual_height(); + bool empty() const { return m_lines.empty(); } + size_t lines() const { return m_lines.size(); } + bool hit_test(float x, float y, size_t &start, size_t &span); + void restyle(size_t start, size_t span, rgb_t const *fgcolor, rgb_t const *bgcolor); void emit(render_container &container, float x, float y); - void add_text(const char *text, rgb_t fgcolor = rgb_t::white(), rgb_t bgcolor = rgb_t::transparent(), float size = 1.0) + void emit(render_container &container, size_t start, size_t lines, float x, float y); + void add_text(std::string_view text, rgb_t fgcolor = rgb_t::white(), rgb_t bgcolor = rgb_t::transparent(), float size = 1.0) + { + add_text(text, justify(), char_style{ fgcolor, bgcolor, size }); + } + void add_text(std::string_view text, text_justify line_justify, rgb_t fgcolor = rgb_t::white(), rgb_t bgcolor = rgb_t::transparent(), float size = 1.0) { - // create the style - char_style style = { 0, }; - style.fgcolor = fgcolor; - style.bgcolor = bgcolor; - style.size = size; - - // and add the text - add_text(text, style); + add_text(text, line_justify, char_style{ fgcolor, bgcolor, size }); } private: @@ -86,53 +87,10 @@ private: float size; }; - // information about the "source" of a character - also in a struct - // to facilitate copying - struct source_info - { - size_t start; - size_t span; - }; - - // this should really be "positioned glyph" as glyphs != characters, but - // we'll get there eventually - struct positioned_char - { - char32_t character; - char_style style; - source_info source; - float xoffset; - float xwidth; - }; - // class to represent a line - class line - { - public: - line(text_layout &layout, text_justify justify, float yoffset, float height); - - // methods - void add_character(char32_t ch, const char_style &style, const source_info &source); - void truncate(size_t position); - - // accessors - float xoffset() const; - float yoffset() const { return m_yoffset; } - float width() const { return m_width; } - float height() const { return m_height; } - text_justify justify() const { return m_justify; } - size_t character_count() const { return m_characters.size(); } - const positioned_char &character(size_t index) const { return m_characters[index]; } - positioned_char &character(size_t index) { return m_characters[index]; } - - private: - std::vector<positioned_char> m_characters; - text_layout &m_layout; - text_justify m_justify; - float m_yoffset; - float m_width; - float m_height; - }; + struct source_info; + struct positioned_char; + class line; // instance variables render_font &m_font; @@ -142,15 +100,15 @@ private: mutable float m_calculated_actual_width; text_justify m_justify; word_wrapping m_wrap; - std::vector<std::unique_ptr<line>> m_lines; + std::vector<std::unique_ptr<line> > m_lines; line *m_current_line; size_t m_last_break; size_t m_text_position; bool m_truncating; // methods - void add_text(const char *text, const char_style &style); - void start_new_line(text_justify justify, float height); + void add_text(std::string_view text, text_justify line_justify, char_style const &style); + void start_new_line(float height); float get_char_width(char32_t ch, float size); void truncate_wrap(); void word_wrap(); @@ -159,4 +117,4 @@ private: } // namespace ui -#endif // MAME_FRONTEND_UI_TEXT_H +#endif // MAME_FRONTEND_UI_TEXT_H diff --git a/src/frontend/mame/ui/textbox.cpp b/src/frontend/mame/ui/textbox.cpp new file mode 100644 index 00000000000..223af5e67cf --- /dev/null +++ b/src/frontend/mame/ui/textbox.cpp @@ -0,0 +1,553 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + ui/textbox.cpp + + Menu that displays a non-interactive text box + +***************************************************************************/ + +#include "emu.h" +#include "textbox.h" + +#include "ui/ui.h" + +#include "uiinput.h" + +#include <string_view> +#include <utility> + + +namespace ui { + +namespace { + +inline std::string_view split_column(std::string_view &line) +{ + auto const split = line.find('\t'); + if (std::string::npos == split) + { + return std::exchange(line, std::string_view()); + } + else + { + std::string_view result = line.substr(0, split); + line.remove_prefix(split + 1); + return result; + } +} + + +template <typename T, typename U, typename V> +void populate_three_column_layout(std::string_view text, T &&l, U &&c, V &&r) +{ + while (!text.empty()) + { + // pop a line from the front + auto const eol = text.find('\n'); + std::string_view line = (std::string_view::npos != eol) + ? text.substr(0, eol + 1) + : text; + text.remove_prefix(line.length()); + + // left-justify up to the first tab + std::string_view const lcol = split_column(line); + if (!lcol.empty()) + l(lcol); + + // centre up to the second tab + if (!line.empty()) + { + std::string_view const ccol = split_column(line); + if (!ccol.empty()) + c(ccol); + } + + // right-justify the rest + if (!line.empty()) + r(line); + } +} + +} // anonymous namespace + + + +//------------------------------------------------- +// menu_textbox - base text box menu class +//------------------------------------------------- + +menu_textbox::menu_textbox(mame_ui_manager &mui, render_container &container) + : menu(mui, container) + , m_layout() + , m_line_bounds(0.0F, 0.0F) + , m_visible_top(0.0F) + , m_layout_width(-1.0F) + , m_desired_width(-1.0F) + , m_desired_lines(-1) + , m_window_lines(0) + , m_top_line(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_scroll_base(0) +{ +} + + +menu_textbox::~menu_textbox() +{ +} + + +void menu_textbox::reset_layout() +{ + // force recompute and scroll to top + m_layout.reset(); + m_top_line = 0; + m_pointer_action = pointer_action::NONE; +} + + +void menu_textbox::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + menu::recompute_metrics(width, height, aspect); + + m_layout.reset(); + m_pointer_action = pointer_action::NONE; +} + + +std::tuple<int, bool, bool> menu_textbox::custom_pointer_updated(bool changed, ui_event const &uievt) +{ + // no pointer input if we don't have up-to-date content on-screen + if (!m_layout || (ui_event::type::POINTER_ABORT == uievt.event_type)) + { + m_pointer_action = pointer_action::NONE; + return std::make_tuple(IPT_INVALID, false, false); + } + + // if nothing's happening, check for clicks + if (pointer_idle()) + { + if ((uievt.pointer_pressed & 0x01) && !(uievt.pointer_buttons & ~u32(0x01))) + { + auto const [x, y] = pointer_location(); + if ((x >= m_line_bounds.first) && (x < m_line_bounds.second)) + { + if (m_top_line && pointer_in_line(y, 0)) + { + // scroll up arrow + --m_top_line; + m_pointer_action = pointer_action::SCROLL_UP; + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(300); + m_last_pointer = std::make_pair(x, y); + return std::make_tuple(IPT_INVALID, true, true); + } + else if (((m_top_line + m_window_lines) < m_layout->lines()) && pointer_in_line(y, m_window_lines - 1)) + { + // scroll down arrow + ++m_top_line; + m_pointer_action = pointer_action::SCROLL_DOWN; + m_scroll_repeat = std::chrono::steady_clock::now() + std::chrono::milliseconds(300); + m_last_pointer = std::make_pair(x, y); + return std::make_tuple(IPT_INVALID, true, true); + } + else if ((2 == uievt.pointer_clicks) && pointer_in_line(y, m_window_lines + 1)) + { + // return to previous menu item + // FIXME: this should really use the start point of the multi-click action + m_pointer_action = pointer_action::CHECK_EXIT; + return std::make_tuple(IPT_INVALID, true, false); + } + else if ((ui_event::pointer::TOUCH == uievt.pointer_type) && (y >= m_visible_top) && (y < (m_visible_top + (float(m_window_lines) * line_height())))) + { + m_pointer_action = pointer_action::SCROLL_DRAG; + m_base_pointer = std::make_pair(x, y); + m_last_pointer = m_base_pointer; + m_scroll_base = m_top_line; + return std::make_tuple(IPT_INVALID, true, false); + } + } + } + return std::make_tuple(IPT_INVALID, false, false); + } + + // handle in-progress actions + switch (m_pointer_action) + { + case pointer_action::NONE: + break; + + case pointer_action::SCROLL_UP: + case pointer_action::SCROLL_DOWN: + { + // check for re-entry + bool redraw(false); + float const linetop(m_visible_top + ((pointer_action::SCROLL_DOWN == m_pointer_action) ? (float(m_window_lines - 1) * line_height()) : 0.0F)); + float const linebottom(linetop + line_height()); + auto const [x, y] = pointer_location(); + bool const reentered(reentered_rect(m_last_pointer.first, m_last_pointer.second, x, y, m_line_bounds.first, linetop, m_line_bounds.second, linebottom)); + if (reentered) + { + auto const now(std::chrono::steady_clock::now()); + if (scroll_if_expired(now)) + { + redraw = true; + m_scroll_repeat = now + std::chrono::milliseconds(100); + } + } + m_last_pointer = std::make_pair(x, y); + 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); + } + + case pointer_action::SCROLL_DRAG: + { + // scroll if it moved + auto const newtop(drag_scroll( + pointer_location().second, m_base_pointer.second, m_last_pointer.second, -line_height(), + m_scroll_base, 0, int(m_layout->lines() - m_window_lines))); + bool const scrolled(newtop != m_top_line); + m_top_line = newtop; + + // catch the end of the gesture + 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, scrolled); + } + + case pointer_action::CHECK_EXIT: + if (uievt.pointer_released & 0x01) + return std::make_tuple((2 == uievt.pointer_clicks) ? IPT_UI_SELECT : IPT_INVALID, false, false); + else if (uievt.pointer_buttons & ~u32(0x01)) + return std::make_tuple(IPT_INVALID, false, false); + return std::make_tuple(IPT_INVALID, true, false); + } + return std::make_tuple(IPT_INVALID, false, false); +} + + +bool menu_textbox::custom_mouse_scroll(int lines) +{ + m_top_line += lines; + return true; +} + + +bool menu_textbox::handle(event const *ev) +{ + // deal with repeating scroll arrows + bool scrolled(false); + if ((pointer_action::SCROLL_UP == m_pointer_action) || (pointer_action::SCROLL_DOWN == m_pointer_action)) + { + float const linetop(m_visible_top + ((pointer_action::SCROLL_DOWN == m_pointer_action) ? (float(m_window_lines - 1) * line_height()) : 0.0F)); + float const linebottom(linetop + line_height()); + if (pointer_in_rect(m_line_bounds.first, linetop, m_line_bounds.second, linebottom)) + { + while (scroll_if_expired(std::chrono::steady_clock::now())) + { + scrolled = true; + m_scroll_repeat += std::chrono::milliseconds(100); + } + } + } + + if (ev) + { + switch (ev->iptkey) + { + case IPT_UI_SELECT: + stack_pop(); + return true; + + case IPT_UI_UP: + --m_top_line; + return true; + + case IPT_UI_DOWN: + ++m_top_line; + return true; + + case IPT_UI_PAGE_UP: + m_top_line -= m_window_lines - 3; + return true; + + case IPT_UI_PAGE_DOWN: + m_top_line += m_window_lines - 3; + return true; + + case IPT_UI_HOME: + m_top_line = 0; + return true; + + case IPT_UI_END: + m_top_line = m_layout->lines() - m_window_lines; + return true; + } + } + + return scrolled; +} + + +void menu_textbox::draw(uint32_t flags) +{ + float const visible_width = 1.0F - (2.0F * lr_border()); + float const visible_left = (1.0F - visible_width) * 0.5F; + float const extra_height = 2.0F * line_height(); + float const visible_extra_menu_height = get_customtop() + get_custombottom() + extra_height; + + // determine effective positions + float const maximum_width = visible_width - (2.0F * gutter_width()); + + draw_background(); + + // account for extra space at the top and bottom and the separator/item for closing + float visible_main_menu_height = 1.0F - (2.0F * tb_border()) - visible_extra_menu_height; + m_window_lines = int(std::trunc(visible_main_menu_height / line_height())); + + // lay out the text if necessary + if (!m_layout || (m_layout_width != maximum_width)) + { + m_desired_width = maximum_width; + populate_text(m_layout, m_desired_width, m_desired_lines); + m_layout_width = maximum_width; + } + m_window_lines = (std::min)(m_desired_lines, m_window_lines); + visible_main_menu_height = float(m_window_lines) * line_height(); + + // compute top/left of inner menu area by centering, if the menu is at the bottom of the extra, adjust + m_visible_top = ((1.0F - (visible_main_menu_height + visible_extra_menu_height)) * 0.5F) + get_customtop(); + + // get width required to draw the sole menu item + menu_item const &pitem = item(0); + std::string_view const itemtext = pitem.text(); + float const itemwidth = gutter_width() + get_string_width(itemtext) + gutter_width(); + float const draw_width = std::min(maximum_width, std::max(itemwidth, m_desired_width)); + + // compute text box size + float const x1 = visible_left + ((maximum_width - draw_width) * 0.5F); + float const y1 = m_visible_top - tb_border(); + float const x2 = visible_left + visible_width - ((maximum_width - draw_width) * 0.5F); + float const y2 = m_visible_top + visible_main_menu_height + tb_border() + extra_height; + float const effective_left = x1 + gutter_width(); + m_line_bounds = std::make_pair(x1 + (0.5F * UI_LINE_WIDTH), x2 - (0.5F * UI_LINE_WIDTH)); + float const separator = m_visible_top + float(m_window_lines) * line_height(); + + ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color()); + + int const desired_lines = m_layout->lines(); + int const drawn_lines = (std::min)(desired_lines, m_window_lines); + m_top_line = (std::max)(0, m_top_line); + if ((m_top_line + drawn_lines) >= desired_lines) + m_top_line = desired_lines - drawn_lines; + + if (m_top_line) + { + // if we're not showing the top line, display the up arrow + rgb_t fgcolor(ui().colors().text_color()); + bool const hovered(pointer_in_rect(m_line_bounds.first, m_visible_top, m_line_bounds.second, m_visible_top + line_height())); + if (hovered && (pointer_action::SCROLL_UP == m_pointer_action)) + { + fgcolor = ui().colors().selected_color(); + highlight( + m_line_bounds.first, m_visible_top, + m_line_bounds.second, m_visible_top + line_height(), + ui().colors().selected_bg_color()); + } + else if ((hovered && pointer_idle()) || (pointer_action::SCROLL_UP == m_pointer_action)) + { + fgcolor = ui().colors().mouseover_color(); + highlight( + m_line_bounds.first, m_visible_top, + m_line_bounds.second, m_visible_top + line_height(), + ui().colors().mouseover_bg_color()); + } + draw_arrow( + 0.5F * (x1 + x2 - ud_arrow_width()), m_visible_top + (0.25F * line_height()), + 0.5F * (x1 + x2 + ud_arrow_width()), m_visible_top + (0.75F * line_height()), + fgcolor, ROT0); + } + if ((m_top_line + m_window_lines) < desired_lines) + { + // if we're not showing the bottom line, display the down arrow + float const line_y(m_visible_top + float(m_window_lines - 1) * line_height()); + rgb_t fgcolor(ui().colors().text_color()); + bool const hovered(pointer_in_rect(m_line_bounds.first, line_y, m_line_bounds.second, line_y + line_height())); + if (hovered && (pointer_action::SCROLL_DOWN == m_pointer_action)) + { + fgcolor = ui().colors().selected_color(); + highlight( + m_line_bounds.first, line_y, + m_line_bounds.second, line_y + line_height(), + ui().colors().selected_bg_color()); + } + else if ((hovered && pointer_idle()) || (pointer_action::SCROLL_DOWN == m_pointer_action)) + { + fgcolor = ui().colors().mouseover_color(); + highlight( + m_line_bounds.first, line_y, + m_line_bounds.second, line_y + line_height(), + ui().colors().mouseover_bg_color()); + } + draw_arrow( + 0.5F * (x1 + x2 - ud_arrow_width()), line_y + (0.25F * line_height()), + 0.5F * (x1 + x2 + ud_arrow_width()), line_y + (0.75F * line_height()), + fgcolor, ROT0 ^ ORIENTATION_FLIP_Y); + } + + // draw visible lines, minus 1 for top arrow and 1 for bottom arrow + auto const text_lines = drawn_lines - (m_top_line ? 1 : 0) - ((m_top_line + drawn_lines) != desired_lines); + m_layout->emit( + container(), + m_top_line ? (m_top_line + 1) : 0, text_lines, + effective_left, m_visible_top + (m_top_line ? line_height() : 0.0F)); + + // add visual separator before the "return to prevous menu" item + container().add_line( + x1, separator + (0.5F * line_height()), + x2, separator + (0.5F * line_height()), + UI_LINE_WIDTH, ui().colors().text_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + + float const line_y0 = m_visible_top + float(m_window_lines + 1) * line_height(); + float const line_y1 = line_y0 + line_height(); + + highlight(m_line_bounds.first, line_y0, m_line_bounds.second, line_y1, ui().colors().selected_bg_color()); + ui().draw_text_full( + container(), itemtext, + effective_left, line_y0, draw_width, + text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, + mame_ui_manager::NORMAL, + ui().colors().selected_color(), ui().colors().selected_bg_color(), + nullptr, nullptr, + line_height()); + + // if there is something special to add, do it by calling the virtual method + custom_render(flags, get_selection_ref(), get_customtop(), get_custombottom(), x1, y1, x2, y2); +} + + +bool menu_textbox::scroll_if_expired(std::chrono::steady_clock::time_point now) +{ + if (now < m_scroll_repeat) + return false; + + if (pointer_action::SCROLL_DOWN == m_pointer_action) + { + if ((m_top_line + m_window_lines) < m_layout->lines()) + ++m_top_line; + if ((m_top_line + m_window_lines) == m_layout->lines()) + m_pointer_action = pointer_action::NONE; + } + else + { + if (0 < m_top_line) + --m_top_line; + if (!m_top_line) + m_pointer_action = pointer_action::NONE; + } + return true; +} + + +inline bool menu_textbox::pointer_in_line(float y, int line) const +{ + float const top(m_visible_top + (float(line) * line_height())); + return (top <= y) && ((top + line_height()) > y); +} + + + +//------------------------------------------------- +// menu_fixed_textbox - text box with three- +// column content supplied at construction +//------------------------------------------------- + +menu_fixed_textbox::menu_fixed_textbox( + mame_ui_manager &mui, + render_container &container, + std::string &&heading, + std::string &&content) + : menu_textbox(mui, container) + , m_heading(std::move(heading)) + , m_content(std::move(content)) +{ +} + + +menu_fixed_textbox::~menu_fixed_textbox() +{ +} + + +void menu_fixed_textbox::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + menu_textbox::recompute_metrics(width, height, aspect); + + set_custom_space(line_height() + 3.0F * tb_border(), 0.0F); +} + + +void menu_fixed_textbox::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +{ + std::string_view const toptext[] = { m_heading }; + draw_text_box( + std::begin(toptext), std::end(toptext), + origx1, origx2, origy1 - top, origy1 - tb_border(), + text_layout::text_justify::CENTER, text_layout::word_wrapping::NEVER, false, + ui().colors().text_color(), UI_GREEN_COLOR); +} + + +void menu_fixed_textbox::populate_text( + std::optional<text_layout> &layout, + float &width, + int &lines) +{ + // ugly - use temporary layouts to compute required width + { + text_layout l(create_layout(width)); + text_layout c(create_layout(width)); + text_layout r(create_layout(width)); + populate_three_column_layout( + m_content, + [&l] (std::string_view s) + { + l.add_text(s, text_layout::text_justify::LEFT); + if (s.back() != '\n') + l.add_text("\n", text_layout::text_justify::LEFT); + }, + [&c] (std::string_view s) { + c.add_text(s, text_layout::text_justify::LEFT); + if (s.back() != '\n') + c.add_text("\n", text_layout::text_justify::LEFT); + }, + [&r] (std::string_view s) { + r.add_text(s, text_layout::text_justify::LEFT); + if (s.back() != '\n') + r.add_text("\n", text_layout::text_justify::LEFT); + }); + width = (std::min)(l.actual_width() + c.actual_width() + r.actual_width(), width); + } + + // now do it for real + layout.emplace(create_layout(width)); + rgb_t const color = ui().colors().text_color(); + populate_three_column_layout( + m_content, + [&layout, color] (std::string_view s) { layout->add_text(s, text_layout::text_justify::LEFT, color); }, + [&layout, color] (std::string_view s) { layout->add_text(s, text_layout::text_justify::CENTER, color); }, + [&layout, color] (std::string_view s) { layout->add_text(s, text_layout::text_justify::RIGHT, color); }); + lines = layout->lines(); +} + + +void menu_fixed_textbox::populate() +{ +} + +} // namespace ui diff --git a/src/frontend/mame/ui/textbox.h b/src/frontend/mame/ui/textbox.h new file mode 100644 index 00000000000..ff8c7a62930 --- /dev/null +++ b/src/frontend/mame/ui/textbox.h @@ -0,0 +1,100 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + ui/textbox.h + + Menu that displays a non-interactive text box + +***************************************************************************/ +#ifndef MAME_FRONTEND_UI_TEXTBOX_H +#define MAME_FRONTEND_UI_TEXTBOX_H + +#include "ui/menu.h" +#include "ui/text.h" + +#include <chrono> +#include <optional> +#include <string> +#include <tuple> +#include <utility> + + +namespace ui { + +class menu_textbox : public menu +{ +public: + virtual ~menu_textbox() override; + +protected: + menu_textbox(mame_ui_manager &mui, render_container &container); + + void reset_layout(); + + virtual void populate_text(std::optional<text_layout> &layout, float &width, int &lines) = 0; + + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual std::tuple<int, bool, bool> custom_pointer_updated(bool changed, ui_event const &uievt) override; + virtual bool custom_mouse_scroll(int lines) override; + + virtual bool handle(event const *ev) override; + +private: + enum class pointer_action + { + NONE, + SCROLL_UP, + SCROLL_DOWN, + SCROLL_DRAG, + CHECK_EXIT + }; + + virtual void draw(uint32_t flags) override; + + bool scroll_if_expired(std::chrono::steady_clock::time_point now); + bool pointer_in_line(float y, int line) const; + + std::optional<text_layout> m_layout; + std::pair<float, float> m_line_bounds; + float m_visible_top; + float m_layout_width; + float m_desired_width; + int m_desired_lines; + int m_window_lines; + int m_top_line; + + pointer_action m_pointer_action; + std::chrono::steady_clock::time_point m_scroll_repeat; + std::pair<float, float> m_base_pointer; + std::pair<float, float> m_last_pointer; + int m_scroll_base; +}; + + +class menu_fixed_textbox : public menu_textbox +{ +public: + menu_fixed_textbox( + mame_ui_manager &mui, + render_container &container, + std::string &&headig, + std::string &&content); + virtual ~menu_fixed_textbox() override; + +protected: + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) override; + + virtual void populate_text(std::optional<text_layout> &layout, float &width, int &lines) override; + +private: + virtual void populate() override; + + std::string const m_heading; + std::string const m_content; +}; + +} // namespace ui + +#endif // MAME_FRONTEND_UI_TEXTBOX_H diff --git a/src/frontend/mame/ui/toolbar.ipp b/src/frontend/mame/ui/toolbar.ipp index 933bf9c2d40..daecebddd12 100644 --- a/src/frontend/mame/ui/toolbar.ipp +++ b/src/frontend/mame/ui/toolbar.ipp @@ -1,121 +1,75 @@ // license:BSD-3-Clause -// copyright-holders:Dankan1890 +// copyright-holders:Vas Crabb #ifndef MAME_FRONTEND_UI_TOOLBAR_IPP #define MAME_FRONTEND_UI_TOOLBAR_IPP #pragma once namespace ui { + namespace { -// TODO: move this to an external image file and zlib compress it into a souce file as part of the build process -uint32_t const toolbar_bitmap_bmp[][1024] = { - { - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x02D07A00, 0x15D07A00, 0x0FD07A00, 0x00D07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x76D27F04, 0xBFDA9714, 0xB9D78F0E, 0x4DD17B01, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3BD07A00, 0xFFE8B228, 0xFFFDEB50, 0xFFFBE34A, 0xD0E1A11C, 0x13D07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x0BD07A00, 0xA0D48306, 0xFFFACE42, 0xFFFBCE45, 0xFFFCD146, 0xFFF2BD34, 0x67D07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x49D27E03, 0xE9EAAB26, 0xFFFDD044, 0xFFF9C741, 0xFFFAC942, 0xFFFED245, 0xD1DF9716, 0x27D07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xA2DB8D0F, 0xFFF6C236, 0xFFFAC740, 0xFFF8C53F, 0xFFF8C53F, 0xFFFDCB41, 0xF7F0B62E, 0x71D68308, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x31D07A00, 0xFFE7A420, 0xFFFDCA3F, 0xFFF8C23D, 0xFFF8C23D, 0xFFF8C23D, 0xFFF8C23D, 0xFFFCC83D, 0xE0E19818, 0x11D07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x08D07A00, 0x99D38004, 0xFFF9C237, 0xFFFAC43C, 0xFFF8BF3A, 0xFFF8BF3A, 0xFFF8BF3A, 0xFFF8BF3A, 0xFFFBC53C, 0xFFF1B32B, 0x63D07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x01D07A00, 0x15D07A00, 0x24D07A00, 0x39D07A00, 0x4AD07A00, 0x79D48205, 0xE6E9A820, 0xFFFDC539, 0xFFF8BC37, 0xFFF8BC37, 0xFFF8BC37, 0xFFF8BC37, 0xFFF8BC37, 0xFFF9BD37, 0xFFFEC63A, 0xD8DF9613, 0x64D17C01, 0x3FD07A00, 0x2FD07A00, 0x1CD07A00, 0x0AD07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x04D07A00, 0x3BD07A00, 0x8BD07A00, 0xA5D17B01, 0xBFDA940F, 0xCEE1A317, 0xE2E7B622, 0xF4EDC229, 0xFFF1C62D, 0xFFFAC735, 0xFFFABC35, 0xFFF8B934, 0xFFF8B934, 0xFFF8B934, 0xFFF8B934, 0xFFF8B934, 0xFFF8B934, 0xFFFCBF36, 0xFFF7C733, 0xFCEFC52C, 0xE9EABB24, 0xD8E4AE1D, 0xC6DD9C13, 0xB4D58608, 0x99D07A00, 0x75D07A00, 0x20D07A00, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x01D07A00, 0xBBD78608, 0xFFE9AE1F, 0xFFF9D133, 0xFFFCD839, 0xFFFCD338, 0xFFFCCC36, 0xFFFCC333, 0xFFFCBB32, 0xFFF7B630, 0xFFF7B630, 0xFFF7B630, 0xFFF7B630, 0xFFF7B630, 0xFFF7B630, 0xFFF7B630, 0xFFF7B630, 0xFFF7B630, 0xFFF7B630, 0xFFF7B630, 0xFFFAB831, 0xFFFCC033, 0xFFFCC735, 0xFFFCD037, 0xFFFCD739, 0xFFFBD536, 0xFFF5C92F, 0xE8E4A318, 0x55D78507, 0x00000000, 0x00000000, - 0x00000000, 0x13D07A00, 0xFFDF9212, 0xFFFABC2F, 0xFFF9B72F, 0xFFF8B32E, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B32D, 0xFFF9B52E, 0xFFF9B92F, 0xFFF6B52A, 0xC1DB8B0D, 0x00000000, 0x00000000, - 0x00000000, 0x07D07A00, 0xE6DC8B0E, 0xFFF4AB27, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFEFA421, 0xAAD9860A, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x5ED58005, 0xE8E39213, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF4A925, 0xE2DC890C, 0x45D27C02, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x41D07A00, 0xE7E18F11, 0xFFF3A420, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFEFA11D, 0xE0DB880A, 0x35D07A00, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x5DD47E03, 0xE6E08D0D, 0xFFF5A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFF3A11D, 0xDFDB8609, 0x4FD27C01, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x40D07A00, 0xE6E08A0C, 0xFFF29D19, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFF6A01C, 0xFFEE9917, 0xDDDA8407, 0x30D07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x5BD37D02, 0xE6DF880A, 0xFFF59C18, 0xFFF69D18, 0xFFF69D18, 0xFFF69D18, 0xFFF69D18, 0xFFF69D18, 0xFFF69D18, 0xFFF69D18, 0xFFF69D18, 0xFFF69D18, 0xFFF69D18, 0xFFF69D18, 0xFFF69D18, 0xFFF69D18, 0xFFF69D18, 0xFFF29A16, 0xDCD98306, 0x49D17B01, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x7BD07A00, 0xFFEF9311, 0xFFF69A15, 0xFFF59915, 0xFFF59915, 0xFFF59915, 0xFFF59915, 0xFFF59915, 0xFFF59915, 0xFFF59915, 0xFFF59915, 0xFFF59915, 0xFFF59915, 0xFFF59915, 0xFFF59915, 0xFFF69915, 0xFFE2890A, 0x3BD07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x01D07A00, 0xA2D17B00, 0xFFF59612, 0xFFF69713, 0xFFF59612, 0xFFF59612, 0xFFF59612, 0xFFF59612, 0xFFF59612, 0xFFF59612, 0xFFF59612, 0xFFF59612, 0xFFF59612, 0xFFF59612, 0xFFF59612, 0xFFF59612, 0xFFF79712, 0xFFE98D0B, 0x4BD07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x14D07A00, 0xBED87F03, 0xFFF6940E, 0xFFF5930E, 0xFFF5930E, 0xFFF5930E, 0xFFF5930E, 0xFFF5930E, 0xFFF5930E, 0xFFF5930E, 0xFFF5930E, 0xFFF5930E, 0xFFF5930E, 0xFFF5930E, 0xFFF5930E, 0xFFF5930E, 0xFFF7940E, 0xFFF1900B, 0x7ED07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x27D07A00, 0xD1DE8205, 0xFFF8920C, 0xFFF5900C, 0xFFF5900C, 0xFFF5900C, 0xFFF5900C, 0xFFF5900C, 0xFFF5900C, 0xFFF5900C, 0xFFF5900C, 0xFFF5900C, 0xFFF5900C, 0xFFF5900C, 0xFFF5900C, 0xFFF5900C, 0xFFF6910C, 0xFFF5910C, 0xA5D27B01, 0x03D07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x40D07A00, 0xEAE48505, 0xFFFA9009, 0xFFF58D09, 0xFFF58D09, 0xFFF58D09, 0xFFF58D09, 0xFFF58D09, 0xFFF58D09, 0xFFF58D09, 0xFFF58D09, 0xFFF58D09, 0xFFF58D09, 0xFFF58D09, 0xFFF58D09, 0xFFF58D09, 0xFFF58D09, 0xFFF78E09, 0xC1D97F02, 0x17D07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x57D17B00, 0xFBE88504, 0xFFF78D06, 0xFFF48B06, 0xFFF48B06, 0xFFF48B06, 0xFFF48B06, 0xFFF48B06, 0xFFF38B06, 0xFFEC8705, 0xFFF18A06, 0xFFF48B06, 0xFFF48B06, 0xFFF48B06, 0xFFF48B06, 0xFFF48B06, 0xFFF48B06, 0xFFF88E06, 0xD6DF8102, 0x2CD07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x83D67D01, 0xFFED8503, 0xFFF48804, 0xFFF48804, 0xFFF48804, 0xFFF48804, 0xFFF28804, 0xFFEA8503, 0xCDDC7F02, 0x79D17B00, 0xA1D47C01, 0xEFE18102, 0xFFEE8604, 0xFFF38804, 0xFFF48804, 0xFFF48804, 0xFFF48804, 0xFFF88B04, 0xEFE58203, 0x46D07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xA0D87D01, 0xFFED8401, 0xFFF48602, 0xFFF48602, 0xFFF48602, 0xFFEF8501, 0xE9DE7F01, 0x8FD67D00, 0x23D07A00, 0x04D07A00, 0x0DD07A00, 0x46D07A00, 0xC3D97D01, 0xFFE28001, 0xFFF38602, 0xFFF48602, 0xFFF48602, 0xFFF58702, 0xFDE88201, 0x59D17A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x5FD47B00, 0xF3E58000, 0xFFF18400, 0xFFED8200, 0xDEE07F01, 0x90D37B00, 0x1FD07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x01D07A00, 0x3BD07A00, 0xBDD67C00, 0xF2E48000, 0xFFEF8300, 0xFFF08300, 0xDEDF7E01, 0x34D07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x10D07A00, 0x71D57C00, 0xD2DB7D00, 0x9AD87C00, 0x34D07A00, 0x0AD07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x13D07A00, 0x52D27B00, 0xBBD97D00, 0xCBDA7D00, 0x5DD27B00, 0x0AD07A00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 - }, - { - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x41D07A00, 0x8BD07A00, 0xAAD07A00, 0xAAD07A00, 0xAAC48715, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAACA810B, 0xAAD07A00, 0xA4D07A00, 0x7DD07A00, 0x1CD07A00, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x07D07A00, 0x7BD38206, 0xFFE8B82B, 0xFFF9E24B, 0xFFFEEE55, 0xFFFDEE55, 0xFFCBA95F, 0xFFEBEBEB, 0xFFF1F1F1, 0xFFF3F3F3, 0xFFF7F7F7, 0xFFF9F9F9, 0xFFFCFCFC, 0xFFFEFEFE, 0xFFFEFEFE, 0xFFFEFEFE, 0xFFFCFCFC, 0xFFFAFAFA, 0xFFF7F7F7, 0xFFF5F5F5, 0xFFF2F2F2, 0xFFE9E9E9, 0xFFD4AC2F, 0xFFFDEE55, 0xFFFDEC53, 0xFFF6DE47, 0xE4DE9E19, 0x49D38105, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x2DD07A00, 0xD7E39E1C, 0xFFFDDC4A, 0xFFFBD047, 0xFFFACC45, 0xFFF9CB45, 0xFFC4A258, 0xFFD0D0D0, 0xFFD6D6D6, 0xFFDDDDDD, 0xFFE6E6E6, 0xFFEEEEEE, 0xFFF6F6F6, 0xFFFBFBFB, 0xFFF0E5CC, 0xFFD4B167, 0xFFD2B066, 0xFFD0AE64, 0xFFE9E9E9, 0xFFE1E1E1, 0xFFD8D8D8, 0xFFD2D2D2, 0xFFD3A12A, 0xFFF9CB45, 0xFFFACD46, 0xFFFBD348, 0xFFF7CD3E, 0xB2DB9112, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x52D07A00, 0xFCEBAD2C, 0xFFFCCC44, 0xFFF9C943, 0xFFF9C943, 0xFFF9C943, 0xFFC4A258, 0xFFD0D0D0, 0xFFD6D6D6, 0xFFDDDDDD, 0xFFE6E6E6, 0xFFEEEEEE, 0xFFF6F6F6, 0xFFFBFBFB, 0xFFE9D8B3, 0xFFE6B437, 0xFFF9C943, 0xFFD3A02A, 0xFFE9E9E9, 0xFFE1E1E1, 0xFFD8D8D8, 0xFFD2D2D2, 0xFFD3A02A, 0xFFF9C943, 0xFFF9C943, 0xFFF9C943, 0xFFFBCB44, 0xFADD9416, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x55D07A00, 0xFFEBAD2B, 0xFFF9C741, 0xFFF9C741, 0xFFF9C741, 0xFFF9C741, 0xFFC4A258, 0xFFD0D0D0, 0xFFD6D6D6, 0xFFDDDDDD, 0xFFE6E6E6, 0xFFEEEEEE, 0xFFF6F6F6, 0xFFFBFBFB, 0xFFE9D8B3, 0xFFE6B335, 0xFFF9C741, 0xFFD3A029, 0xFFE9E9E9, 0xFFE1E1E1, 0xFFD8D8D8, 0xFFD2D2D2, 0xFFD3A029, 0xFFF9C741, 0xFFF9C741, 0xFFF9C741, 0xFFF9C741, 0xFFDD9416, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x55D07A00, 0xFFEAAB2A, 0xFFF8C43F, 0xFFF8C43F, 0xFFF8C43F, 0xFFF8C43F, 0xFFC4A258, 0xFFD0D0D0, 0xFFD6D6D6, 0xFFDDDDDD, 0xFFE6E6E6, 0xFFEEEEEE, 0xFFF6F6F6, 0xFFFBFBFB, 0xFFE9D8B3, 0xFFE5B133, 0xFFF8C43F, 0xFFD29F28, 0xFFE9E9E9, 0xFFE1E1E1, 0xFFD8D8D8, 0xFFD2D2D2, 0xFFD29F28, 0xFFF8C43F, 0xFFF8C43F, 0xFFF8C43F, 0xFFF8C43F, 0xFFDD9315, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x55D07A00, 0xFFEAAA28, 0xFFF8C23C, 0xFFF8C23C, 0xFFF8C23C, 0xFFF8C23C, 0xFFC4A258, 0xFFD0D0D0, 0xFFD6D6D6, 0xFFDDDDDD, 0xFFE6E6E6, 0xFFEEEEEE, 0xFFF6F6F6, 0xFFFBFBFB, 0xFFE9D8B3, 0xFFE5B032, 0xFFF8C23C, 0xFFD29E27, 0xFFE9E9E9, 0xFFE1E1E1, 0xFFD8D8D8, 0xFFD2D2D2, 0xFFD29E27, 0xFFF8C23C, 0xFFF8C23C, 0xFFF8C23C, 0xFFF8C23C, 0xFFDD9214, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x55D07A00, 0xFFEAA826, 0xFFF8BF39, 0xFFF8BF39, 0xFFF8BF39, 0xFFF8BF39, 0xFFC9A352, 0xFFCFCDC7, 0xFFD6D6D6, 0xFFDDDDDD, 0xFFE6E6E6, 0xFFEEEEEE, 0xFFF6F6F6, 0xFFFBFBFB, 0xFFE9D8B3, 0xFFD8A329, 0xFFE5AE30, 0xFFCC9723, 0xFFECECEC, 0xFFE1E1E1, 0xFFD8D8D8, 0xFFD0CCC2, 0xFFD8A128, 0xFFF8BF39, 0xFFF8BF39, 0xFFF8BF39, 0xFFF8BF39, 0xFFDD9113, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x55D07A00, 0xFFEAA624, 0xFFF8BC36, 0xFFF8BC36, 0xFFF8BC36, 0xFFF8BC36, 0xFFD7A63B, 0xFFCCBFA3, 0xFFD6D6D6, 0xFFDDDDDD, 0xFFE6E6E6, 0xFFEEEEEE, 0xFFF6F6F6, 0xFFFBFBFB, 0xFFF8F2E6, 0xFFEAD8B3, 0xFFEAD8B3, 0xFFEAD8B3, 0xFFF5F5F5, 0xFFE1E1E1, 0xFFD8D7D5, 0xFFCBB280, 0xFFE9AF2F, 0xFFF8BC36, 0xFFF8BC36, 0xFFF8BC36, 0xFFF8BC36, 0xFFDD9012, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x55D07A00, 0xFFEAA422, 0xFFF8B933, 0xFFF8B933, 0xFFF8B933, 0xFFF8B933, 0xFFF1B430, 0xFFD6A02D, 0xFFD0B57B, 0xFFD3C099, 0xFFD9C8A3, 0xFFDFCDA8, 0xFFE4D3AE, 0xFFE7D6B1, 0xFFE9D8B3, 0xFFE8D7B2, 0xFFE5D3AE, 0xFFE1CFAA, 0xFFDBCAA5, 0xFFD5C298, 0xFFD0AB5D, 0xFFDDA42C, 0xFFF8B933, 0xFFF8B933, 0xFFF8B933, 0xFFF8B933, 0xFFF8B933, 0xFFDD8F11, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x55D07A00, 0xFFEAA120, 0xFFF7B530, 0xFFF7B530, 0xFFF7B530, 0xFFF7B530, 0xFFF7B530, 0xFFF4B32F, 0xFFE9AB2B, 0xFFE5A72A, 0xFFE4A729, 0xFFE4A729, 0xFFE4A729, 0xFFE4A729, 0xFFE4A729, 0xFFE4A729, 0xFFE4A729, 0xFFE4A729, 0xFFE4A729, 0xFFE5A82A, 0xFFEDAE2D, 0xFFF5B430, 0xFFF7B530, 0xFFF7B530, 0xFFF7B530, 0xFFF7B530, 0xFFF7B530, 0xFFDD8E10, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x55D07A00, 0xFFEA9F1E, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFF7B22D, 0xFFDD8D0F, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x55D07A00, 0xFFEA9D1C, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFF7AF2A, 0xFFDD8B0E, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x55D07A00, 0xFFEA9A19, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF0A725, 0xFFE8A324, 0xFFE4A123, 0xFFE4A123, 0xFFE4A123, 0xFFE4A123, 0xFFE4A123, 0xFFE4A123, 0xFFE4A123, 0xFFE4A123, 0xFFE4A123, 0xFFE4A123, 0xFFE4A123, 0xFFE4A123, 0xFFE5A124, 0xFFE9A424, 0xFFF4AA26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFF7AB26, 0xFFDD8A0D, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x55D07A00, 0xFFE99917, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFE9A122, 0xFFD7A84A, 0xFFE5CC98, 0xFFEAD8B3, 0xFFEAD8B3, 0xFFEAD8B3, 0xFFEAD8B3, 0xFFEAD8B3, 0xFFEAD8B3, 0xFFEAD8B3, 0xFFEAD8B3, 0xFFEAD8B3, 0xFFEAD8B3, 0xFFEAD8B3, 0xFFEAD8B3, 0xFFE9D6AE, 0xFFE4C78C, 0xFFD89C2A, 0xFFF0A522, 0xFFF6A823, 0xFFF6A823, 0xFFF6A823, 0xFFDD890B, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x55D07A00, 0xFFE99614, 0xFFF6A41F, 0xFFF6A41F, 0xFFEFA11F, 0xFFD7A94D, 0xFFFBF9F6, 0xFFF7F7F7, 0xFFEFEFEF, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFF2F2F2, 0xFFF8F8F8, 0xFFEEE1C5, 0xFFDBA136, 0xFFF6A41F, 0xFFF6A41F, 0xFFF6A41F, 0xFFDC880A, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x55D07A00, 0xFFE99413, 0xFFF6A11C, 0xFFF6A11C, 0xFFE79B1C, 0xFFDDC594, 0xFFF3F3F3, 0xFFEDEDED, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFEDEDED, 0xFFF2EFEA, 0xFFD2AA59, 0xFFF6A11C, 0xFFF6A11C, 0xFFF6A11C, 0xFFDC8709, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x55D07A00, 0xFFE99110, 0xFFF69D18, 0xFFF69D18, 0xFFE49719, 0xFFDCCAA5, 0xFFE9E9E9, 0xFFE2E2E2, 0xFFDEDEDE, 0xFFDEDEDE, 0xFFDEDEDE, 0xFFDEDEDE, 0xFFDEDEDE, 0xFFDEDEDE, 0xFFDEDEDE, 0xFFDEDEDE, 0xFFDEDEDE, 0xFFDEDEDE, 0xFFDEDEDE, 0xFFDEDEDE, 0xFFDEDEDE, 0xFFE6E6E6, 0xFFEAEAEA, 0xFFCEAB61, 0xFFF69D18, 0xFFF69D18, 0xFFF69D18, 0xFFDC8608, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x55D07A00, 0xFFE88F0E, 0xFFF59A15, 0xFFF59A15, 0xFFE39518, 0xFFDAC9A4, 0xFFE7E7E7, 0xFFE0E0E0, 0xFFDCDCDC, 0xFFDCDCDC, 0xFFDCDCDC, 0xFFDCDCDC, 0xFFDCDCDC, 0xFFDCDCDC, 0xFFDCDCDC, 0xFFDCDCDC, 0xFFDCDCDC, 0xFFDCDCDC, 0xFFDCDCDC, 0xFFDCDCDC, 0xFFDCDCDC, 0xFFE4E4E4, 0xFFE7E7E7, 0xFFCDAA60, 0xFFF59A15, 0xFFF59A15, 0xFFF59A15, 0xFFDC8507, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x55D07A00, 0xFFE88D0C, 0xFFF59712, 0xFFF59712, 0xFFE39315, 0xFFD8C6A1, 0xFFE4E4E4, 0xFFE4E4E4, 0xFFE4E4E4, 0xFFE4E4E4, 0xFFE4E4E4, 0xFFE4E4E4, 0xFFE4E4E4, 0xFFE4E4E4, 0xFFE4E4E4, 0xFFE4E4E4, 0xFFE4E4E4, 0xFFE4E4E4, 0xFFE4E4E4, 0xFFE4E4E4, 0xFFE4E4E4, 0xFFE4E4E4, 0xFFE4E4E4, 0xFFCCA95F, 0xFFF59712, 0xFFF59712, 0xFFF59712, 0xFFDC8406, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x55D07A00, 0xFFE88B0A, 0xFFF5930F, 0xFFF5930F, 0xFFE39114, 0xFFD5C49F, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFCBA85E, 0xFFF5930F, 0xFFF5930F, 0xFFF5930F, 0xFFDC8205, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x55D07A00, 0xFFE88808, 0xFFF5900C, 0xFFF5900C, 0xFFE38E11, 0xFFD3C29D, 0xFFDCDCDC, 0xFFCECECE, 0xFFC7C7C7, 0xFFC7C7C7, 0xFFC7C7C7, 0xFFC7C7C7, 0xFFC7C7C7, 0xFFC7C7C7, 0xFFC7C7C7, 0xFFC7C7C7, 0xFFC7C7C7, 0xFFC7C7C7, 0xFFC7C7C7, 0xFFC7C7C7, 0xFFC7C7C7, 0xFFD6D6D6, 0xFFDDDDDD, 0xFFCAA75D, 0xFFF5900C, 0xFFF5900C, 0xFFF5900C, 0xFFDC8104, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x55D07A00, 0xFFE88706, 0xFFF58E09, 0xFFF58E09, 0xFFE38D10, 0xFFD1C09B, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFC8A65C, 0xFFF58E09, 0xFFF58E09, 0xFFF58E09, 0xFFDC8103, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x55D07A00, 0xFFE88504, 0xFFF48B07, 0xFFF48B07, 0xFFE38B0E, 0xFFCEBD98, 0xFFD6D6D6, 0xFFD6D6D6, 0xFFD6D6D6, 0xFFD6D6D6, 0xFFD6D6D6, 0xFFD6D6D6, 0xFFD6D6D6, 0xFFD6D6D6, 0xFFD6D6D6, 0xFFD6D6D6, 0xFFD6D6D6, 0xFFD6D6D6, 0xFFD6D6D6, 0xFFD6D6D6, 0xFFD6D6D6, 0xFFD6D6D6, 0xFFD6D6D6, 0xFFC7A55B, 0xFFF48B07, 0xFFF48B07, 0xFFF48B07, 0xFFDC8002, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x52D07A00, 0xFCE78404, 0xFFF48905, 0xFFF48905, 0xFFE28A0D, 0xFFCDBC97, 0xFFD3D3D3, 0xFFC6C6C6, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFCDCDCD, 0xFFD4D4D4, 0xFFC7A45A, 0xFFF48905, 0xFFF48905, 0xFFF38905, 0xFADC7F02, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x2ED07A00, 0xD8DF7F01, 0xFFF38602, 0xFFF48602, 0xFFE2880B, 0xFFCBBA95, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFC6A359, 0xFFF48602, 0xFFF48602, 0xFFED8402, 0xB2D97D01, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x08D07A00, 0x7BD37B00, 0xFFE27F00, 0xFFF08401, 0xFFE2870A, 0xFFCAB893, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFC5A258, 0xFFF38501, 0xFFEE8301, 0xE4DB7D00, 0x49D27B00, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x43D07A00, 0x8DD07A00, 0xAACD7D05, 0xAAC28919, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC08C1D, 0xAAC48715, 0xA5D07A00, 0x7FD07A00, 0x1DD07A00, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 - }, - { - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x0E999999, 0x59999999, 0x9E999999, 0xAA999999, 0xAA999999, 0xAA999999, 0xAA999999, 0xAA999999, 0xAA999999, 0xAA999999, 0xAA999999, 0xAA999999, 0xAA999999, 0xAA999999, 0xAA999999, 0xAA999999, 0xAA999999, 0xAA999999, 0xAA999999, 0xAA999999, 0xAA999999, 0xAA999999, 0x8B999999, 0x41999999, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x0F999999, 0xC8AEAEAE, 0xFFDADADA, 0xFFF7F7F7, 0xFFFAFAFA, 0xFFFAFAFA, 0xFFFAFAFA, 0xFFFAFAFA, 0xFFFAFAFA, 0xFFFAFAFA, 0xFFFAFAFA, 0xFFFAFAFA, 0xFFFAFAFA, 0xFFFAFAFA, 0xFFFAFAFA, 0xFFFAFAFA, 0xFFFAFAFA, 0xFFFAFAFA, 0xFFFAFAFA, 0xFFFAFAFA, 0xFFFAFAFA, 0xFFFAFAFA, 0xFFFAFAFA, 0xFFF1F1F1, 0xFFCDCDCD, 0x7BA1A1A1, 0x08999999, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x5B999999, 0xFFDADADA, 0xFFF8F8F8, 0xFFF2F2F2, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFF0F0F0, 0xFFF3F3F3, 0xFFFAFAFA, 0xD8BFBFBF, 0x2E999999, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0xA3999999, 0xFFEEEEEE, 0xFFF0F0F0, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFF2F2F2, 0xFCD1D1D1, 0x53999999, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0xAA999999, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFEEEEEE, 0xFFD1D1D1, 0x55999999, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0xAA999999, 0xFFEDEDED, 0xFFEDEDED, 0xFFEDEDED, 0xFFB5B5B5, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF9A9A9A, 0xFFD3D3D3, 0xFFEDEDED, 0xFFEDEDED, 0xFFD1D1D1, 0x55999999, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0xAA999999, 0xFFEDEDED, 0xFFEDEDED, 0xFFEDEDED, 0xFFDADADA, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFD1D1D1, 0xFFE4E4E4, 0xFFEDEDED, 0xFFEDEDED, 0xFFD1D1D1, 0x55999999, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0xAA999999, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFECECEC, 0xFFD0D0D0, 0x55999999, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0xAA999999, 0xFFEBEBEB, 0xFFEBEBEB, 0xFFEBEBEB, 0xFFEBEBEB, 0xFFEBEBEB, 0xFFEBEBEB, 0xFFEBEBEB, 0xFFEBEBEB, 0xFFEBEBEB, 0xFFEBEBEB, 0xFFEBEBEB, 0xFFEBEBEB, 0xFFEBEBEB, 0xFFEBEBEB, 0xFFEBEBEB, 0xFFEBEBEB, 0xFFEBEBEB, 0xFFEBEBEB, 0xFFEBEBEB, 0xFFEBEBEB, 0xFFEBEBEB, 0xFFEBEBEB, 0xFFEBEBEB, 0xFFEBEBEB, 0xFFCFCFCF, 0x55999999, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0xAA999999, 0xFFEAEAEA, 0xFFEAEAEA, 0xFFEAEAEA, 0xFFD8D8D8, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFCFCFCF, 0xFFE1E1E1, 0xFFEAEAEA, 0xFFEAEAEA, 0xFFCFCFCF, 0x55999999, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0xAA999999, 0xFFE9E9E9, 0xFFE9E9E9, 0xFFE9E9E9, 0xFFB4B4B4, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF9A9A9A, 0xFFD0D0D0, 0xFFE9E9E9, 0xFFE9E9E9, 0xFFCECECE, 0x55999999, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0xAA999999, 0xFFE8E8E8, 0xFFE8E8E8, 0xFFE8E8E8, 0xFFE8E8E8, 0xFFE8E8E8, 0xFFE8E8E8, 0xFFE8E8E8, 0xFFE8E8E8, 0xFFE8E8E8, 0xFFE8E8E8, 0xFFE8E8E8, 0xFFE8E8E8, 0xFFE8E8E8, 0xFFE8E8E8, 0xFFE8E8E8, 0xFFE8E8E8, 0xFFE8E8E8, 0xFFE8E8E8, 0xFFE8E8E8, 0xFFE8E8E8, 0xFFE8E8E8, 0xFFE8E8E8, 0xFFE8E8E8, 0xFFE8E8E8, 0xFFCDCDCD, 0x55999999, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0xAA999999, 0xFFE7E7E7, 0xFFE7E7E7, 0xFFE7E7E7, 0xFFE7E7E7, 0xFFE7E7E7, 0xFFE7E7E7, 0xFFE7E7E7, 0xFFE7E7E7, 0xFFE7E7E7, 0xFFE7E7E7, 0xFFE7E7E7, 0xFFE7E7E7, 0xFFE7E7E7, 0xFFE7E7E7, 0xFFE7E7E7, 0xFFE7E7E7, 0xFFE7E7E7, 0xFFE7E7E7, 0xFFE7E7E7, 0xFFE7E7E7, 0xFFE7E7E7, 0xFFE7E7E7, 0xFFE7E7E7, 0xFFE7E7E7, 0xFFCDCDCD, 0x55999999, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0xAA999999, 0xFFE6E6E6, 0xFFE6E6E6, 0xFFE6E6E6, 0xFFE6E6E6, 0xFFE6E6E6, 0xFFE6E6E6, 0xFFE6E6E6, 0xFFE6E6E6, 0xFFE6E6E6, 0xFFE6E6E6, 0xFFE6E6E6, 0xFFE6E6E6, 0xFFE6E6E6, 0xFFE6E6E6, 0xFFE6E6E6, 0xFFE6E6E6, 0xFFE6E6E6, 0xFFE6E6E6, 0xFFE6E6E6, 0xFFE6E6E6, 0xFFE6E6E6, 0xFFE6E6E6, 0xFFE6E6E6, 0xFFE6E6E6, 0xFFCCCCCC, 0x55999999, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0xAA999999, 0xFFE5E5E5, 0xFFE5E5E5, 0xFFE5E5E5, 0xFFE5E5E5, 0xFFE5E5E5, 0xFFE5E5E5, 0xFFE5E5E5, 0xFFE5E5E5, 0xFFE5E5E5, 0xFFE5E5E5, 0xFFE5E5E5, 0xFFE5E5E5, 0xFFE5E5E5, 0xFFE5E5E5, 0xFFE5E5E5, 0xFFE5E5E5, 0xFFE5E5E5, 0xFFE5E5E5, 0xFFE5E5E5, 0xFFE5E5E5, 0xFFE5E5E5, 0xFFE5E5E5, 0xFFE5E5E5, 0xFFE5E5E5, 0xFFCBCBCB, 0x55999999, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0xAA999999, 0xFFE4E4E4, 0xFFE4E4E4, 0xFFE4E4E4, 0xFFB2B2B2, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF9A9A9A, 0xFFCCCCCC, 0xFFE4E4E4, 0xFFE4E4E4, 0xFFCBCBCB, 0x55999999, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0xAA999999, 0xFFE3E3E3, 0xFFE3E3E3, 0xFFE3E3E3, 0xFFD2D2D2, 0xFFCACACA, 0xFFCACACA, 0xFFCACACA, 0xFFCACACA, 0xFFCACACA, 0xFFCACACA, 0xFFCACACA, 0xFFCACACA, 0xFFCACACA, 0xFFCACACA, 0xFFCACACA, 0xFFCACACA, 0xFFCACACA, 0xFFCACACA, 0xFFCACACA, 0xFFB0BABD, 0xFFAEB9BC, 0xFFDADBDB, 0xFFABC1C8, 0xFFD9DDDE, 0xFFCACACA, 0x55999999, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0xAA999999, 0xFFE2E2E2, 0xFFE2E2E2, 0xFFE2E2E2, 0xFFE2E2E2, 0xFFE2E2E2, 0xFFE2E2E2, 0xFFE2E2E2, 0xFFE2E2E2, 0xFFE2E2E2, 0xFFE2E2E2, 0xFFE2E2E2, 0xFFE2E2E2, 0xFFE2E2E2, 0xFFE2E2E2, 0xFFE2E2E2, 0xFFE2E2E2, 0xFFDEE0E0, 0xFFBECFD4, 0xFFC5D1D4, 0xFF6CADC1, 0xFF53B4CE, 0xFF89B6C4, 0xFF35AAC8, 0xFFA8C3CC, 0xFFA6B9BE, 0x59758F96, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0xAA999999, 0xFFE1E1E1, 0xFFE1E1E1, 0xFFE1E1E1, 0xFFE1E1E1, 0xFFE1E1E1, 0xFFE1E1E1, 0xFFE1E1E1, 0xFFE1E1E1, 0xFFE1E1E1, 0xFFE1E1E1, 0xFFE1E1E1, 0xFFE1E1E1, 0xFFE1E1E1, 0xFFE1E1E1, 0xFFE1E1E1, 0xFFE1E1E1, 0xFFD2D7D9, 0xFF5CABC4, 0xFF4AB6D1, 0xFF35ACD0, 0xFF2ABAE5, 0xFF25B0D9, 0xFF28B6E3, 0xFF49ACC8, 0xFF3AACCB, 0x632385A4, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0xAA999999, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFE0E0E0, 0xFFDEDFDF, 0xFFA7C0C9, 0xFF79B4C5, 0xFF3CA1C0, 0xFF29B3E0, 0xFF25B0DC, 0xFF5DC2E3, 0xFFB1E2F2, 0xFF59C2E3, 0xFF26B3DE, 0xFF26A8D2, 0xA41C8CAD, 0x661783A4, 0x180E6784, - 0x00000000, 0x00000000, 0x00000000, 0xAA999999, 0xFFDFDFDF, 0xFFDFDFDF, 0xFFDFDFDF, 0xFFC0C0C0, 0xFFB0B0B0, 0xFFB0B0B0, 0xFFB0B0B0, 0xFFB0B0B0, 0xFFB0B0B0, 0xFFB0B0B0, 0xFFB0B0B0, 0xFFB0B0B0, 0xFFB0B0B0, 0xFFB0B0B0, 0xFFDEDEDE, 0xFF87AEBA, 0xFF24A7D1, 0xFF25ABD5, 0xFF25A8D1, 0xFF1EA4CF, 0xFF9BD7EA, 0xFFFFFFFF, 0xFF91D3E8, 0xFF23A6D0, 0xFF25A9D2, 0xFD26ACD4, 0xD11E94B8, 0x280D647F, - 0x00000000, 0x00000000, 0x00000000, 0xAA999999, 0xFFDEDEDE, 0xFFDEDEDE, 0xFFDEDEDE, 0xFFBFBFBF, 0xFFB0B0B0, 0xFFB0B0B0, 0xFFB0B0B0, 0xFFB0B0B0, 0xFFB0B0B0, 0xFFB0B0B0, 0xFFB0B0B0, 0xFFB0B0B0, 0xFFB0B0B0, 0xFFB0B0B0, 0xFFBFCCD1, 0xFF85B3C2, 0xFF1F96BC, 0xFF229FC7, 0xFF229FC6, 0xFF1B9BC5, 0xFF95D1E4, 0xFFFFFFFF, 0xFF8DCDE2, 0xFF219DC6, 0xFF229FC6, 0xFF22A0C8, 0xBE1986A9, 0x46137696, - 0x00000000, 0x00000000, 0x00000000, 0xAA999999, 0xFFDDDDDD, 0xFFDDDDDD, 0xFFDDDDDD, 0xFFDDDDDD, 0xFFDDDDDD, 0xFFDDDDDD, 0xFFDDDDDD, 0xFFDDDDDD, 0xFFDDDDDD, 0xFFDDDDDD, 0xFFDDDDDD, 0xFFDDDDDD, 0xFFDDDDDD, 0xFFDDDDDD, 0xFF77A5B4, 0xFF2795B9, 0xFF2099C0, 0xFF2097BD, 0xFF2097BD, 0xFF1A95BC, 0xFF8BC9DD, 0xFFFEFEFF, 0xFF7CC2D8, 0xFF1D96BC, 0xFF2097BD, 0xFF2097BE, 0xFB219BC1, 0xDE1780A1, - 0x00000000, 0x00000000, 0x00000000, 0xAA999999, 0xFFDCDCDC, 0xFFDCDCDC, 0xFFDCDCDC, 0xFFDCDCDC, 0xFFDCDCDC, 0xFFDCDCDC, 0xFFDCDCDC, 0xFFDCDCDC, 0xFFDCDCDC, 0xFFDCDCDC, 0xFFDCDCDC, 0xFFDCDCDC, 0xFFDCDCDC, 0xFFDCDCDC, 0xFFCBD2D3, 0xFF5293A8, 0xFF1D8FB3, 0xFF1C8EB1, 0xFF1C8EB1, 0xFF198CB0, 0xFF77BAD0, 0xFFFCFDFE, 0xFF64B1CA, 0xFF178BAF, 0xFF1C8EB1, 0xFF1C8EB2, 0xF21984A6, 0x5E0F6884, - 0x00000000, 0x00000000, 0x00000000, 0xAA999999, 0xFFDBDBDB, 0xFFDBDBDB, 0xFFDBDBDB, 0xFFDBDBDB, 0xFFDBDBDB, 0xFFDBDBDB, 0xFFDBDBDB, 0xFFDBDBDB, 0xFFDBDBDB, 0xFFDBDBDB, 0xFFDBDBDB, 0xFFDBDBDB, 0xFFDBDBDB, 0xFFDBDBDB, 0xFF85ABB8, 0xFF2789AA, 0xFF1B87A9, 0xFF1A85A7, 0xFF1A85A7, 0xFF1884A6, 0xFF69AEC5, 0xFFF9FCFD, 0xFF51A2BC, 0xFF1683A5, 0xFF1A85A7, 0xFF1A85A7, 0xFB1B88AB, 0xC0147695, - 0x00000000, 0x00000000, 0x00000000, 0xAA999999, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFAFC0C6, 0xFF5F95A6, 0xFF177998, 0xFF187C9D, 0xFF187C9C, 0xFF177B9C, 0xFF2C87A4, 0xFF75B0C3, 0xFF1E7F9F, 0xFF177B9C, 0xFF187C9C, 0xFF177D9D, 0xD7147190, 0x7C0F6682, - 0x00000000, 0x00000000, 0x00000000, 0xAA999999, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFFDADADA, 0xFF94B1B9, 0xFF167695, 0xFF167695, 0xFF157593, 0xFF137492, 0xFF4E97AE, 0xFFA3CAD6, 0xFF4390A9, 0xFF147492, 0xFF157593, 0xFF177796, 0xBD126D8B, 0x190B5B75, - 0x00000000, 0x00000000, 0x00000000, 0x91999999, 0xFFD2D2D2, 0xFFD9D9D9, 0xFFD9D9D9, 0xFFD9D9D9, 0xFFD9D9D9, 0xFFD9D9D9, 0xFFD9D9D9, 0xFFD9D9D9, 0xFFD9D9D9, 0xFFD9D9D9, 0xFFD9D9D9, 0xFFD9D9D9, 0xFFD9D9D9, 0xFFD9D9D9, 0xFFD6D7D8, 0xFF7FA4B0, 0xFF4D899B, 0xFF21718B, 0xFF136D8A, 0xFF0C6986, 0xFF7FB0BF, 0xFFE5EFF2, 0xFF78ABBB, 0xFF116C89, 0xFC136C88, 0xCA106682, 0x990F6580, 0x240B5E78, - 0x00000000, 0x00000000, 0x00000000, 0x45999999, 0xFFBCBCBC, 0xFFD8D8D8, 0xFFD9D9D9, 0xFFD9D9D9, 0xFFD9D9D9, 0xFFD9D9D9, 0xFFD9D9D9, 0xFFD9D9D9, 0xFFD9D9D9, 0xFFD9D9D9, 0xFFD9D9D9, 0xFFD9D9D9, 0xFFD9D9D9, 0xFFD9D9D9, 0xFFD9D9D9, 0xFFD3D6D6, 0xFFBFC9CC, 0xFF3D7E93, 0xFF126681, 0xFF116682, 0xFF22728B, 0xFF44889D, 0xFF1F6F89, 0xF90F6480, 0xFC116681, 0x620D607A, 0x0A0A5A74, 0x020A5B75, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x7B9C9C9C, 0xD7B1B1B1, 0xFAC1C1C1, 0xFFC3C3C3, 0xFFC3C3C3, 0xFFC3C3C3, 0xFFC3C3C3, 0xFFC3C3C3, 0xFFC3C3C3, 0xFFC3C3C3, 0xFFC3C3C3, 0xFFC3C3C3, 0xFFC3C3C3, 0xFFC3C3C3, 0xFFC3C3C3, 0xFFC3C3C3, 0xFFBDBFC0, 0xFF819BA4, 0xFF96A8AD, 0xFF467C8E, 0xF220687F, 0xCB276A7F, 0xE90E607A, 0x520B5D77, 0x5F0B5D77, 0x2B0B5D77, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x07999999, 0x2D999999, 0x50999999, 0x55999999, 0x55999999, 0x55999999, 0x55999999, 0x55999999, 0x55999999, 0x55999999, 0x55999999, 0x55999999, 0x55999999, 0x55999999, 0x55999999, 0x55999999, 0x55999999, 0x55999999, 0x55999999, 0x72407283, 0x6626687D, 0x22467584, 0x800B5C76, 0x1A0A5B75, 0x00000000, 0x00000000, 0x00000000, 0x00000000 - } +// TODO: move this to external image files and zlib compress them into a source file as part of the build process +char const *const toolbar_icons_svg[] = { + // favourites star + u8"<?xml version='1.0' encoding='UTF-8' standalone='no'?>" + "<svg xmlns:svg='http://www.w3.org/2000/svg' xmlns='http://www.w3.org/2000/svg' version='1.1' height='104' width='104'>" + "<path fill='#ffef0f' stroke='#ffdf1f' stroke-width='8' stroke-linejoin='round' d='m 50,6 11,35 h 37 l -30,22 12,35 -30,-21 -30,21 12,-35 -30,-22 h 37 z' />" + "</svg>", + // save diskette + u8"<?xml version='1.0' encoding='UTF-8' standalone='no'?>" + "<svg xmlns:svg='http://www.w3.org/2000/svg' xmlns='http://www.w3.org/2000/svg' version='1.1' height='100' width='100'>" + "<path fill='#ffcf0f' d='m 0,3 a 3,3 0 0,1 3,-3 h 90 l 7,7 v 90 a 3,3 0 0,1 -3,3 h -94 a 3,3 0 0,1 -3,-3 z m 3,89 v 4 h 5 v -4 z m 89,0 v 4 h 5 v -4 z' />" + "<path fill='#ffbf1f' d='m 10,0 h 67 v 32 a 3,3 0 0,1 -3,3 h -61 a 3,3 0 0,1 -3,-3 z' />" + "<path fill='#b0b0b0' d='m 24,0 h 53 v 31 a 3,3 0 0,1 -3,3 h -47 a 3,3 0 0,1 -3,-3 z m 31,5 v 25 h 13 v -25 z' />" + "<path fill='#ffbf1f' d='m 10,47 a 3,3 0 0,1 3,-3 h 74 a 3,3 0 0,1 3,3 v 53 h -80 z' />" + "<path fill='#e7e7e7' d='m 11,48 a 3,3 0 0,1 3,-3 h 72 a 3,3 0 0,1 3,3 v 40 h -78 z' />" + "<path fill='#f71f1f' d='m 11,87 h 78 v 9 a 3,3 0 0,1 -3,3 h -72 a 3,3 0 0,1 -3,-3 z' />" + "</svg>", + // audit magnifying glass + u8"<?xml version='1.0' encoding='UTF-8' standalone='no'?>" + "<svg xmlns:svg='http://www.w3.org/2000/svg' xmlns='http://www.w3.org/2000/svg' version='1.1' height='100' width='100'>" + "<path fill-opacity='0' stroke='#bfbfbf' stroke-linecap='butt' stroke-width='8' d='m 68,68 -10,-10' />" + "<path fill-opacity='0' stroke='#cf8f3f' stroke-linecap='round' stroke-width='16' d='m 92,92 -22,-22' />" + "<circle cx='36' cy='36' r='36' fill='#cfcfcf' />" + "<circle cx='36' cy='36' r='30' fill='#9ebeff' />" + "<path fill-opacity='0' stroke='#b9cef7' stroke-linecap='round' stroke-width='10' d='m 16,36 a 20,20 0 0,1 20,-20' />" + "</svg>", + // info + u8"<?xml version='1.0' encoding='UTF-8' standalone='no'?>" + "<svg xmlns:svg='http://www.w3.org/2000/svg' xmlns='http://www.w3.org/2000/svg' version='1.1' height='100' width='100'>" + "<circle cx='50' cy='50' r='47' fill='#001fff' stroke='#3f56ff' stroke-width='6' stroke-opacity='0.8' />" + "<circle cx='50' cy='20' r='10' fill='#ffffff' />" + "<path fill='#ffffff' d='m 59,38 v 34 a 10,4 0 0,0 10,4 v 8 h -36 v -8 a 10,4 0 0,0 10,-4 v -23 a 8,4 0 0,0 -8,-4 v -6 z' />" + "</svg>", + // previous menu + u8"<?xml version='1.0' encoding='UTF-8' standalone='no'?>" + "<svg xmlns:svg='http://www.w3.org/2000/svg' xmlns='http://www.w3.org/2000/svg' version='1.1' height='100' width='100'>" + "<rect y='8' x='8' height='84' width='84' fill='#3f56ff' stroke='#3f56ff' stroke-width='16' stroke-opacity='0.8' stroke-linejoin='round' />" + "<rect y='10' x='10' height='80' width='80' fill='#001fff' stroke='#001fff' stroke-width='8' stroke-linejoin='round' />" + "<path fill='#ffffff' stroke='#ffffff' stroke-width='8' stroke-linejoin='round' d='m 16,46 28,-28 v 16 q 40,12 40,48 q -10,-24 -40,-24 v 16 z' />" + "</svg>", + // exit + u8"<?xml version='1.0' encoding='UTF-8' standalone='no'?>" + "<svg xmlns:svg='http://www.w3.org/2000/svg' xmlns='http://www.w3.org/2000/svg' version='1.1' height='100' width='100'>" + "<rect y='8' x='8' height='84' width='84' fill='#ff3f3f' fill-opacity='0.8' stroke='#ff3f3f' stroke-opacity='0.8' stroke-width='16' stroke-linejoin='round' />" + "<rect y='10' x='10' height='80' width='80' fill='#ff0000' stroke='#ff0000' stroke-width='8' stroke-linejoin='round' />" + "<path fill='#ffffff' stroke='#ffffff' stroke-width='8' stroke-linejoin='round' d='m 16,24 8,-8 26,26 26,-26 8,8 -26,26 26,26 -8,8 -26,-26 -26,26 -8,-8 26,-26 z' />" + "</svg>" }; + +enum +{ + TOOLBAR_BITMAP_FAVORITE, + TOOLBAR_BITMAP_SAVE, + TOOLBAR_BITMAP_AUDIT, + TOOLBAR_BITMAP_INFO, + TOOLBAR_BITMAP_PREVMENU, + TOOLBAR_BITMAP_EXIT }; -#define UI_TOOLBAR_BUTTONS (ARRAY_LENGTH(toolbar_bitmap_bmp)) +constexpr size_t UI_TOOLBAR_BUTTONS = std::size(toolbar_icons_svg); } // anonymous namespace + } // namespace ui #endif // MAME_FRONTEND_UI_TOOLBAR_IPP diff --git a/src/frontend/mame/ui/ui.cpp b/src/frontend/mame/ui/ui.cpp index 27987bbc9c9..a3977a310c0 100644 --- a/src/frontend/mame/ui/ui.cpp +++ b/src/frontend/mame/ui/ui.cpp @@ -9,40 +9,47 @@ *********************************************************************/ #include "emu.h" +#include "ui/ui.h" + +#include "infoxml.h" +#include "iptseqpoll.h" +#include "luaengine.h" #include "mame.h" +#include "ui/filemngr.h" +#include "ui/info.h" +#include "ui/mainmenu.h" +#include "ui/menu.h" +#include "ui/quitmenu.h" +#include "ui/sliders.h" +#include "ui/state.h" +#include "ui/systemlist.h" +#include "ui/viewgfx.h" + +#include "imagedev/cassette.h" +#include "machine/laserdsc.h" +#include "video/vector.h" + +#include "config.h" #include "emuopts.h" #include "mameopts.h" -#include "video/vector.h" -#include "machine/laserdsc.h" #include "drivenum.h" +#include "fileio.h" #include "natkeyboard.h" #include "render.h" -#include "luaengine.h" #include "cheat.h" #include "rendfont.h" +#include "rendlay.h" +#include "romload.h" +#include "screen.h" +#include "speaker.h" #include "uiinput.h" -#include "ui/ui.h" -#include "ui/info.h" -#include "ui/menu.h" -#include "ui/mainmenu.h" -#include "ui/filemngr.h" -#include "ui/sliders.h" -#include "ui/state.h" -#include "ui/viewgfx.h" -#include "imagedev/cassette.h" -#include "../osd/modules/lib/osdobj_common.h" - -/*************************************************************************** - CONSTANTS -***************************************************************************/ +// FIXME: allow OSD module headers to be included in a less ugly way +#include "../osd/modules/lib/osdlib.h" +#include "../osd/modules/lib/osdobj_common.h" -enum -{ - LOADSAVE_NONE, - LOADSAVE_LOAD, - LOADSAVE_SAVE -}; +#include <functional> +#include <type_traits> /*************************************************************************** @@ -100,11 +107,9 @@ static input_item_id const non_char_keys[] = // messagebox buffer std::string mame_ui_manager::messagebox_text; std::string mame_ui_manager::messagebox_poptext; -rgb_t mame_ui_manager::messagebox_backcolor; // slider info std::vector<ui::menu_item> mame_ui_manager::slider_list; -slider_state *mame_ui_manager::slider_current; /*************************************************************************** @@ -148,16 +153,114 @@ static uint32_t const mouse_bitmap[32*32] = }; +enum class mame_ui_manager::ui_callback_type : int +{ + NOINPUT, + GENERAL, + MODAL, + MENU, + CUSTOM +}; + + +struct mame_ui_manager::active_pointer +{ + active_pointer(ui_event const &event) + : target(event.target) + , updated(std::chrono::steady_clock::time_point::min()) + , type(event.pointer_type) + , ptrid(event.pointer_id) + , x(-1.0F) + , y(-1.0F) + { + } + + bool operator<(std::pair<render_target *, u16> const &val) const noexcept + { + return std::make_pair(target, ptrid) < val; + } + + render_target *target; + std::chrono::steady_clock::time_point updated; + osd::ui_event_handler::pointer type; + u16 ptrid; + float x, y; +}; + + +class mame_ui_manager::pointer_options +{ +public: + pointer_options() + : m_initial_timeout(std::chrono::seconds(3)) + , m_timeout(std::chrono::seconds(3)) + , m_initial_hide_inactive(true) + , m_hide_inactive(true) + , m_timeout_set(false) + , m_hide_inactive_set(false) + { + } + + std::chrono::steady_clock::duration timeout() const noexcept { return m_timeout; } + bool hide_inactive() const noexcept { return m_hide_inactive; } + bool timeout_set() const noexcept { return m_timeout_set; } + bool hide_inactive_set() const noexcept { return m_hide_inactive_set; } + bool options_set() const noexcept { return m_timeout_set || m_hide_inactive_set; } + + void set_initial_timeout(std::chrono::steady_clock::duration value) noexcept + { + m_initial_timeout = value; + if (!m_timeout_set) + m_timeout = value; + } + + void set_initial_hide_inactive(bool value) noexcept + { + m_initial_hide_inactive = value; + if (!m_hide_inactive_set) + m_hide_inactive = value; + } + + void set_timeout(std::chrono::steady_clock::duration value) noexcept + { + m_timeout = value; + m_timeout_set = true; + } + + void set_hide_inactive(bool value) noexcept + { + m_hide_inactive = value; + m_hide_inactive_set = true; + } + + void restore_initial() noexcept + { + m_timeout = m_initial_timeout; + m_hide_inactive = m_initial_hide_inactive; + m_timeout_set = false; + m_hide_inactive_set = false; + } + +private: + std::chrono::steady_clock::duration m_initial_timeout; + std::chrono::steady_clock::duration m_timeout; + bool m_initial_hide_inactive; + bool m_hide_inactive; + bool m_timeout_set; + bool m_hide_inactive_set; +}; + + //------------------------------------------------- // ctor - set up the user interface //------------------------------------------------- mame_ui_manager::mame_ui_manager(running_machine &machine) : ui_manager(machine) - , m_font(nullptr) - , m_handler_callback(nullptr) - , m_handler_callback_type(ui_callback_type::GENERAL) - , m_handler_param(0) + , m_font() + , m_handler_callback() + , m_handler_callback_type(ui_callback_type::NOINPUT) + , m_ui_active(true) , m_single_step(false) , m_showfps(false) , m_showfps_end(0) @@ -165,8 +268,17 @@ mame_ui_manager::mame_ui_manager(running_machine &machine) , m_popup_text_end(0) , m_mouse_bitmap(32, 32) , m_mouse_arrow_texture(nullptr) - , m_mouse_show(false) - , m_target_font_height(0) {} + , m_pointers_changed(false) + , m_target_font_height(0) + , m_unthrottle_mute(false) + , m_image_display_enabled(true) + , m_machine_info() + , m_unemulated_features() + , m_imperfect_features() + , m_last_launch_time(std::time_t(-1)) + , m_last_warning_time(std::time_t(-1)) +{ +} mame_ui_manager::~mame_ui_manager() { @@ -175,26 +287,41 @@ mame_ui_manager::~mame_ui_manager() void mame_ui_manager::init() { load_ui_options(); - // initialize the other UI bits - ui::menu::init(machine(), options()); - ui_gfx_init(machine()); + // start loading system names as early as possible + ui::system_list::instance().cache_data(options()); + + // initialize the other UI bits m_ui_colors.refresh(options()); // update font row info from setting update_target_font_height(); // more initialization - using namespace std::placeholders; - set_handler(ui_callback_type::GENERAL, std::bind(&mame_ui_manager::handler_messagebox, this, _1)); - m_non_char_keys_down = std::make_unique<uint8_t[]>((ARRAY_LENGTH(non_char_keys) + 7) / 8); - m_mouse_show = machine().system().flags & machine_flags::CLICKABLE_ARTWORK ? true : false; - - // request a callback upon exiting + set_handler( + ui_callback_type::NOINPUT, + handler_callback_func( + [this] (render_container &container) -> uint32_t + { + draw_text_box(container, messagebox_text, ui::text_layout::text_justify::LEFT, 0.5f, 0.5f, colors().background_color()); + return 0; + })); + m_non_char_keys_down = std::make_unique<uint8_t[]>((std::size(non_char_keys) + 7) / 8); + + // request notification callbacks + machine().add_notifier(MACHINE_NOTIFY_FRAME, machine_notify_delegate(&mame_ui_manager::frame_update, this)); machine().add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(&mame_ui_manager::exit, this)); + machine().configuration().config_register( + "ui_warnings", + configuration_manager::load_delegate(&mame_ui_manager::config_load_warnings, this), + configuration_manager::save_delegate(&mame_ui_manager::config_save_warnings, this)); + machine().configuration().config_register( + "pointer_input", + configuration_manager::load_delegate(&mame_ui_manager::config_load_pointers, this), + configuration_manager::save_delegate(&mame_ui_manager::config_save_pointers, this)); // create mouse bitmap - uint32_t *dst = &m_mouse_bitmap.pix32(0); + uint32_t *dst = &m_mouse_bitmap.pix(0); memcpy(dst,mouse_bitmap,32*32*sizeof(uint32_t)); m_mouse_arrow_texture = machine().render().texture_alloc(); m_mouse_arrow_texture->set_bitmap(m_mouse_bitmap, m_mouse_bitmap.cliprect(), TEXFORMAT_ARGB32); @@ -212,6 +339,25 @@ void mame_ui_manager::update_target_font_height() //------------------------------------------------- +// exit - called for each emulated frame +//------------------------------------------------- + +void mame_ui_manager::frame_update() +{ + // this hackery is needed to ensure natural keyboard and clickable artwork input is in sync with I/O ports + if (ui_callback_type::GENERAL == m_handler_callback_type) + { + process_ui_events(); + for (auto *target = machine().render().first_target(); target; target = target->next()) + { + if (!target->hidden()) + target->update_pointer_fields(); + } + } +} + + +//------------------------------------------------- // exit - clean up ourselves on exit //------------------------------------------------- @@ -222,10 +368,185 @@ void mame_ui_manager::exit() m_mouse_arrow_texture = nullptr; // free the font - if (m_font != nullptr) + m_font.reset(); + + // free persistent data for other classes + m_session_data.clear(); +} + + +//------------------------------------------------- +// config_load_warnings - load info on last time +// emulation status warnings showed +//------------------------------------------------- + +void mame_ui_manager::config_load_warnings( + config_type cfg_type, + config_level cfg_level, + util::xml::data_node const *parentnode) +{ + // make sure it's relevant and there's data available + if (config_type::SYSTEM == cfg_type) { - machine().render().font_free(m_font); - m_font = nullptr; + m_unemulated_features.clear(); + m_imperfect_features.clear(); + if (!parentnode) + { + m_last_launch_time = std::time_t(-1); + m_last_warning_time = std::time_t(-1); + } + else + { + m_last_launch_time = std::time_t(parentnode->get_attribute_int("launched", -1)); + m_last_warning_time = std::time_t(parentnode->get_attribute_int("warned", -1)); + for (util::xml::data_node const *node = parentnode->get_first_child(); node; node = node->get_next_sibling()) + { + if (!std::strcmp(node->get_name(), "feature")) + { + char const *const device = node->get_attribute_string("device", nullptr); + char const *const feature = node->get_attribute_string("type", nullptr); + char const *const status = node->get_attribute_string("status", nullptr); + if (device && *device && feature && *feature && status && *status) + { + if (!std::strcmp(status, "unemulated")) + m_unemulated_features.emplace(device, feature); + else if (!std::strcmp(status, "imperfect")) + m_imperfect_features.emplace(device, feature); + } + } + } + } + } +} + + +//------------------------------------------------- +// config_save_warnings - save information on +// last time emulation status warnings showed +//------------------------------------------------- + +void mame_ui_manager::config_save_warnings( + config_type cfg_type, + util::xml::data_node *parentnode) +{ + // only save system-level configuration when times are valid + if ((config_type::SYSTEM == cfg_type) && (std::time_t(-1) != m_last_launch_time) && (std::time_t(-1) != m_last_warning_time)) + { + parentnode->set_attribute_int("launched", static_cast<long long>(m_last_launch_time)); + parentnode->set_attribute_int("warned", static_cast<long long>(m_last_warning_time)); + + for (auto const &feature : m_unemulated_features) + { + util::xml::data_node *const feature_node = parentnode->add_child("feature", nullptr); + feature_node->set_attribute("device", feature.first.c_str()); + feature_node->set_attribute("type", feature.second.c_str()); + feature_node->set_attribute("status", "unemulated"); + } + + for (auto const &feature : m_imperfect_features) + { + util::xml::data_node *const feature_node = parentnode->add_child("feature", nullptr); + feature_node->set_attribute("device", feature.first.c_str()); + feature_node->set_attribute("type", feature.second.c_str()); + feature_node->set_attribute("status", "imperfect"); + } + } +} + + +//------------------------------------------------- +// config_load_pointers - load pointer input +// settings +//------------------------------------------------- + +void mame_ui_manager::config_load_pointers( + config_type cfg_type, + config_level cfg_level, + util::xml::data_node const *parentnode) +{ + switch (cfg_type) + { + case config_type::INIT: + { + int last(-1); + for (auto const &target : machine().render().targets()) + { + assert(target.index() >= 0); + if (!target.hidden()) + last = (std::max)(target.index(), last); + } + m_pointer_options.resize(last + 1); + } + break; + + case config_type::CONTROLLER: + case config_type::SYSTEM: + if (!parentnode) + break; + for (auto const *targetnode = parentnode->get_child("target"); targetnode; targetnode = targetnode->get_next_sibling("target")) + { + auto const index(targetnode->get_attribute_int("index", -1)); + if ((0 <= index) && (m_pointer_options.size() > index)) + { + auto const timeout(targetnode->get_attribute_float("activity_timeout", -1.0F)); + auto const ms(std::lround(timeout * 1000.0F)); + if ((0 <= ms) && (10'000 >= ms)) + { + if (config_type::SYSTEM == cfg_type) + m_pointer_options[index].set_timeout(std::chrono::milliseconds(ms)); + else + m_pointer_options[index].set_initial_timeout(std::chrono::milliseconds(ms)); + } + + auto const hide(targetnode->get_attribute_int("hide_inactive", -1)); + if (0 <= hide) + { + if (config_type::SYSTEM == cfg_type) + m_pointer_options[index].set_hide_inactive(hide != 0); + else + m_pointer_options[index].set_initial_hide_inactive(hide != 0); + } + } + } + break; + + case config_type::DEFAULT: + case config_type::FINAL: + break; + } +} + + +//------------------------------------------------- +// config_save_pointers - save pointer input +// settings +//------------------------------------------------- + +void mame_ui_manager::config_save_pointers( + config_type cfg_type, + util::xml::data_node *parentnode) +{ + if (config_type::SYSTEM == cfg_type) + { + for (std::size_t i = 0; m_pointer_options.size() > i; ++i) + { + pointer_options const &options(m_pointer_options[i]); + if (options.options_set()) + { + util::xml::data_node *const targetnode = parentnode->add_child("target", nullptr); + if (targetnode) + { + targetnode->set_attribute_int("index", i); + if (options.timeout_set()) + { + auto const ms(std::chrono::duration_cast<std::chrono::milliseconds>(options.timeout())); + targetnode->set_attribute_float("activity_timeout", float(ms.count()) * 0.001F); + } + if (options.hide_inactive_set()) + targetnode->set_attribute_int("hide_inactive", options.hide_inactive()); + } + } + } } } @@ -237,17 +558,10 @@ void mame_ui_manager::exit() void mame_ui_manager::initialize(running_machine &machine) { m_machine_info = std::make_unique<ui::machine_info>(machine); + set_ui_active(!machine_info().has_keyboard() || machine.options().ui_active()); // initialize the on-screen display system slider_list = slider_init(machine); - if (slider_list.size() > 0) - { - slider_current = reinterpret_cast<slider_state *>(slider_list[0].ref); - } - else - { - slider_current = nullptr; - } // if no test switch found, assign its input sequence to a service mode DIP if (!m_machine_info->has_test_switch() && m_machine_info->has_dips()) @@ -255,9 +569,16 @@ void mame_ui_manager::initialize(running_machine &machine) const char *const service_mode_dipname = ioport_configurer::string_from_token(DEF_STR(Service_Mode)); for (auto &port : machine.ioport().ports()) for (ioport_field &field : port.second->fields()) - if (field.type() == IPT_DIPSWITCH && strcmp(field.name(), service_mode_dipname) == 0) + if ((field.type() == IPT_DIPSWITCH) && (field.name() == service_mode_dipname)) // FIXME: probably breaks with localisation, also issues with multiple devices field.set_defseq(machine.ioport().type_seq(IPT_SERVICE)); } + + // handle throttle-related options and initial muting state now that the sound manager has been brought up + const bool starting_throttle = machine.options().throttle(); + machine.video().set_throttled(starting_throttle); + m_unthrottle_mute = options().unthrottle_mute(); + if (!starting_throttle && m_unthrottle_mute) + machine.sound().ui_mute(true); } @@ -266,8 +587,14 @@ void mame_ui_manager::initialize(running_machine &machine) // pair for the current UI handler //------------------------------------------------- -void mame_ui_manager::set_handler(ui_callback_type callback_type, const std::function<uint32_t (render_container &)> &&callback) +void mame_ui_manager::set_handler(ui_callback_type callback_type, handler_callback_func &&callback) { + m_active_pointers.clear(); + if (!m_display_pointers.empty()) + { + m_display_pointers.clear(); + m_pointers_changed = true; + } m_handler_callback = std::move(callback); m_handler_callback_type = callback_type; } @@ -301,84 +628,209 @@ static void output_joined_collection(const TColl &collection, TEmitMemberFunc em void mame_ui_manager::display_startup_screens(bool first_time) { const int maxstate = 3; - int str = machine().options().seconds_to_run(); + int const str = machine().options().seconds_to_run(); bool show_gameinfo = !machine().options().skip_gameinfo(); - bool show_warnings = true, show_mandatory_fileman = true; + bool show_warnings = true; bool video_none = strcmp(downcast<osd_options &>(machine().options()).video(), OSDOPTVAL_NONE) == 0; // disable everything if we are using -str for 300 or fewer seconds, or if we're the empty driver, // or if we are debugging, or if there's no mame window to send inputs to - if (!first_time || (str > 0 && str < 60*5) || &machine().system() == &GAME_NAME(___empty) || (machine().debug_flags & DEBUG_FLAG_ENABLED) != 0 || video_none) - show_gameinfo = show_warnings = show_mandatory_fileman = false; + if (!first_time || (str > 0 && str < 60*5) || &machine().system() == &GAME_NAME(___empty) || (machine().debug_flags & DEBUG_FLAG_ENABLED) || video_none) + show_gameinfo = show_warnings = false; #if defined(__EMSCRIPTEN__) // also disable for the JavaScript port since the startup screens do not run asynchronously show_gameinfo = show_warnings = false; #endif + // set up event handlers + switch_code_poller poller(machine().input()); + std::string warning_text; + rgb_t warning_color; + bool config_menu = false; + auto handler_messagebox_anykey = + [this, &poller, &warning_text, &warning_color, &config_menu] (render_container &container) -> uint32_t + { + // draw a standard message window + draw_text_box(container, warning_text, ui::text_layout::text_justify::LEFT, 0.5f, 0.5f, warning_color); + + if (machine().ui_input().pressed(IPT_UI_CANCEL)) + { + // if the user cancels, exit out completely + machine().schedule_exit(); + return HANDLER_CANCEL; + } + else if (machine().ui_input().pressed(IPT_UI_MENU)) + { + config_menu = true; + return HANDLER_CANCEL; + } + else if (poller.poll() != INPUT_CODE_INVALID) + { + // if any key is pressed, just exit + return HANDLER_CANCEL; + } + + return 0; + }; + set_handler(ui_callback_type::GENERAL, handler_callback_func(&mame_ui_manager::handler_ingame, this)); + // loop over states - using namespace std::placeholders; - set_handler(ui_callback_type::GENERAL, std::bind(&mame_ui_manager::handler_ingame, this, _1)); - for (int state = 0; state < maxstate && !machine().scheduled_event_pending() && !ui::menu::stack_has_special_main_menu(machine()); state++) + for (int state = 0; state < maxstate && !machine().scheduled_event_pending() && !ui::menu::stack_has_special_main_menu(*this); state++) { // default to standard colors - messagebox_backcolor = colors().background_color(); - messagebox_text.clear(); + warning_color = colors().background_color(); + warning_text.clear(); // pick the next state switch (state) { case 0: - if (show_warnings) - messagebox_text = machine_info().warnings_string(); - if (!messagebox_text.empty()) + if (show_gameinfo) + warning_text = machine_info().game_info_string(); + if (!warning_text.empty()) { - set_handler(ui_callback_type::MODAL, std::bind(&mame_ui_manager::handler_messagebox_anykey, this, _1)); - messagebox_backcolor = machine_info().warnings_color(); + warning_text.append(_("\n\nPress any key to continue")); + set_handler(ui_callback_type::MODAL, handler_callback_func(handler_messagebox_anykey)); } break; case 1: - if (show_gameinfo) - messagebox_text = machine_info().game_info_string(); - if (!messagebox_text.empty()) - set_handler(ui_callback_type::MODAL, std::bind(&mame_ui_manager::handler_messagebox_anykey, this, _1)); + if (show_warnings) + { + bool need_warning = machine_info().has_warnings(); + if (machine_info().has_severe_warnings() || !machine_info().has_warnings()) + { + // critical warnings - no need to persist stuff + m_unemulated_features.clear(); + m_imperfect_features.clear(); + m_last_launch_time = std::time_t(-1); + m_last_warning_time = std::time_t(-1); + } + else + { + // non-critical warnings - map current unemulated/imperfect features + device_feature_set unemulated_features, imperfect_features; + for (device_t &device : device_enumerator(machine().root_device())) + { + device_t::feature_type unemulated = device.type().unemulated_features(); + if ((&device != &machine().root_device()) && (device.type().emulation_flags() & device_t::flags::NOT_WORKING)) + unemulated_features.emplace(device.type().shortname(), "functionality"); + for (std::underlying_type_t<device_t::feature_type> feature = 1U; unemulated; feature <<= 1) + { + if (unemulated & feature) + { + char const *const name = info_xml_creator::feature_name(device_t::feature_type(feature)); + if (name) + unemulated_features.emplace(device.type().shortname(), name); + unemulated &= device_t::feature_type(~feature); + } + } + device_t::feature_type imperfect = device.type().imperfect_features(); + for (std::underlying_type_t<device_t::feature_type> feature = 1U; imperfect; feature <<= 1) + { + if (imperfect & feature) + { + char const *const name = info_xml_creator::feature_name(device_t::feature_type(feature)); + if (name) + imperfect_features.emplace(device.type().shortname(), name); + imperfect &= device_t::feature_type(~feature); + } + } + } + + // machine flags can cause warnings, too + if (machine_info().machine_flags() & machine_flags::NO_COCKTAIL) + unemulated_features.emplace(machine().root_device().type().shortname(), "cocktail"); + + // if the warnings match what was shown sufficiently recently, it's skippable + if ((unemulated_features != m_unemulated_features) || (imperfect_features != m_imperfect_features)) + { + m_last_launch_time = std::time_t(-1); + } + else if (!machine().rom_load().warnings() && (std::time_t(-1) != m_last_launch_time) && (std::time_t(-1) != m_last_warning_time) && options().skip_warnings()) + { + auto const now = std::chrono::system_clock::now(); + if (((std::chrono::system_clock::from_time_t(m_last_launch_time) + std::chrono::hours(7 * 24)) >= now) && ((std::chrono::system_clock::from_time_t(m_last_warning_time) + std::chrono::hours(14 * 24)) >= now)) + need_warning = false; + } + + // update the information to save out + m_unemulated_features = std::move(unemulated_features); + m_imperfect_features = std::move(imperfect_features); + if (need_warning) + m_last_warning_time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + } + if (need_warning) + { + warning_text = machine_info().warnings_string(); + warning_text.append(_("\n\nPress any key to continue")); + set_handler(ui_callback_type::MODAL, handler_callback_func(handler_messagebox_anykey)); + warning_color = machine_info().warnings_color(); + } + } break; case 2: std::vector<std::reference_wrapper<const std::string>> mandatory_images = mame_machine_manager::instance()->missing_mandatory_images(); - if (!mandatory_images.empty() && show_mandatory_fileman) + if (!mandatory_images.empty()) { std::ostringstream warning; - warning << _("This driver requires images to be loaded in the following device(s): "); + if ((str > 0) || (machine().debug_flags & DEBUG_FLAG_ENABLED) || video_none) + { + warning << "Images must be mounted for the following devices: "; + output_joined_collection(mandatory_images, + [&warning] (const std::reference_wrapper<const std::string> &img) { warning << img.get(); }, + [&warning] () { warning << ", "; }); + + throw emu_fatalerror(std::move(warning).str()); + } + warning << _("This system requires media images to be mounted for the following device(s): "); output_joined_collection(mandatory_images, - [&warning](const std::reference_wrapper<const std::string> &img) { warning << "\"" << img.get() << "\""; }, - [&warning]() { warning << ","; }); + [&warning] (const std::reference_wrapper<const std::string> &img) { warning << '"' << img.get() << '"'; }, + [&warning] () { warning << ", "; }); - ui::menu_file_manager::force_file_manager(*this, machine().render().ui_container(), warning.str().c_str()); + ui::menu_file_manager::force_file_manager(*this, machine().render().ui_container(), std::move(warning).str()); } break; } - // clear the input memory - machine().input().reset_polling(); - while (machine().input().poll_switches() != INPUT_CODE_INVALID) { } + // clear the input memory and wait for all keys to be released + poller.reset(); + while (poller.poll() != INPUT_CODE_INVALID) { } - // loop while we have a handler - while (m_handler_callback_type == ui_callback_type::MODAL && !machine().scheduled_event_pending() && !ui::menu::stack_has_special_main_menu(machine())) + if (m_handler_callback_type == ui_callback_type::MODAL) { - machine().video().frame_update(); + config_menu = false; + + // loop while we have a handler + while (m_handler_callback_type == ui_callback_type::MODAL && !machine().scheduled_event_pending() && !ui::menu::stack_has_special_main_menu(*this)) + machine().video().frame_update(); } // clear the handler and force an update - set_handler(ui_callback_type::GENERAL, std::bind(&mame_ui_manager::handler_ingame, this, _1)); + set_handler(ui_callback_type::GENERAL, handler_callback_func(&mame_ui_manager::handler_ingame, this)); machine().video().frame_update(); } + // update last launch time if this was a run that was eligible for emulation warnings + if (machine_info().has_warnings() && show_warnings && !machine().scheduled_event_pending()) + m_last_launch_time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + // if we're the empty driver, force the menus on - if (ui::menu::stack_has_special_main_menu(machine())) + if (ui::menu::stack_has_special_main_menu(*this)) + { + show_menu(); + } + else if (config_menu) + { show_menu(); + + // loop while we have a handler + while (m_handler_callback_type != ui_callback_type::GENERAL && !machine().scheduled_event_pending()) + machine().video().frame_update(); + } } @@ -394,7 +846,6 @@ void mame_ui_manager::set_startup_text(const char *text, bool force) // copy in the new text messagebox_text.assign(text); - messagebox_backcolor = colors().background_color(); // don't update more than 4 times/second if (force || (curtime - lastupdatetime) > osd_ticks_per_second() / 4) @@ -410,16 +861,20 @@ void mame_ui_manager::set_startup_text(const char *text, bool force) // render it; called by video.c //------------------------------------------------- -void mame_ui_manager::update_and_render(render_container &container) +bool mame_ui_manager::update_and_render(render_container &container) { // always start clean - container.empty(); + for (auto &target : machine().render().targets()) + { + if (target.ui_container()) + target.ui_container()->empty(); + } // if we're paused, dim the whole screen if (machine().phase() >= machine_phase::RESET && (single_step() || machine().paused())) { int alpha = (1.0f - machine().options().pause_brightness()) * 255.0f; - if (ui::menu::stack_has_special_main_menu(machine())) + if (ui::menu::stack_has_special_main_menu(*this)) alpha = 255; if (alpha > 255) alpha = 255; @@ -431,39 +886,48 @@ void mame_ui_manager::update_and_render(render_container &container) if (machine().phase() >= machine_phase::RESET) mame_machine_manager::instance()->cheat().render_text(*this, container); + // draw the FPS counter if it should be visible + if (show_fps_counter()) + draw_fps_counter(container); + // call the current UI handler - m_handler_param = m_handler_callback(container); + machine().ui_input().check_ui_inputs(); + uint32_t const handler_result = m_handler_callback(container); // display any popup messages if (osd_ticks() < m_popup_text_end) - draw_text_box(container, messagebox_poptext.c_str(), ui::text_layout::CENTER, 0.5f, 0.9f, messagebox_backcolor); + draw_text_box(container, messagebox_poptext, ui::text_layout::text_justify::CENTER, 0.5f, 0.9f, colors().background_color()); else m_popup_text_end = 0; - // display the internal mouse cursor - if (m_mouse_show || (is_menu_active() && machine().options().ui_mouse())) + // display the internal pointers + bool const pointer_update = m_pointers_changed; + m_pointers_changed = false; + if (!is_menu_active() || machine().options().ui_mouse()) { - int32_t mouse_target_x, mouse_target_y; - bool mouse_button; - render_target *mouse_target = machine().ui_input().find_mouse(&mouse_target_x, &mouse_target_y, &mouse_button); - - if (mouse_target != nullptr) + const float cursor_size = 0.6 * get_line_height(); + for (auto const &pointer : m_display_pointers) { - float mouse_y=-1,mouse_x=-1; - if (mouse_target->map_point_container(mouse_target_x, mouse_target_y, container, mouse_x, mouse_y)) - { - const float cursor_size = 0.6 * get_line_height(); - container.add_quad(mouse_x, mouse_y, mouse_x + cursor_size * container.manager().ui_aspect(&container), mouse_y + cursor_size, colors().text_color(), m_mouse_arrow_texture, PRIMFLAG_ANTIALIAS(1) | PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); - } + render_container &container = *pointer.target.get().ui_container(); + container.add_quad( + pointer.x, + pointer.y, + pointer.x + cursor_size * container.manager().ui_aspect(&container), + pointer.y + cursor_size, + rgb_t::white(), + m_mouse_arrow_texture, + PRIMFLAG_ANTIALIAS(1) | PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } } - // cancel takes us back to the ingame handler - if (m_handler_param == UI_HANDLER_CANCEL) + // cancel takes us back to the in-game handler + if (handler_result & HANDLER_CANCEL) { - using namespace std::placeholders; - set_handler(ui_callback_type::GENERAL, std::bind(&mame_ui_manager::handler_ingame, this, _1)); + machine().ui_input().reset(); + set_handler(ui_callback_type::GENERAL, handler_callback_func(&mame_ui_manager::handler_ingame, this)); } + + return pointer_update || (handler_result & HANDLER_UPDATE); } @@ -474,9 +938,9 @@ void mame_ui_manager::update_and_render(render_container &container) render_font *mame_ui_manager::get_font() { // allocate the font and messagebox string - if (m_font == nullptr) + if (!m_font) m_font = machine().render().font_alloc(machine().options().ui_font()); - return m_font; + return m_font.get(); } @@ -485,19 +949,16 @@ render_font *mame_ui_manager::get_font() // of a line //------------------------------------------------- -float mame_ui_manager::get_line_height() +float mame_ui_manager::get_line_height(float scale) { - int32_t raw_font_pixel_height = get_font()->pixel_height(); - render_target &ui_target = machine().render().ui_target(); - int32_t target_pixel_height = ui_target.height(); - float one_to_one_line_height; - float scale_factor; + int32_t const raw_font_pixel_height = get_font()->pixel_height(); + float target_pixel_height = machine().render().ui_target().height(); // compute the font pixel height at the nominal size - one_to_one_line_height = (float)raw_font_pixel_height / (float)target_pixel_height; + float const one_to_one_line_height = float(raw_font_pixel_height) / target_pixel_height; // determine the scale factor - scale_factor = target_font_height() / one_to_one_line_height; + float scale_factor = target_font_height() * scale / one_to_one_line_height; // if our font is small-ish, do integral scaling if (raw_font_pixel_height < 24) @@ -508,17 +969,17 @@ float mame_ui_manager::get_line_height() if (one_to_one_line_height < UI_MAX_FONT_HEIGHT || raw_font_pixel_height < 12) scale_factor = 1.0f; } - - // otherwise, just ensure an integral scale factor else - scale_factor = floor(scale_factor); + { + // otherwise, just ensure an integral scale factor + scale_factor = floorf(scale_factor); + } } - - // otherwise, just make sure we hit an even number of pixels else { - int32_t height = scale_factor * one_to_one_line_height * (float)target_pixel_height; - scale_factor = (float)height / (one_to_one_line_height * (float)target_pixel_height); + // otherwise, just make sure we hit an even number of pixels + int32_t height = scale_factor * one_to_one_line_height * target_pixel_height; + scale_factor = float(height) / (one_to_one_line_height * target_pixel_height); } return scale_factor * one_to_one_line_height; @@ -541,9 +1002,14 @@ float mame_ui_manager::get_char_width(char32_t ch) // character string //------------------------------------------------- -float mame_ui_manager::get_string_width(const char *s, float text_size) +float mame_ui_manager::get_string_width(std::string_view s) +{ + return get_string_width(s, get_line_height()); +} + +float mame_ui_manager::get_string_width(std::string_view s, float text_size) { - return get_font()->utf8string_width(get_line_height() * text_size, machine().render().ui_aspect(), s); + return get_font()->utf8string_width(text_size, machine().render().ui_aspect(), s); } @@ -579,9 +1045,14 @@ void mame_ui_manager::draw_outlined_box(render_container &container, float x0, f // draw_text - simple text renderer //------------------------------------------------- -void mame_ui_manager::draw_text(render_container &container, const char *buf, float x, float y) +void mame_ui_manager::draw_text(render_container &container, std::string_view buf, float x, float y) { - draw_text_full(container, buf, x, y, 1.0f - x, ui::text_layout::LEFT, ui::text_layout::WORD, mame_ui_manager::NORMAL, colors().text_color(), colors().text_bg_color(), nullptr, nullptr); + draw_text_full( + container, + buf, + x, y, 1.0f - x, + ui::text_layout::text_justify::LEFT, ui::text_layout::word_wrapping::WORD, + mame_ui_manager::NORMAL, colors().text_color(), colors().text_bg_color(), nullptr, nullptr); } @@ -591,17 +1062,43 @@ void mame_ui_manager::draw_text(render_container &container, const char *buf, fl // and full size computation //------------------------------------------------- -void mame_ui_manager::draw_text_full(render_container &container, const char *origs, float x, float y, float origwrapwidth, ui::text_layout::text_justify justify, ui::text_layout::word_wrapping wrap, draw_mode draw, rgb_t fgcolor, rgb_t bgcolor, float *totalwidth, float *totalheight, float text_size) +void mame_ui_manager::draw_text_full( + render_container &container, + std::string_view origs, + float x, float y, float origwrapwidth, + ui::text_layout::text_justify justify, ui::text_layout::word_wrapping wrap, + draw_mode draw, rgb_t fgcolor, rgb_t bgcolor, + float *totalwidth, float *totalheight) +{ + draw_text_full( + container, + origs, + x, y, origwrapwidth, + justify, wrap, + draw, fgcolor, bgcolor, + totalwidth, totalheight, + get_line_height()); +} + +void mame_ui_manager::draw_text_full( + render_container &container, + std::string_view origs, + float x, float y, float origwrapwidth, + ui::text_layout::text_justify justify, ui::text_layout::word_wrapping wrap, + draw_mode draw, rgb_t fgcolor, rgb_t bgcolor, + float *totalwidth, float *totalheight, + float text_size) { // create the layout - auto layout = create_layout(container, origwrapwidth, justify, wrap); + ui::text_layout layout( + *get_font(), machine().render().ui_aspect(&container) * text_size, text_size, + origwrapwidth, justify, wrap); // append text to it layout.add_text( origs, fgcolor, - draw == OPAQUE_ ? bgcolor : rgb_t::transparent(), - text_size); + (draw == OPAQUE_) ? bgcolor : rgb_t::transparent()); // and emit it (if we are asked to do so) if (draw != NONE) @@ -620,10 +1117,10 @@ void mame_ui_manager::draw_text_full(render_container &container, const char *or // message with a box around it //------------------------------------------------- -void mame_ui_manager::draw_text_box(render_container &container, const char *text, ui::text_layout::text_justify justify, float xpos, float ypos, rgb_t backcolor) +void mame_ui_manager::draw_text_box(render_container &container, std::string_view text, ui::text_layout::text_justify justify, float xpos, float ypos, rgb_t backcolor) { // cap the maximum width - float maximum_width = 1.0f - box_lr_border() * 2; + float maximum_width = 1.0f - (box_lr_border() * machine().render().ui_aspect(&container) * 2.0f); // create a layout ui::text_layout layout = create_layout(container, maximum_width, justify); @@ -644,18 +1141,19 @@ void mame_ui_manager::draw_text_box(render_container &container, const char *tex void mame_ui_manager::draw_text_box(render_container &container, ui::text_layout &layout, float xpos, float ypos, rgb_t backcolor) { // xpos and ypos are where we want to "pin" the layout, but we need to adjust for the actual size of the payload - auto actual_left = layout.actual_left(); - auto actual_width = layout.actual_width(); - auto actual_height = layout.actual_height(); - auto x = std::min(std::max(xpos - actual_width / 2, box_lr_border()), 1.0f - actual_width - box_lr_border()); - auto y = std::min(std::max(ypos - actual_height / 2, box_tb_border()), 1.0f - actual_height - box_tb_border()); + auto const lrborder = box_lr_border() * machine().render().ui_aspect(&container); + auto const actual_left = layout.actual_left(); + auto const actual_width = layout.actual_width(); + auto const actual_height = layout.actual_height(); + auto const x = std::clamp(xpos - actual_width / 2, lrborder, 1.0f - actual_width - lrborder); + auto const y = std::clamp(ypos - actual_height / 2, box_tb_border(), 1.0f - actual_height - box_tb_border()); // add a box around that - draw_outlined_box(container, - x - box_lr_border(), - y - box_tb_border(), - x + actual_width + box_lr_border(), - y + actual_height + box_tb_border(), backcolor); + draw_outlined_box( + container, + x - lrborder, y - box_tb_border(), + x + actual_width + lrborder, y + actual_height + box_tb_border(), + backcolor); // emit the text layout.emit(container, x - actual_left, y); @@ -667,7 +1165,7 @@ void mame_ui_manager::draw_text_box(render_container &container, ui::text_layout // message with a box around it //------------------------------------------------- -void mame_ui_manager::draw_message_window(render_container &container, const char *text) +void mame_ui_manager::draw_message_window(render_container &container, std::string_view text) { draw_text_box(container, text, ui::text_layout::text_justify::LEFT, 0.5f, 0.5f, colors().background_color()); } @@ -681,7 +1179,7 @@ void mame_ui_manager::draw_message_window(render_container &container, const cha void mame_ui_manager::show_fps_temp(double seconds) { if (!m_showfps) - m_showfps_end = osd_ticks() + seconds * osd_ticks_per_second(); + m_showfps_end = std::max<osd_ticks_t>(osd_ticks() + seconds * osd_ticks_per_second(), m_showfps_end); } @@ -693,10 +1191,7 @@ void mame_ui_manager::set_show_fps(bool show) { m_showfps = show; if (!show) - { - m_showfps = 0; m_showfps_end = 0; - } } @@ -717,7 +1212,7 @@ bool mame_ui_manager::show_fps() const bool mame_ui_manager::show_fps_counter() { - bool result = m_showfps || osd_ticks() < m_showfps_end; + bool const result = m_showfps || (osd_ticks() < m_showfps_end); if (!result) m_showfps_end = 0; return result; @@ -752,18 +1247,13 @@ bool mame_ui_manager::show_profiler() const void mame_ui_manager::show_menu() { - using namespace std::placeholders; - set_handler(ui_callback_type::MENU, std::bind(&ui::menu::ui_handler, _1, std::ref(*this))); -} - - -//------------------------------------------------- -// show_mouse - change mouse status -//------------------------------------------------- + for (auto *target = machine().render().first_target(); target; target = target->next()) + { + if (!target->hidden()) + target->forget_pointers(); + } -void mame_ui_manager::show_mouse(bool status) -{ - m_mouse_show = status; + set_handler(ui_callback_type::MENU, ui::menu::get_ui_handler(*this)); } @@ -772,10 +1262,9 @@ void mame_ui_manager::show_mouse(bool status) // UI handler is active //------------------------------------------------- -bool mame_ui_manager::is_menu_active(void) +bool mame_ui_manager::is_menu_active() { - return m_handler_callback_type == ui_callback_type::MENU - || m_handler_callback_type == ui_callback_type::VIEWER; + return m_handler_callback_type == ui_callback_type::MENU; } @@ -785,93 +1274,113 @@ bool mame_ui_manager::is_menu_active(void) ***************************************************************************/ //------------------------------------------------- -// handler_messagebox - displays the current -// messagebox_text string but handles no input -//------------------------------------------------- - -uint32_t mame_ui_manager::handler_messagebox(render_container &container) -{ - draw_text_box(container, messagebox_text.c_str(), ui::text_layout::LEFT, 0.5f, 0.5f, messagebox_backcolor); - return 0; -} - - -//------------------------------------------------- -// handler_messagebox_anykey - displays the -// current messagebox_text string and waits for -// any keypress +// process_ui_events - processes queued UI input +// events //------------------------------------------------- -uint32_t mame_ui_manager::handler_messagebox_anykey(render_container &container) +void mame_ui_manager::process_ui_events() { - uint32_t state = 0; - - // draw a standard message window - draw_text_box(container, messagebox_text.c_str(), ui::text_layout::LEFT, 0.5f, 0.5f, messagebox_backcolor); - - // if the user cancels, exit out completely - if (machine().ui_input().pressed(IPT_UI_CANCEL)) + // process UI events + bool const use_natkbd(machine().natkeyboard().in_use() && (machine().phase() == machine_phase::RUNNING)); + ui_event event; + while (machine().ui_input().pop_event(&event)) { - machine().schedule_exit(); - state = UI_HANDLER_CANCEL; - } - - // if any key is pressed, just exit - else if (machine().input().poll_switches() != INPUT_CODE_INVALID) - state = UI_HANDLER_CANCEL; - - return state; -} + switch (event.event_type) + { + case ui_event::type::NONE: + case ui_event::type::WINDOW_FOCUS: + case ui_event::type::WINDOW_DEFOCUS: + case ui_event::type::MOUSE_WHEEL: + break; + case ui_event::type::POINTER_UPDATE: + if (event.target) + { + if (osd::ui_event_handler::pointer::TOUCH != event.pointer_type) + { + auto pos(std::lower_bound(m_active_pointers.begin(), m_active_pointers.end(), std::make_pair(event.target, event.pointer_id))); + if ((m_active_pointers.end() == pos) || (pos->target != event.target) || (pos->ptrid != event.pointer_id)) + pos = m_active_pointers.emplace(pos, event); + else + assert(pos->type == event.pointer_type); + pos->updated = std::chrono::steady_clock::now(); + event.target->map_point_container(event.pointer_x, event.pointer_y, *event.target->ui_container(), pos->x, pos->y); + } + + event.target->pointer_updated( + event.pointer_type, event.pointer_id, event.pointer_device, + event.pointer_x, event.pointer_y, + event.pointer_buttons, event.pointer_pressed, event.pointer_released, + event.pointer_clicks); + } + break; -//------------------------------------------------- -// process_natural_keyboard - processes any -// natural keyboard input -//------------------------------------------------- + case ui_event::type::POINTER_LEAVE: + if (event.target) + { + auto const pos(std::lower_bound(m_active_pointers.begin(), m_active_pointers.end(), std::make_pair(event.target, event.pointer_id))); + if (m_active_pointers.end() != pos) + m_active_pointers.erase(pos); + + event.target->pointer_left( + event.pointer_type, event.pointer_id, event.pointer_device, + event.pointer_x, event.pointer_y, + event.pointer_released, + event.pointer_clicks); + } + break; -void mame_ui_manager::process_natural_keyboard() -{ - ui_event event; - int i, pressed; - input_item_id itemid; - input_code code; - uint8_t *key_down_ptr; - uint8_t key_down_mask; + case ui_event::type::POINTER_ABORT: + if (event.target) + { + auto const pos(std::lower_bound(m_active_pointers.begin(), m_active_pointers.end(), std::make_pair(event.target, event.pointer_id))); + if (m_active_pointers.end() != pos) + m_active_pointers.erase(pos); + + event.target->pointer_aborted( + event.pointer_type, event.pointer_id, event.pointer_device, + event.pointer_x, event.pointer_y, + event.pointer_released, + event.pointer_clicks); + } + break; - // loop while we have interesting events - while (machine().ui_input().pop_event(&event)) - { - // if this was a UI_EVENT_CHAR event, post it - if (event.event_type == ui_event::IME_CHAR) - machine().ioport().natkeyboard().post(event.ch); + case ui_event::type::IME_CHAR: + if (use_natkbd) + machine().natkeyboard().post_char(event.ch); + break; + } } - // process natural keyboard keys that don't get UI_EVENT_CHARs - for (i = 0; i < ARRAY_LENGTH(non_char_keys); i++) + // process natural keyboard keys that don't get IME text input events + if (use_natkbd) { - // identify this keycode - itemid = non_char_keys[i]; - code = machine().input().code_from_itemid(itemid); + for (int i = 0; i < std::size(non_char_keys); i++) + { + // identify this keycode + input_item_id itemid = non_char_keys[i]; + input_code code = machine().input().code_from_itemid(itemid); - // ...and determine if it is pressed - pressed = machine().input().code_pressed(code); + // ...and determine if it is pressed + bool pressed = machine().input().code_pressed(code); - // figure out whey we are in the key_down map - key_down_ptr = &m_non_char_keys_down[i / 8]; - key_down_mask = 1 << (i % 8); + // figure out whey we are in the key_down map + uint8_t *key_down_ptr = &m_non_char_keys_down[i / 8]; + uint8_t key_down_mask = 1 << (i % 8); - if (pressed && !(*key_down_ptr & key_down_mask)) - { - // this key is now down - *key_down_ptr |= key_down_mask; + if (pressed && !(*key_down_ptr & key_down_mask)) + { + // this key is now down + *key_down_ptr |= key_down_mask; - // post the key - machine().ioport().natkeyboard().post(UCHAR_MAMEKEY_BEGIN + code.item_id()); - } - else if (!pressed && (*key_down_ptr & key_down_mask)) - { - // this key is now up - *key_down_ptr &= ~key_down_mask; + // post the key + machine().natkeyboard().post_char(UCHAR_MAMEKEY_BEGIN + code.item_id()); + } + else if (!pressed && (*key_down_ptr & key_down_mask)) + { + // this key is now up + *key_down_ptr &= ~key_down_mask; + } } } } @@ -918,6 +1427,7 @@ void mame_ui_manager::decrease_frameskip() bool mame_ui_manager::can_paste() { // check to see if the clipboard is not empty + // FIXME: this is expensive - need a cheaper way to check if clipboard contains suitable content return !osd_get_clipboard_text().empty(); } @@ -928,32 +1438,12 @@ bool mame_ui_manager::can_paste() void mame_ui_manager::draw_fps_counter(render_container &container) { - draw_text_full(container, machine().video().speed_text().c_str(), 0.0f, 0.0f, 1.0f, - ui::text_layout::RIGHT, ui::text_layout::WORD, OPAQUE_, rgb_t::white(), rgb_t::black(), nullptr, nullptr); -} - - -//------------------------------------------------- -// draw_timecode_counter -//------------------------------------------------- - -void mame_ui_manager::draw_timecode_counter(render_container &container) -{ - std::string tempstring; - draw_text_full(container, machine().video().timecode_text(tempstring).c_str(), 0.0f, 0.0f, 1.0f, - ui::text_layout::RIGHT, ui::text_layout::WORD, OPAQUE_, rgb_t(0xf0, 0xf0, 0x10, 0x10), rgb_t::black(), nullptr, nullptr); -} - - -//------------------------------------------------- -// draw_timecode_total -//------------------------------------------------- - -void mame_ui_manager::draw_timecode_total(render_container &container) -{ - std::string tempstring; - draw_text_full(container, machine().video().timecode_total_text(tempstring).c_str(), 0.0f, 0.0f, 1.0f, - ui::text_layout::LEFT, ui::text_layout::WORD, OPAQUE_, rgb_t(0xf0, 0x10, 0xf0, 0x10), rgb_t::black(), nullptr, nullptr); + draw_text_full( + container, + machine().video().speed_text(), + 0.0f, 0.0f, 1.0f, + ui::text_layout::text_justify::RIGHT, ui::text_layout::word_wrapping::WORD, + OPAQUE_, rgb_t::white(), rgb_t::black(), nullptr, nullptr); } @@ -963,32 +1453,13 @@ void mame_ui_manager::draw_timecode_total(render_container &container) void mame_ui_manager::draw_profiler(render_container &container) { - const char *text = g_profiler.text(machine()); - draw_text_full(container, text, 0.0f, 0.0f, 1.0f, ui::text_layout::LEFT, ui::text_layout::WORD, OPAQUE_, rgb_t::white(), rgb_t::black(), nullptr, nullptr); -} - - -//------------------------------------------------- -// start_save_state -//------------------------------------------------- - -void mame_ui_manager::start_save_state() -{ - ui::menu::stack_reset(machine()); - show_menu(); - ui::menu::stack_push<ui::menu_save_state>(*this, machine().render().ui_container()); -} - - -//------------------------------------------------- -// start_load_state -//------------------------------------------------- - -void mame_ui_manager::start_load_state() -{ - ui::menu::stack_reset(machine()); - show_menu(); - ui::menu::stack_push<ui::menu_load_state>(*this, machine().render().ui_container()); + std::string_view text = g_profiler.text(machine()); + draw_text_full( + container, + text, + 0.0f, 0.0f, 1.0f, + ui::text_layout::text_justify::LEFT, ui::text_layout::word_wrapping::WORD, + OPAQUE_, rgb_t::white(), rgb_t::black(), nullptr, nullptr); } @@ -1000,17 +1471,17 @@ void mame_ui_manager::start_load_state() void mame_ui_manager::image_handler_ingame() { // run display routine for devices - if (machine().phase() == machine_phase::RUNNING) + if (m_image_display_enabled && machine().phase() == machine_phase::RUNNING) { auto layout = create_layout(machine().render().ui_container()); // loop through all devices, build their text into the layout - for (device_image_interface &image : image_interface_iterator(machine().root_device())) + for (device_image_interface &image : image_interface_enumerator(machine().root_device())) { std::string str = image.call_display(); if (!str.empty()) { - layout.add_text(str.c_str()); + layout.add_text(str); layout.add_text("\n"); } } @@ -1032,19 +1503,10 @@ void mame_ui_manager::image_handler_ingame() uint32_t mame_ui_manager::handler_ingame(render_container &container) { - bool is_paused = machine().paused(); - - // first draw the FPS counter - if (show_fps_counter()) - draw_fps_counter(container); - - // Show the duration of current part (intro or gameplay or extra) - if (show_timecode_counter()) - draw_timecode_counter(container); + // let the OSD do its thing first + machine().osd().check_osd_inputs(); - // Show the total time elapsed for the video preview (all parts intro, gameplay, extras) - if (show_timecode_total()) - draw_timecode_total(container); + bool is_paused = machine().paused(); // draw the profiler if visible if (show_profiler()) @@ -1058,8 +1520,8 @@ uint32_t mame_ui_manager::handler_ingame(render_container &container) } // determine if we should disable the rest of the UI - bool has_keyboard = machine_info().has_keyboard(); - bool ui_disabled = (has_keyboard && !machine().ui_active()); + bool const has_keyboard = machine_info().has_keyboard(); + bool const ui_disabled = !ui_active(); // is ScrLk UI toggling applicable here? if (has_keyboard) @@ -1068,50 +1530,54 @@ uint32_t mame_ui_manager::handler_ingame(render_container &container) if (machine().ui_input().pressed(IPT_UI_TOGGLE_UI)) { // toggle the UI - machine().set_ui_active(!machine().ui_active()); + set_ui_active(!ui_active()); // display a popup indicating the new status - if (machine().ui_active()) - { - popup_time(2, "%s\n%s\n%s\n%s\n%s\n%s\n", - _("Keyboard Emulation Status"), - "-------------------------", - _("Mode: PARTIAL Emulation"), - _("UI: Enabled"), - "-------------------------", - _("**Use ScrLock to toggle**")); - } + std::string const name = get_general_input_setting(IPT_UI_TOGGLE_UI); + if (ui_active()) + popup_time(2, _("UI controls enabled\nUse %1$s to toggle"), name); else - { - popup_time(2, "%s\n%s\n%s\n%s\n%s\n%s\n", - _("Keyboard Emulation Status"), - "-------------------------", - _("Mode: FULL Emulation"), - _("UI: Disabled"), - "-------------------------", - _("**Use ScrLock to toggle**")); - } + popup_time(2, _("UI controls disabled\nUse %1$s to toggle"), name); } } - // is the natural keyboard enabled? - if (machine().ioport().natkeyboard().in_use() && (machine().phase() == machine_phase::RUNNING)) - process_natural_keyboard(); + // process UI events and update pointers if necessary + process_ui_events(); + display_pointer_vector pointers; + pointers.reserve(m_active_pointers.size()); + auto const now(std::chrono::steady_clock::now()); + auto expiry(now); + render_target *target(nullptr); + layout_view const *view(nullptr); + bool hide_inactive(true); + for (auto const &pointer : m_active_pointers) + { + if (pointer.target != target) + { + target = pointer.target; + view = &target->current_view(); + hide_inactive = m_pointer_options[target->index()].hide_inactive() && view->hide_inactive_pointers(); + expiry = now - m_pointer_options[target->index()].timeout(); + } + if (view->show_pointers()) + { + if (!hide_inactive || (osd::ui_event_handler::pointer::PEN == pointer.type) || (pointer.updated >= expiry)) + pointers.emplace_back(display_pointer{ *target, pointer.type, pointer.x, pointer.y }); + } + } + set_pointers(pointers.begin(), pointers.end()); if (!ui_disabled) { // paste command if (machine().ui_input().pressed(IPT_UI_PASTE)) - machine().ioport().natkeyboard().paste(); + machine().natkeyboard().paste(); } image_handler_ingame(); - // handle a save input timecode request - if (machine().ui_input().pressed(IPT_UI_TIMECODE)) - machine().video().save_input_timecode(); - - if (ui_disabled) return ui_disabled; + if (ui_disabled) + return 0; if (machine().ui_input().pressed(IPT_UI_CANCEL)) { @@ -1120,18 +1586,18 @@ uint32_t mame_ui_manager::handler_ingame(render_container &container) } // turn on menus if requested - if (machine().ui_input().pressed(IPT_UI_CONFIGURE)) + if (machine().ui_input().pressed(IPT_UI_MENU)) { show_menu(); return 0; } // if the on-screen display isn't up and the user has toggled it, turn it on - if ((machine().debug_flags & DEBUG_FLAG_ENABLED) == 0 && machine().ui_input().pressed(IPT_UI_ON_SCREEN_DISPLAY)) + if (!(machine().debug_flags & DEBUG_FLAG_ENABLED) && machine().ui_input().pressed(IPT_UI_ON_SCREEN_DISPLAY)) { - using namespace std::placeholders; - set_handler(ui_callback_type::MENU, std::bind(&ui::menu_sliders::ui_handler, _1, std::ref(*this))); - return 1; + ui::menu::stack_push<ui::menu_sliders>(*this, machine().render().ui_container(), true); + show_menu(); + return 0; } // handle a reset request @@ -1143,17 +1609,29 @@ uint32_t mame_ui_manager::handler_ingame(render_container &container) // handle a request to display graphics/palette if (machine().ui_input().pressed(IPT_UI_SHOW_GFX)) { + for (auto *target = machine().render().first_target(); target; target = target->next()) + { + if (!target->hidden()) + target->forget_pointers(); + } + if (!is_paused) machine().pause(); using namespace std::placeholders; - set_handler(ui_callback_type::VIEWER, std::bind(&ui_gfx_ui_handler, _1, std::ref(*this), is_paused)); - return is_paused ? 1 : 0; + set_handler( + ui_callback_type::MENU, + handler_callback_func( + [this, is_paused] (render_container &container) -> uint32_t + { + return ui_gfx_ui_handler(container, *this, is_paused); + })); + return 0; } // handle a tape control key if (machine().ui_input().pressed(IPT_UI_TAPE_START)) { - for (cassette_image_device &cass : cassette_device_iterator(machine().root_device())) + for (cassette_image_device &cass : cassette_device_enumerator(machine().root_device())) { cass.change_state(CASSETTE_PLAY, CASSETTE_MASK_UISTATE); return 0; @@ -1161,7 +1639,7 @@ uint32_t mame_ui_manager::handler_ingame(render_container &container) } if (machine().ui_input().pressed(IPT_UI_TAPE_STOP)) { - for (cassette_image_device &cass : cassette_device_iterator(machine().root_device())) + for (cassette_image_device &cass : cassette_device_enumerator(machine().root_device())) { cass.change_state(CASSETTE_STOPPED, CASSETTE_MASK_UISTATE); return 0; @@ -1171,15 +1649,31 @@ uint32_t mame_ui_manager::handler_ingame(render_container &container) // handle a save state request if (machine().ui_input().pressed(IPT_UI_SAVE_STATE)) { - start_save_state(); - return LOADSAVE_SAVE; + ui::menu::stack_push<ui::menu_save_state>(*this, machine().render().ui_container(), true); + show_menu(); + return 0; } // handle a load state request if (machine().ui_input().pressed(IPT_UI_LOAD_STATE)) { - start_load_state(); - return LOADSAVE_LOAD; + ui::menu::stack_push<ui::menu_load_state>(*this, machine().render().ui_container(), true); + show_menu(); + return 0; + } + + // handle a quick save state request + if (machine().ui_input().pressed(IPT_UI_SAVE_STATE_QUICK)) + { + machine().schedule_save("quick"); + return 0; + } + + // handle a quick load state request + if (machine().ui_input().pressed(IPT_UI_LOAD_STATE_QUICK)) + { + machine().schedule_load("quick"); + return 0; } // handle a save snapshot request @@ -1204,15 +1698,15 @@ uint32_t mame_ui_manager::handler_ingame(render_container &container) // handle a toggle cheats request if (machine().ui_input().pressed(IPT_UI_TOGGLE_CHEAT)) - 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); // toggle MNG recording if (machine().ui_input().pressed(IPT_UI_RECORD_MNG)) - machine().video().toggle_record_mng(); + machine().video().toggle_record_movie(movie_recording::format::MNG); - // toggle MNG recording + // toggle AVI recording if (machine().ui_input().pressed(IPT_UI_RECORD_AVI)) - machine().video().toggle_record_avi(); + machine().video().toggle_record_movie(movie_recording::format::AVI); // toggle profiler display if (machine().ui_input().pressed(IPT_UI_SHOW_PROFILER)) @@ -1232,21 +1726,11 @@ uint32_t mame_ui_manager::handler_ingame(render_container &container) // toggle throttle? if (machine().ui_input().pressed(IPT_UI_THROTTLE)) - machine().video().toggle_throttle(); - - // toggle autofire - if (machine().ui_input().pressed(IPT_UI_TOGGLE_AUTOFIRE)) { - if (!machine().options().cheat()) - { - machine().popmessage(_("Autofire can't be enabled")); - } - else - { - bool autofire_toggle = machine().ioport().get_autofire_toggle(); - machine().ioport().set_autofire_toggle(!autofire_toggle); - machine().popmessage("Autofire %s", autofire_toggle ? _("Enabled") : _("Disabled")); - } + const bool new_throttle_state = !machine().video().throttled(); + machine().video().set_throttled(new_throttle_state); + if (m_unthrottle_mute) + machine().sound().ui_mute(!new_throttle_state); } // check for fast forward @@ -1268,89 +1752,99 @@ uint32_t mame_ui_manager::handler_ingame(render_container &container) void mame_ui_manager::request_quit() { - using namespace std::placeholders; if (!machine().options().confirm_quit()) + { machine().schedule_exit(); + } else - set_handler(ui_callback_type::GENERAL, std::bind(&mame_ui_manager::handler_confirm_quit, this, _1)); + { + ui::menu::stack_push<ui::menu_confirm_quit>(*this, machine().render().ui_container()); + show_menu(); + } } //------------------------------------------------- -// handler_confirm_quit - leads the user through -// confirming quit emulation +// set_pointer_activity_timeout - set per-target +// pointer activity timeout //------------------------------------------------- -uint32_t mame_ui_manager::handler_confirm_quit(render_container &container) +void mame_ui_manager::set_pointer_activity_timeout(int target, std::chrono::steady_clock::duration timeout) noexcept { - uint32_t state = 0; - - // get the text for 'UI Select' - std::string ui_select_text = machine().input().seq_name(machine().ioport().type_seq(IPT_UI_SELECT, 0, SEQ_TYPE_STANDARD)); + assert((0 <= target) && (m_pointer_options.size() > target)); + if ((0 <= target) && (m_pointer_options.size() > target)) + m_pointer_options[target].set_timeout(timeout); +} - // get the text for 'UI Cancel' - std::string ui_cancel_text = machine().input().seq_name(machine().ioport().type_seq(IPT_UI_CANCEL, 0, SEQ_TYPE_STANDARD)); - // assemble the quit message - std::string quit_message = string_format(_("Are you sure you want to quit?\n\n" - "Press ''%1$s'' to quit,\n" - "Press ''%2$s'' to return to emulation."), - ui_select_text, - ui_cancel_text); +//------------------------------------------------- +// set_hide_inactive_pointers - set per-target +// hide inactive pointers setting +//------------------------------------------------- - draw_text_box(container, quit_message.c_str(), ui::text_layout::CENTER, 0.5f, 0.5f, UI_RED_COLOR); - machine().pause(); +void mame_ui_manager::set_hide_inactive_pointers(int target, bool hide) noexcept +{ + assert((0 <= target) && (m_pointer_options.size() > target)); + if ((0 <= target) && (m_pointer_options.size() > target)) + m_pointer_options[target].set_hide_inactive(hide); +} - // if the user press ENTER, quit the game - if (machine().ui_input().pressed(IPT_UI_SELECT)) - machine().schedule_exit(); - // if the user press ESC, just continue - else if (machine().ui_input().pressed(IPT_UI_CANCEL)) - { - machine().resume(); - state = UI_HANDLER_CANCEL; - } +//------------------------------------------------- +// restore_initial_pointer_options - restore +// initial per-target pointer settings +//------------------------------------------------- - return state; +void mame_ui_manager::restore_initial_pointer_options(int target) noexcept +{ + assert((0 <= target) && (m_pointer_options.size() > target)); + if ((0 <= target) && (m_pointer_options.size() > target)) + m_pointer_options[target].restore_initial(); } -/*************************************************************************** - SLIDER CONTROLS -***************************************************************************/ - //------------------------------------------------- -// ui_get_slider_list - get the list of sliders +// pointer_activity_timeout - get per-target +// pointer activity timeout //------------------------------------------------- -std::vector<ui::menu_item>& mame_ui_manager::get_slider_list(void) +std::chrono::steady_clock::duration mame_ui_manager::pointer_activity_timeout(int target) const noexcept { - return slider_list; + assert((0 <= target) && (m_pointer_options.size() > target)); + if ((0 <= target) && (m_pointer_options.size() > target)) + return m_pointer_options[target].timeout(); + else + return pointer_options().timeout(); } //------------------------------------------------- -// slider_alloc - allocate a new slider entry +// hide_inactive_pointers - get per-target hide +// inactive pointers setting //------------------------------------------------- -std::unique_ptr<slider_state> mame_ui_manager::slider_alloc(int id, const char *title, int32_t minval, int32_t defval, int32_t maxval, int32_t incval, void *arg) +bool mame_ui_manager::hide_inactive_pointers(int target) const noexcept { - auto state = make_unique_clear<slider_state>(); + assert((0 <= target) && (m_pointer_options.size() > target)); + if ((0 <= target) && (m_pointer_options.size() > target)) + return m_pointer_options[target].hide_inactive(); + else + return pointer_options().hide_inactive(); +} - state->minval = minval; - state->defval = defval; - state->maxval = maxval; - state->incval = incval; - using namespace std::placeholders; - state->update = std::bind(&mame_ui_manager::slider_changed, this, _1, _2, _3, _4, _5); - state->arg = arg; - state->id = id; - state->description = title; +/*************************************************************************** + SLIDER CONTROLS +***************************************************************************/ + +//------------------------------------------------- +// ui_get_slider_list - get the list of sliders +//------------------------------------------------- - return state; +std::vector<ui::menu_item> &mame_ui_manager::get_slider_list() +{ + return slider_list; } @@ -1361,139 +1855,131 @@ std::unique_ptr<slider_state> mame_ui_manager::slider_alloc(int id, const char * std::vector<ui::menu_item> mame_ui_manager::slider_init(running_machine &machine) { + using namespace std::placeholders; + m_sliders.clear(); // add overall volume - m_sliders.push_back(slider_alloc(SLIDER_ID_VOLUME, _("Master Volume"), -32, 0, 0, 1, nullptr)); + slider_alloc(_("Master Volume"), -960, 0, 120, 10, std::bind(&mame_ui_manager::slider_volume, this, _1, _2)); - // add per-channel volume - mixer_input info; - for (int item = 0; machine.sound().indexed_mixer_input(item, info); item++) + // add per-sound device and per-sound device channel volume + for (device_sound_interface &snd : sound_interface_enumerator(machine.root_device())) { - int32_t maxval = 2000; - int32_t defval = 1000; - - std::string str = string_format(_("%1$s Volume"), info.stream->input_name(info.inputnum)); - m_sliders.push_back(slider_alloc(SLIDER_ID_MIXERVOL + item, str.c_str(), 0, defval, maxval, 20, (void *)(uintptr_t)item)); + // Don't add microphones, speakers or devices without outputs + if (dynamic_cast<sound_io_device *>(&snd) || !snd.outputs()) + continue; + + slider_alloc(util::string_format(_("%1$s volume"), snd.device().tag()), -960, 0, 120, 10, std::bind(&mame_ui_manager::slider_devvol, this, &snd, _1, _2)); + if (snd.outputs() != 1) + for (int channel = 0; channel != snd.outputs(); channel ++) + slider_alloc(util::string_format(_("%1$s channel %d volume"), snd.device().tag(), channel), -960, 0, 120, 10, std::bind(&mame_ui_manager::slider_devvol_chan, this, &snd, channel, _1, _2)); } // add analog adjusters - int slider_index = 0; for (auto &port : machine.ioport().ports()) { for (ioport_field &field : port.second->fields()) { if (field.type() == IPT_ADJUSTER) { - m_sliders.push_back(slider_alloc(SLIDER_ID_ADJUSTER + slider_index++, field.name(), field.minval(), field.defvalue(), field.maxval(), 1, (void *)&field)); + slider_alloc(field.name(), field.minval(), field.defvalue(), field.maxval(), 1, std::bind(&mame_ui_manager::slider_adjuster, this, std::ref(field), _1, _2)); } } } // add CPU overclocking (cheat only) - slider_index = 0; if (machine.options().cheat()) { - for (device_execute_interface &exec : execute_interface_iterator(machine.root_device())) + for (device_execute_interface &exec : execute_interface_enumerator(machine.root_device())) { - void *param = (void *)&exec.device(); std::string str = string_format(_("Overclock CPU %1$s"), exec.device().tag()); - m_sliders.push_back(slider_alloc(SLIDER_ID_OVERCLOCK + slider_index++, str.c_str(), 10, 1000, 2000, 1, param)); + slider_alloc(std::move(str), 100, 1000, 4000, 10, std::bind(&mame_ui_manager::slider_overclock, this, std::ref(exec.device()), _1, _2)); } - for (device_sound_interface &snd : sound_interface_iterator(machine.root_device())) + for (device_sound_interface &snd : sound_interface_enumerator(machine.root_device())) { device_execute_interface *exec; if (!snd.device().interface(exec) && snd.device().unscaled_clock() != 0) { - void *param = (void *)&snd.device(); std::string str = string_format(_("Overclock %1$s sound"), snd.device().tag()); - m_sliders.push_back(slider_alloc(SLIDER_ID_OVERCLOCK + slider_index++, str.c_str(), 10, 1000, 2000, 1, param)); + slider_alloc(std::move(str), 100, 1000, 4000, 10, std::bind(&mame_ui_manager::slider_overclock, this, std::ref(snd.device()), _1, _2)); } } } // add screen parameters - screen_device_iterator scriter(machine.root_device()); - slider_index = 0; + screen_device_enumerator scriter(machine.root_device()); for (screen_device &screen : scriter) { - int defxscale = floor(screen.xscale() * 1000.0f + 0.5f); - int defyscale = floor(screen.yscale() * 1000.0f + 0.5f); - int defxoffset = floor(screen.xoffset() * 1000.0f + 0.5f); - int defyoffset = floor(screen.yoffset() * 1000.0f + 0.5f); - void *param = (void *)&screen; + int defxscale = floorf(screen.xscale() * 1000.0f + 0.5f); + int defyscale = floorf(screen.yscale() * 1000.0f + 0.5f); + int defxoffset = floorf(screen.xoffset() * 1000.0f + 0.5f); + int defyoffset = floorf(screen.yoffset() * 1000.0f + 0.5f); std::string screen_desc = machine_info().get_screen_desc(screen); // add refresh rate tweaker if (machine.options().cheat()) { std::string str = string_format(_("%1$s Refresh Rate"), screen_desc); - m_sliders.push_back(slider_alloc(SLIDER_ID_REFRESH + slider_index, str.c_str(), -10000, 0, 10000, 1000, param)); + slider_alloc(std::move(str), -10000, 0, 10000, 1000, std::bind(&mame_ui_manager::slider_refresh, this, std::ref(screen), _1, _2)); } // add standard brightness/contrast/gamma controls per-screen std::string str = string_format(_("%1$s Brightness"), screen_desc); - m_sliders.push_back(slider_alloc(SLIDER_ID_BRIGHTNESS + slider_index, str.c_str(), 100, 1000, 2000, 10, param)); + slider_alloc(std::move(str), 100, 1000, 2000, 10, std::bind(&mame_ui_manager::slider_brightness, this, std::ref(screen), _1, _2)); str = string_format(_("%1$s Contrast"), screen_desc); - m_sliders.push_back(slider_alloc(SLIDER_ID_CONTRAST + slider_index, str.c_str(), 100, 1000, 2000, 50, param)); + slider_alloc(std::move(str), 100, 1000, 2000, 50, std::bind(&mame_ui_manager::slider_contrast, this, std::ref(screen), _1, _2)); str = string_format(_("%1$s Gamma"), screen_desc); - m_sliders.push_back(slider_alloc(SLIDER_ID_GAMMA + slider_index, str.c_str(), 100, 1000, 3000, 50, param)); + slider_alloc(std::move(str), 100, 1000, 3000, 50, std::bind(&mame_ui_manager::slider_gamma, this, std::ref(screen), _1, _2)); // add scale and offset controls per-screen str = string_format(_("%1$s Horiz Stretch"), screen_desc); - m_sliders.push_back(slider_alloc(SLIDER_ID_XSCALE + slider_index, str.c_str(), 500, defxscale, 1500, 2, param)); + slider_alloc(std::move(str), 500, defxscale, 1500, 2, std::bind(&mame_ui_manager::slider_xscale, this, std::ref(screen), _1, _2)); str = string_format(_("%1$s Horiz Position"), screen_desc); - m_sliders.push_back(slider_alloc(SLIDER_ID_XOFFSET + slider_index, str.c_str(), -500, defxoffset, 500, 2, param)); + slider_alloc(std::move(str), -500, defxoffset, 500, 2, std::bind(&mame_ui_manager::slider_xoffset, this, std::ref(screen), _1, _2)); str = string_format(_("%1$s Vert Stretch"), screen_desc); - m_sliders.push_back(slider_alloc(SLIDER_ID_YSCALE + slider_index, str.c_str(), 500, defyscale, 1500, 2, param)); + slider_alloc(std::move(str), 500, defyscale, 1500, 2, std::bind(&mame_ui_manager::slider_yscale, this, std::ref(screen), _1, _2)); str = string_format(_("%1$s Vert Position"), screen_desc); - m_sliders.push_back(slider_alloc(SLIDER_ID_YOFFSET + slider_index, str.c_str(), -500, defyoffset, 500, 2, param)); - slider_index++; + slider_alloc(std::move(str), -500, defyoffset, 500, 2, std::bind(&mame_ui_manager::slider_yoffset, this, std::ref(screen), _1, _2)); } - slider_index = 0; - for (laserdisc_device &laserdisc : laserdisc_device_iterator(machine.root_device())) + for (laserdisc_device &laserdisc : laserdisc_device_enumerator(machine.root_device())) { if (laserdisc.overlay_configured()) { laserdisc_overlay_config config; laserdisc.get_overlay_config(config); - int defxscale = floor(config.m_overscalex * 1000.0f + 0.5f); - int defyscale = floor(config.m_overscaley * 1000.0f + 0.5f); - int defxoffset = floor(config.m_overposx * 1000.0f + 0.5f); - int defyoffset = floor(config.m_overposy * 1000.0f + 0.5f); - void *param = (void *)&laserdisc; + int defxscale = floorf(config.m_overscalex * 1000.0f + 0.5f); + int defyscale = floorf(config.m_overscaley * 1000.0f + 0.5f); + int defxoffset = floorf(config.m_overposx * 1000.0f + 0.5f); + int defyoffset = floorf(config.m_overposy * 1000.0f + 0.5f); // add scale and offset controls per-overlay std::string str = string_format(_("Laserdisc '%1$s' Horiz Stretch"), laserdisc.tag()); - m_sliders.push_back(slider_alloc(SLIDER_ID_OVERLAY_XSCALE + slider_index, str.c_str(), 500, (defxscale == 0) ? 1000 : defxscale, 1500, 2, param)); + slider_alloc(std::move(str), 500, (defxscale == 0) ? 1000 : defxscale, 1500, 2, std::bind(&mame_ui_manager::slider_overxscale, this, std::ref(laserdisc), _1, _2)); str = string_format(_("Laserdisc '%1$s' Horiz Position"), laserdisc.tag()); - m_sliders.push_back(slider_alloc(SLIDER_ID_OVERLAY_YSCALE + slider_index, str.c_str(), -500, defxoffset, 500, 2, param)); + slider_alloc(std::move(str), -500, defxoffset, 500, 2, std::bind(&mame_ui_manager::slider_overxoffset, this, std::ref(laserdisc), _1, _2)); str = string_format(_("Laserdisc '%1$s' Vert Stretch"), laserdisc.tag()); - m_sliders.push_back(slider_alloc(SLIDER_ID_OVERLAY_XOFFSET + slider_index, str.c_str(), 500, (defyscale == 0) ? 1000 : defyscale, 1500, 2, param)); + slider_alloc(std::move(str), 500, (defyscale == 0) ? 1000 : defyscale, 1500, 2, std::bind(&mame_ui_manager::slider_overyscale, this, std::ref(laserdisc), _1, _2)); str = string_format(_("Laserdisc '%1$s' Vert Position"), laserdisc.tag()); - m_sliders.push_back(slider_alloc(SLIDER_ID_OVERLAY_YOFFSET + slider_index, str.c_str(), -500, defyoffset, 500, 2, param)); - slider_index++; + slider_alloc(std::move(str), -500, defyoffset, 500, 2, std::bind(&mame_ui_manager::slider_overyoffset, this, std::ref(laserdisc), _1, _2)); } } - slider_index = 0; for (screen_device &screen : scriter) { if (screen.screen_type() == SCREEN_TYPE_VECTOR) { - // add vector control - m_sliders.push_back(slider_alloc(SLIDER_ID_FLICKER + slider_index, _("Vector Flicker"), 0, 0, 1000, 10, nullptr)); - m_sliders.push_back(slider_alloc(SLIDER_ID_BEAM_WIDTH_MIN + slider_index, _("Beam Width Minimum"), 100, 100, 1000, 1, nullptr)); - m_sliders.push_back(slider_alloc(SLIDER_ID_BEAM_WIDTH_MAX + slider_index, _("Beam Width Maximum"), 100, 100, 1000, 1, nullptr)); - m_sliders.push_back(slider_alloc(SLIDER_ID_BEAM_INTENSITY + slider_index, _("Beam Intensity Weight"), -1000, 0, 1000, 10, nullptr)); - slider_index++; + // add vector control (FIXME: these should all be per-screen rather than global) + slider_alloc(_("Vector Flicker"), 0, 0, 1000, 10, std::bind(&mame_ui_manager::slider_flicker, this, std::ref(screen), _1, _2)); + slider_alloc(_("Beam Width Minimum"), 100, 100, 1000, 1, std::bind(&mame_ui_manager::slider_beam_width_min, this, std::ref(screen), _1, _2)); + slider_alloc(_("Beam Width Maximum"), 100, 100, 1000, 1, std::bind(&mame_ui_manager::slider_beam_width_max, this, std::ref(screen), _1, _2)); + slider_alloc(_("Beam Dot Size"), 100, 100, 1000, 1, std::bind(&mame_ui_manager::slider_beam_dot_size, this, std::ref(screen), _1, _2)); + slider_alloc(_("Beam Intensity Weight"), -1000, 0, 1000, 10, std::bind(&mame_ui_manager::slider_beam_intensity_weight, this, std::ref(screen), _1, _2)); break; } } #ifdef MAME_DEBUG - slider_index = 0; // add crosshair adjusters for (auto &port : machine.ioport().ports()) { @@ -1502,9 +1988,9 @@ std::vector<ui::menu_item> mame_ui_manager::slider_init(running_machine &machine if (field.crosshair_axis() != CROSSHAIR_AXIS_NONE && field.player() == 0) { std::string str = string_format(_("Crosshair Scale %1$s"), (field.crosshair_axis() == CROSSHAIR_AXIS_X) ? _("X") : _("Y")); - m_sliders.push_back(slider_alloc(SLIDER_ID_CROSSHAIR_SCALE + slider_index, str.c_str(), -3000, 1000, 3000, 100, (void *)&field)); + slider_alloc(std::move(str), -3000, 1000, 3000, 100, std::bind(&mame_ui_manager::slider_crossscale, this, std::ref(field), _1, _2)); str = string_format(_("Crosshair Offset %1$s"), (field.crosshair_axis() == CROSSHAIR_AXIS_X) ? _("X") : _("Y")); - m_sliders.push_back(slider_alloc(SLIDER_ID_CROSSHAIR_OFFSET + slider_index, str.c_str(), -3000, 0, 3000, 100, (void *)&field)); + slider_alloc(std::move(str), -3000, 0, 3000, 100, std::bind(&mame_ui_manager::slider_crossoffset, this, std::ref(field), _1, _2)); } } } @@ -1513,129 +1999,107 @@ std::vector<ui::menu_item> mame_ui_manager::slider_init(running_machine &machine std::vector<ui::menu_item> items; for (auto &slider : m_sliders) { - ui::menu_item item; - item.text = slider->description; - item.subtext = ""; - item.flags = 0; - item.ref = slider.get(); - item.type = ui::menu_item_type::SLIDER; - items.push_back(item); + ui::menu_item item(ui::menu_item_type::SLIDER, slider.get()); + item.set_text(slider->description); + items.emplace_back(std::move(item)); } return items; } -//---------------------------------------------------- -// slider_changed - global slider-modified callback -//---------------------------------------------------- - -int32_t mame_ui_manager::slider_changed(running_machine &machine, void *arg, int id, std::string *str, int32_t newval) -{ - if (id == SLIDER_ID_VOLUME) - return slider_volume(machine, arg, id, str, newval); - else if (id >= SLIDER_ID_MIXERVOL && id <= SLIDER_ID_MIXERVOL_LAST) - return slider_mixervol(machine, arg, id, str, newval); - else if (id >= SLIDER_ID_ADJUSTER && id <= SLIDER_ID_ADJUSTER_LAST) - return slider_adjuster(machine, arg, id, str, newval); - else if (id >= SLIDER_ID_OVERCLOCK && id <= SLIDER_ID_OVERCLOCK_LAST) - return slider_overclock(machine, arg, id, str, newval); - else if (id >= SLIDER_ID_REFRESH && id <= SLIDER_ID_REFRESH_LAST) - return slider_refresh(machine, arg, id, str, newval); - else if (id >= SLIDER_ID_BRIGHTNESS && id <= SLIDER_ID_BRIGHTNESS_LAST) - return slider_brightness(machine, arg, id, str, newval); - else if (id >= SLIDER_ID_CONTRAST && id <= SLIDER_ID_CONTRAST_LAST) - return slider_contrast(machine, arg, id, str, newval); - else if (id >= SLIDER_ID_GAMMA && id <= SLIDER_ID_GAMMA_LAST) - return slider_gamma(machine, arg, id, str, newval); - else if (id >= SLIDER_ID_XSCALE && id <= SLIDER_ID_XSCALE_LAST) - return slider_xscale(machine, arg, id, str, newval); - else if (id >= SLIDER_ID_YSCALE && id <= SLIDER_ID_YSCALE_LAST) - return slider_yscale(machine, arg, id, str, newval); - else if (id >= SLIDER_ID_XOFFSET && id <= SLIDER_ID_XOFFSET_LAST) - return slider_xoffset(machine, arg, id, str, newval); - else if (id >= SLIDER_ID_YOFFSET && id <= SLIDER_ID_YOFFSET_LAST) - return slider_yoffset(machine, arg, id, str, newval); - else if (id >= SLIDER_ID_OVERLAY_XSCALE && id <= SLIDER_ID_OVERLAY_XSCALE_LAST) - return slider_overxscale(machine, arg, id, str, newval); - else if (id >= SLIDER_ID_OVERLAY_YSCALE && id <= SLIDER_ID_OVERLAY_YSCALE_LAST) - return slider_overyscale(machine, arg, id, str, newval); - else if (id >= SLIDER_ID_OVERLAY_XOFFSET && id <= SLIDER_ID_OVERLAY_XOFFSET_LAST) - return slider_overxoffset(machine, arg, id, str, newval); - else if (id >= SLIDER_ID_OVERLAY_YOFFSET && id <= SLIDER_ID_OVERLAY_YOFFSET_LAST) - return slider_overyoffset(machine, arg, id, str, newval); - else if (id >= SLIDER_ID_FLICKER && id <= SLIDER_ID_FLICKER_LAST) - return slider_flicker(machine, arg, id, str, newval); - else if (id >= SLIDER_ID_BEAM_WIDTH_MIN && id <= SLIDER_ID_BEAM_WIDTH_MIN_LAST) - return slider_beam_width_min(machine, arg, id, str, newval); - else if (id >= SLIDER_ID_BEAM_WIDTH_MAX && id <= SLIDER_ID_BEAM_WIDTH_MAX_LAST) - return slider_beam_width_max(machine, arg, id, str, newval); - else if (id >= SLIDER_ID_BEAM_INTENSITY && id <= SLIDER_ID_BEAM_INTENSITY_LAST) - return slider_beam_intensity_weight(machine, arg, id, str, newval); -#ifdef MAME_DEBUG - else if (id >= SLIDER_ID_CROSSHAIR_SCALE && id <= SLIDER_ID_CROSSHAIR_SCALE_LAST) - return slider_crossscale(machine, arg, id, str, newval); - else if (id >= SLIDER_ID_CROSSHAIR_OFFSET && id <= SLIDER_ID_CROSSHAIR_OFFSET_LAST) - return slider_crossoffset(machine, arg, id, str, newval); -#endif - return 0; +//------------------------------------------------- +// slider_volume - global volume slider callback +//------------------------------------------------- + +int32_t mame_ui_manager::slider_volume(std::string *str, int32_t newval) +{ + if (newval != SLIDER_NOCHANGE) + machine().sound().set_master_gain(newval == -960 ? 0 : osd::db_to_linear(newval * 0.1f)); + + int curval = machine().sound().master_gain() == 0 ? -960 : floorf(osd::linear_to_db(machine().sound().master_gain()) * 10.0f + 0.5f); + + if (str) + { + if (curval == -960) + *str = _("Mute"); + else if (curval % 10) + *str = string_format(_(u8"%1$5.1f\u00a0dB"), float(curval) * 0.1f); + else + *str = string_format(_(u8"%1$3d\u00a0dB"), curval / 10); + } + return curval; } //------------------------------------------------- -// slider_volume - global volume slider callback +// slider_devvol - device volume +// slider callback //------------------------------------------------- -int32_t mame_ui_manager::slider_volume(running_machine &machine, void *arg, int id, std::string *str, int32_t newval) +int32_t mame_ui_manager::slider_devvol(device_sound_interface *snd, std::string *str, int32_t newval) { if (newval != SLIDER_NOCHANGE) - machine.sound().set_attenuation(newval); + snd->set_user_output_gain(newval == -960 ? 0 : osd::db_to_linear(newval * 0.1f)); + + int curval = snd->user_output_gain() == 0 ? -960 : floorf(osd::linear_to_db(snd->user_output_gain()) * 10.0f + 0.5f); + if (str) - *str = string_format(_("%1$3ddB"), machine.sound().attenuation()); - return machine.sound().attenuation(); + { + if (curval == -960) + *str = _("Mute"); + else if (curval % 10) + *str = string_format(_(u8"%1$5.1f\u00a0dB"), float(curval) * 0.1f); + else + *str = string_format(_(u8"%1$3d\u00a0dB"), curval / 10); + } + return curval; } //------------------------------------------------- -// slider_mixervol - single channel volume +// slider_devvol_chan - device channel volume // slider callback //------------------------------------------------- -int32_t mame_ui_manager::slider_mixervol(running_machine &machine, void *arg, int id, std::string *str, int32_t newval) +int32_t mame_ui_manager::slider_devvol_chan(device_sound_interface *snd, int channel, std::string *str, int32_t newval) { - mixer_input info; - if (!machine.sound().indexed_mixer_input((uintptr_t)arg, info)) - return 0; if (newval != SLIDER_NOCHANGE) + snd->set_user_output_gain(channel, newval == -960 ? 0 : osd::db_to_linear(newval * 0.1f)); + + int curval = snd->user_output_gain(channel) == 0 ? -960 : floorf(osd::linear_to_db(snd->user_output_gain(channel)) * 10.0f + 0.5f); + + if (str) { - int32_t curval = floor(info.stream->user_gain(info.inputnum) * 1000.0f + 0.5f); - if (newval > curval && (newval - curval) <= 4) newval += 4; // round up on increment - info.stream->set_user_gain(info.inputnum, (float)newval * 0.001f); + if (curval == -960) + *str = _("Mute"); + else if (curval % 10) + *str = string_format(_(u8"%1$5.1f\u00a0dB"), float(curval) * 0.1f); + else + *str = string_format(_(u8"%1$3d\u00a0dB"), curval / 10); } - if (str) - *str = string_format("%4.2f", info.stream->user_gain(info.inputnum)); - return floorf(info.stream->user_gain(info.inputnum) * 1000.0f + 0.5f); + return curval; } + //------------------------------------------------- // slider_adjuster - analog adjuster slider // callback //------------------------------------------------- -int32_t mame_ui_manager::slider_adjuster(running_machine &machine, void *arg, int id, std::string *str, int32_t newval) +int32_t mame_ui_manager::slider_adjuster(ioport_field &field, std::string *str, int32_t newval) { - ioport_field *field = (ioport_field *)arg; ioport_field::user_settings settings; - field->get_user_settings(settings); + field.get_user_settings(settings); if (newval != SLIDER_NOCHANGE) { settings.value = newval; - field->set_user_settings(settings); + field.set_user_settings(settings); } if (str) - *str = string_format(_("%1$d%%"), settings.value); + *str = string_format(_("%1$3d%%"), settings.value); return settings.value; } @@ -1645,14 +2109,21 @@ int32_t mame_ui_manager::slider_adjuster(running_machine &machine, void *arg, in // callback //------------------------------------------------- -int32_t mame_ui_manager::slider_overclock(running_machine &machine, void *arg, int id, std::string *str, int32_t newval) +int32_t mame_ui_manager::slider_overclock(device_t &device, std::string *str, int32_t newval) { - device_t *cpu = (device_t *)arg; if (newval != SLIDER_NOCHANGE) - cpu->set_clock_scale((float)newval * 0.001f); + device.set_clock_scale(double(newval) * 0.001); + + int32_t curval = floor(device.clock_scale() * 1000.0 + 0.5); if (str) - *str = string_format(_("%1$3.0f%%"), floor(cpu->clock_scale() * 100.0 + 0.5)); - return floor(cpu->clock_scale() * 1000.0 + 0.5); + { + if (curval % 10) + *str = string_format(_("%1$.1f%%"), float(curval) * 0.1f); + else + *str = string_format(_("%1$3d%%"), curval / 10); + } + + return curval; } @@ -1660,23 +2131,22 @@ int32_t mame_ui_manager::slider_overclock(running_machine &machine, void *arg, i // slider_refresh - refresh rate slider callback //------------------------------------------------- -int32_t mame_ui_manager::slider_refresh(running_machine &machine, void *arg, int id, std::string *str, int32_t newval) +int32_t mame_ui_manager::slider_refresh(screen_device &screen, std::string *str, int32_t newval) { - screen_device *screen = reinterpret_cast<screen_device *>(arg); - double defrefresh = ATTOSECONDS_TO_HZ(screen->refresh_attoseconds()); + double defrefresh = ATTOSECONDS_TO_HZ(screen.refresh_attoseconds()); double refresh; if (newval != SLIDER_NOCHANGE) { - int width = screen->width(); - int height = screen->height(); - const rectangle &visarea = screen->visible_area(); - screen->configure(width, height, visarea, HZ_TO_ATTOSECONDS(defrefresh + (double)newval * 0.001)); + int width = screen.width(); + int height = screen.height(); + const rectangle &visarea = screen.visible_area(); + screen.configure(width, height, visarea, HZ_TO_ATTOSECONDS(defrefresh + double(newval) * 0.001)); } if (str) - *str = string_format(_("%1$.3ffps"), screen->frame_period().as_hz()); - refresh = screen->frame_period().as_hz(); + *str = string_format(_(u8"%1$.3f\u00a0Hz"), screen.frame_period().as_hz()); + refresh = screen.frame_period().as_hz(); return floor((refresh - defrefresh) * 1000.0 + 0.5); } @@ -1686,20 +2156,17 @@ int32_t mame_ui_manager::slider_refresh(running_machine &machine, void *arg, int // callback //------------------------------------------------- -int32_t mame_ui_manager::slider_brightness(running_machine &machine, void *arg, int id, std::string *str, int32_t newval) +int32_t mame_ui_manager::slider_brightness(screen_device &screen, std::string *str, int32_t newval) { - screen_device *screen = reinterpret_cast<screen_device *>(arg); - render_container::user_settings settings; - - screen->container().get_user_settings(settings); + render_container::user_settings settings = screen.container().get_user_settings(); if (newval != SLIDER_NOCHANGE) { - settings.m_brightness = (float)newval * 0.001f; - screen->container().set_user_settings(settings); + settings.m_brightness = float(newval) * 0.001f; + screen.container().set_user_settings(settings); } if (str) *str = string_format(_("%1$.3f"), settings.m_brightness); - return floor(settings.m_brightness * 1000.0f + 0.5f); + return floorf(settings.m_brightness * 1000.0f + 0.5f); } @@ -1708,20 +2175,17 @@ int32_t mame_ui_manager::slider_brightness(running_machine &machine, void *arg, // callback //------------------------------------------------- -int32_t mame_ui_manager::slider_contrast(running_machine &machine, void *arg, int id, std::string *str, int32_t newval) +int32_t mame_ui_manager::slider_contrast(screen_device &screen, std::string *str, int32_t newval) { - screen_device *screen = reinterpret_cast<screen_device *>(arg); - render_container::user_settings settings; - - screen->container().get_user_settings(settings); + render_container::user_settings settings = screen.container().get_user_settings(); if (newval != SLIDER_NOCHANGE) { - settings.m_contrast = (float)newval * 0.001f; - screen->container().set_user_settings(settings); + settings.m_contrast = float(newval) * 0.001f; + screen.container().set_user_settings(settings); } if (str) *str = string_format(_("%1$.3f"), settings.m_contrast); - return floor(settings.m_contrast * 1000.0f + 0.5f); + return floorf(settings.m_contrast * 1000.0f + 0.5f); } @@ -1729,20 +2193,17 @@ int32_t mame_ui_manager::slider_contrast(running_machine &machine, void *arg, in // slider_gamma - screen gamma slider callback //------------------------------------------------- -int32_t mame_ui_manager::slider_gamma(running_machine &machine, void *arg, int id, std::string *str, int32_t newval) +int32_t mame_ui_manager::slider_gamma(screen_device &screen, std::string *str, int32_t newval) { - screen_device *screen = reinterpret_cast<screen_device *>(arg); - render_container::user_settings settings; - - screen->container().get_user_settings(settings); + render_container::user_settings settings = screen.container().get_user_settings(); if (newval != SLIDER_NOCHANGE) { - settings.m_gamma = (float)newval * 0.001f; - screen->container().set_user_settings(settings); + settings.m_gamma = float(newval) * 0.001f; + screen.container().set_user_settings(settings); } if (str) *str = string_format(_("%1$.3f"), settings.m_gamma); - return floor(settings.m_gamma * 1000.0f + 0.5f); + return floorf(settings.m_gamma * 1000.0f + 0.5f); } @@ -1751,20 +2212,17 @@ int32_t mame_ui_manager::slider_gamma(running_machine &machine, void *arg, int i // callback //------------------------------------------------- -int32_t mame_ui_manager::slider_xscale(running_machine &machine, void *arg, int id, std::string *str, int32_t newval) +int32_t mame_ui_manager::slider_xscale(screen_device &screen, std::string *str, int32_t newval) { - screen_device *screen = reinterpret_cast<screen_device *>(arg); - render_container::user_settings settings; - - screen->container().get_user_settings(settings); + render_container::user_settings settings = screen.container().get_user_settings(); if (newval != SLIDER_NOCHANGE) { - settings.m_xscale = (float)newval * 0.001f; - screen->container().set_user_settings(settings); + settings.m_xscale = float(newval) * 0.001f; + screen.container().set_user_settings(settings); } if (str) *str = string_format(_("%1$.3f"), settings.m_xscale); - return floor(settings.m_xscale * 1000.0f + 0.5f); + return floorf(settings.m_xscale * 1000.0f + 0.5f); } @@ -1773,20 +2231,17 @@ int32_t mame_ui_manager::slider_xscale(running_machine &machine, void *arg, int // callback //------------------------------------------------- -int32_t mame_ui_manager::slider_yscale(running_machine &machine, void *arg, int id, std::string *str, int32_t newval) +int32_t mame_ui_manager::slider_yscale(screen_device &screen, std::string *str, int32_t newval) { - screen_device *screen = reinterpret_cast<screen_device *>(arg); - render_container::user_settings settings; - - screen->container().get_user_settings(settings); + render_container::user_settings settings = screen.container().get_user_settings(); if (newval != SLIDER_NOCHANGE) { - settings.m_yscale = (float)newval * 0.001f; - screen->container().set_user_settings(settings); + settings.m_yscale = float(newval) * 0.001f; + screen.container().set_user_settings(settings); } if (str) *str = string_format(_("%1$.3f"), settings.m_yscale); - return floor(settings.m_yscale * 1000.0f + 0.5f); + return floorf(settings.m_yscale * 1000.0f + 0.5f); } @@ -1795,20 +2250,17 @@ int32_t mame_ui_manager::slider_yscale(running_machine &machine, void *arg, int // slider callback //------------------------------------------------- -int32_t mame_ui_manager::slider_xoffset(running_machine &machine, void *arg, int id, std::string *str, int32_t newval) +int32_t mame_ui_manager::slider_xoffset(screen_device &screen, std::string *str, int32_t newval) { - screen_device *screen = reinterpret_cast<screen_device *>(arg); - render_container::user_settings settings; - - screen->container().get_user_settings(settings); + render_container::user_settings settings = screen.container().get_user_settings(); if (newval != SLIDER_NOCHANGE) { - settings.m_xoffset = (float)newval * 0.001f; - screen->container().set_user_settings(settings); + settings.m_xoffset = float(newval) * 0.001f; + screen.container().set_user_settings(settings); } if (str) *str = string_format(_("%1$.3f"), settings.m_xoffset); - return floor(settings.m_xoffset * 1000.0f + 0.5f); + return floorf(settings.m_xoffset * 1000.0f + 0.5f); } @@ -1817,20 +2269,17 @@ int32_t mame_ui_manager::slider_xoffset(running_machine &machine, void *arg, int // slider callback //------------------------------------------------- -int32_t mame_ui_manager::slider_yoffset(running_machine &machine, void *arg, int id, std::string *str, int32_t newval) +int32_t mame_ui_manager::slider_yoffset(screen_device &screen, std::string *str, int32_t newval) { - screen_device *screen = reinterpret_cast<screen_device *>(arg); - render_container::user_settings settings; - - screen->container().get_user_settings(settings); + render_container::user_settings settings = screen.container().get_user_settings(); if (newval != SLIDER_NOCHANGE) { - settings.m_yoffset = (float)newval * 0.001f; - screen->container().set_user_settings(settings); + settings.m_yoffset = float(newval) * 0.001f; + screen.container().set_user_settings(settings); } if (str) *str = string_format(_("%1$.3f"), settings.m_yoffset); - return floor(settings.m_yoffset * 1000.0f + 0.5f); + return floorf(settings.m_yoffset * 1000.0f + 0.5f); } @@ -1839,20 +2288,19 @@ int32_t mame_ui_manager::slider_yoffset(running_machine &machine, void *arg, int // callback //------------------------------------------------- -int32_t mame_ui_manager::slider_overxscale(running_machine &machine, void *arg, int id, std::string *str, int32_t newval) +int32_t mame_ui_manager::slider_overxscale(laserdisc_device &laserdisc, std::string *str, int32_t newval) { - laserdisc_device *laserdisc = (laserdisc_device *)arg; laserdisc_overlay_config settings; - laserdisc->get_overlay_config(settings); + laserdisc.get_overlay_config(settings); if (newval != SLIDER_NOCHANGE) { - settings.m_overscalex = (float)newval * 0.001f; - laserdisc->set_overlay_config(settings); + settings.m_overscalex = float(newval) * 0.001f; + laserdisc.set_overlay_config(settings); } if (str) *str = string_format(_("%1$.3f"), settings.m_overscalex); - return floor(settings.m_overscalex * 1000.0f + 0.5f); + return floorf(settings.m_overscalex * 1000.0f + 0.5f); } @@ -1861,20 +2309,19 @@ int32_t mame_ui_manager::slider_overxscale(running_machine &machine, void *arg, // callback //------------------------------------------------- -int32_t mame_ui_manager::slider_overyscale(running_machine &machine, void *arg, int id, std::string *str, int32_t newval) +int32_t mame_ui_manager::slider_overyscale(laserdisc_device &laserdisc, std::string *str, int32_t newval) { - laserdisc_device *laserdisc = (laserdisc_device *)arg; laserdisc_overlay_config settings; - laserdisc->get_overlay_config(settings); + laserdisc.get_overlay_config(settings); if (newval != SLIDER_NOCHANGE) { - settings.m_overscaley = (float)newval * 0.001f; - laserdisc->set_overlay_config(settings); + settings.m_overscaley = float(newval) * 0.001f; + laserdisc.set_overlay_config(settings); } if (str) *str = string_format(_("%1$.3f"), settings.m_overscaley); - return floor(settings.m_overscaley * 1000.0f + 0.5f); + return floorf(settings.m_overscaley * 1000.0f + 0.5f); } @@ -1883,20 +2330,19 @@ int32_t mame_ui_manager::slider_overyscale(running_machine &machine, void *arg, // slider callback //------------------------------------------------- -int32_t mame_ui_manager::slider_overxoffset(running_machine &machine, void *arg, int id, std::string *str, int32_t newval) +int32_t mame_ui_manager::slider_overxoffset(laserdisc_device &laserdisc, std::string *str, int32_t newval) { - laserdisc_device *laserdisc = (laserdisc_device *)arg; laserdisc_overlay_config settings; - laserdisc->get_overlay_config(settings); + laserdisc.get_overlay_config(settings); if (newval != SLIDER_NOCHANGE) { - settings.m_overposx = (float)newval * 0.001f; - laserdisc->set_overlay_config(settings); + settings.m_overposx = float(newval) * 0.001f; + laserdisc.set_overlay_config(settings); } if (str) *str = string_format(_("%1$.3f"), settings.m_overposx); - return floor(settings.m_overposx * 1000.0f + 0.5f); + return floorf(settings.m_overposx * 1000.0f + 0.5f); } @@ -1905,20 +2351,19 @@ int32_t mame_ui_manager::slider_overxoffset(running_machine &machine, void *arg, // slider callback //------------------------------------------------- -int32_t mame_ui_manager::slider_overyoffset(running_machine &machine, void *arg, int id, std::string *str, int32_t newval) +int32_t mame_ui_manager::slider_overyoffset(laserdisc_device &laserdisc, std::string *str, int32_t newval) { - laserdisc_device *laserdisc = (laserdisc_device *)arg; laserdisc_overlay_config settings; - laserdisc->get_overlay_config(settings); + laserdisc.get_overlay_config(settings); if (newval != SLIDER_NOCHANGE) { - settings.m_overposy = (float)newval * 0.001f; - laserdisc->set_overlay_config(settings); + settings.m_overposy = float(newval) * 0.001f; + laserdisc.set_overlay_config(settings); } if (str) *str = string_format(_("%1$.3f"), settings.m_overposy); - return floor(settings.m_overposy * 1000.0f + 0.5f); + return floorf(settings.m_overposy * 1000.0f + 0.5f); } @@ -1927,13 +2372,13 @@ int32_t mame_ui_manager::slider_overyoffset(running_machine &machine, void *arg, // callback //------------------------------------------------- -int32_t mame_ui_manager::slider_flicker(running_machine &machine, void *arg, int id, std::string *str, int32_t newval) +int32_t mame_ui_manager::slider_flicker([[maybe_unused]] screen_device &screen, std::string *str, int32_t newval) { if (newval != SLIDER_NOCHANGE) - vector_options::s_flicker = (float)newval * 0.001f; + vector_options::s_flicker = float(newval) * 0.001f; if (str) *str = string_format(_("%1$1.2f"), vector_options::s_flicker); - return floor(vector_options::s_flicker * 1000.0f + 0.5f); + return floorf(vector_options::s_flicker * 1000.0f + 0.5f); } @@ -1942,13 +2387,13 @@ int32_t mame_ui_manager::slider_flicker(running_machine &machine, void *arg, int // callback //------------------------------------------------- -int32_t mame_ui_manager::slider_beam_width_min(running_machine &machine, void *arg, int id, std::string *str, int32_t newval) +int32_t mame_ui_manager::slider_beam_width_min([[maybe_unused]] screen_device &screen, std::string *str, int32_t newval) { if (newval != SLIDER_NOCHANGE) - vector_options::s_beam_width_min = std::min((float)newval * 0.01f, vector_options::s_beam_width_max); + vector_options::s_beam_width_min = std::min(float(newval) * 0.01f, vector_options::s_beam_width_max); if (str != nullptr) *str = string_format(_("%1$1.2f"), vector_options::s_beam_width_min); - return floor(vector_options::s_beam_width_min * 100.0f + 0.5f); + return floorf(vector_options::s_beam_width_min * 100.0f + 0.5f); } @@ -1957,13 +2402,28 @@ int32_t mame_ui_manager::slider_beam_width_min(running_machine &machine, void *a // callback //------------------------------------------------- -int32_t mame_ui_manager::slider_beam_width_max(running_machine &machine, void *arg, int id, std::string *str, int32_t newval) +int32_t mame_ui_manager::slider_beam_width_max([[maybe_unused]] screen_device &screen, std::string *str, int32_t newval) { if (newval != SLIDER_NOCHANGE) - vector_options::s_beam_width_max = std::max((float)newval * 0.01f, vector_options::s_beam_width_min); + vector_options::s_beam_width_max = std::max(float(newval) * 0.01f, vector_options::s_beam_width_min); if (str != nullptr) *str = string_format(_("%1$1.2f"), vector_options::s_beam_width_max); - return floor(vector_options::s_beam_width_max * 100.0f + 0.5f); + return floorf(vector_options::s_beam_width_max * 100.0f + 0.5f); +} + + +//------------------------------------------------- +// slider_beam_dot_size - beam dot size slider +// callback +//------------------------------------------------- + +int32_t mame_ui_manager::slider_beam_dot_size([[maybe_unused]] screen_device &screen, std::string *str, int32_t newval) +{ + if (newval != SLIDER_NOCHANGE) + vector_options::s_beam_dot_size = std::max(float(newval) * 0.01f, 0.1f); + if (str != nullptr) + *str = string_format(_("%1$1.2f"), vector_options::s_beam_dot_size); + return floorf(vector_options::s_beam_dot_size * 100.0f + 0.5f); } @@ -1972,13 +2432,13 @@ int32_t mame_ui_manager::slider_beam_width_max(running_machine &machine, void *a // callback //------------------------------------------------- -int32_t mame_ui_manager::slider_beam_intensity_weight(running_machine &machine, void *arg, int id, std::string *str, int32_t newval) +int32_t mame_ui_manager::slider_beam_intensity_weight([[maybe_unused]] screen_device &screen, std::string *str, int32_t newval) { if (newval != SLIDER_NOCHANGE) - vector_options::s_beam_intensity_weight = (float)newval * 0.001f; + vector_options::s_beam_intensity_weight = float(newval) * 0.001f; if (str != nullptr) *str = string_format(_("%1$1.2f"), vector_options::s_beam_intensity_weight); - return floor(vector_options::s_beam_intensity_weight * 1000.0f + 0.5f); + return floorf(vector_options::s_beam_intensity_weight * 1000.0f + 0.5f); } @@ -1988,15 +2448,13 @@ int32_t mame_ui_manager::slider_beam_intensity_weight(running_machine &machine, //------------------------------------------------- #ifdef MAME_DEBUG -int32_t mame_ui_manager::slider_crossscale(running_machine &machine, void *arg, int id, std::string *str, int32_t newval) +int32_t mame_ui_manager::slider_crossscale(ioport_field &field, std::string *str, int32_t newval) { - ioport_field *field = (ioport_field *)arg; - if (newval != SLIDER_NOCHANGE) - field->set_crosshair_scale(float(newval) * 0.001); + field.set_crosshair_scale(float(newval) * 0.001); if (str) - *str = string_format((field->crosshair_axis() == CROSSHAIR_AXIS_X) ? _("Crosshair Scale X %1$1.3f") : _("Crosshair Scale Y %1$1.3f"), float(newval) * 0.001f); - return floor(field->crosshair_scale() * 1000.0f + 0.5f); + *str = string_format((field.crosshair_axis() == CROSSHAIR_AXIS_X) ? _("Crosshair Scale X %1$1.3f") : _("Crosshair Scale Y %1$1.3f"), float(newval) * 0.001f); + return floorf(field.crosshair_scale() * 1000.0f + 0.5f); } #endif @@ -2007,15 +2465,13 @@ int32_t mame_ui_manager::slider_crossscale(running_machine &machine, void *arg, //------------------------------------------------- #ifdef MAME_DEBUG -int32_t mame_ui_manager::slider_crossoffset(running_machine &machine, void *arg, int id, std::string *str, int32_t newval) +int32_t mame_ui_manager::slider_crossoffset(ioport_field &field, std::string *str, int32_t newval) { - ioport_field *field = (ioport_field *)arg; - if (newval != SLIDER_NOCHANGE) - field->set_crosshair_offset(float(newval) * 0.001f); + field.set_crosshair_offset(float(newval) * 0.001f); if (str) - *str = string_format((field->crosshair_axis() == CROSSHAIR_AXIS_X) ? _("Crosshair Offset X %1$1.3f") : _("Crosshair Offset Y %1$1.3f"), float(newval) * 0.001f); - return field->crosshair_offset(); + *str = string_format((field.crosshair_axis() == CROSSHAIR_AXIS_X) ? _("Crosshair Offset X %1$1.3f") : _("Crosshair Offset Y %1$1.3f"), float(newval) * 0.001f); + return field.crosshair_offset(); } #endif @@ -2027,8 +2483,8 @@ int32_t mame_ui_manager::slider_crossoffset(running_machine &machine, void *arg, ui::text_layout mame_ui_manager::create_layout(render_container &container, float width, ui::text_layout::text_justify justify, ui::text_layout::word_wrapping wrap) { // determine scale factors - float yscale = get_line_height(); - float xscale = yscale * machine().render().ui_aspect(&container); + float const yscale = get_line_height(); + float const xscale = yscale * machine().render().ui_aspect(&container); // create the layout return ui::text_layout(*get_font(), xscale, yscale, width, justify, wrap); @@ -2036,26 +2492,6 @@ ui::text_layout mame_ui_manager::create_layout(render_container &container, floa //------------------------------------------------- -// wrap_text -//------------------------------------------------- - -int mame_ui_manager::wrap_text(render_container &container, const char *origs, float x, float y, float origwrapwidth, std::vector<int> &xstart, std::vector<int> &xend, float text_size) -{ - // create the layout - auto layout = create_layout(container, origwrapwidth, ui::text_layout::LEFT, ui::text_layout::WORD); - - // add the text - layout.add_text( - origs, - rgb_t::black(), - rgb_t::black(), - text_size); - - // and get the wrapping info - return layout.get_wrap_info(xstart, xend); -} - -//------------------------------------------------- // draw_textured_box - add primitives to // draw an outlined box with the given // textured background and line color @@ -2074,7 +2510,6 @@ void mame_ui_manager::popup_time_string(int seconds, std::string message) { // extract the text messagebox_poptext = message; - messagebox_backcolor = colors().background_color(); // set a timer m_popup_text_end = osd_ticks() + osd_ticks_per_second() * seconds; @@ -2094,11 +2529,11 @@ void mame_ui_manager::load_ui_options() // parse the file // attempt to open the output file emu_file file(machine().options().ini_path(), OPEN_FLAG_READ); - if (file.open("ui.ini") == osd_file::error::NONE) + if (!file.open("ui.ini")) { try { - options().parse_ini_file((util::core_file&)file, OPTION_PRIORITY_MAME_INI, OPTION_PRIORITY_MAME_INI < OPTION_PRIORITY_DRIVER_INI, true); + options().parse_ini_file((util::core_file &)file, OPTION_PRIORITY_MAME_INI, OPTION_PRIORITY_MAME_INI < OPTION_PRIORITY_DRIVER_INI, true); } catch (options_exception &) { @@ -2115,11 +2550,10 @@ void mame_ui_manager::save_ui_options() { // attempt to open the output file emu_file file(machine().options().ini_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); - if (file.open("ui.ini") == osd_file::error::NONE) + if (!file.open("ui.ini")) { // generate the updated INI - std::string initext = options().output_ini(); - file.puts(initext.c_str()); + file.puts(options().output_ini()); file.close(); } else @@ -2144,13 +2578,13 @@ void mame_ui_manager::save_main_option() // attempt to open the main ini file { emu_file file(machine().options().ini_path(), OPEN_FLAG_READ); - if (file.open(emulator_info::get_configname(), ".ini") == osd_file::error::NONE) + if (!file.open(std::string(emulator_info::get_configname()) + ".ini")) { try { options.parse_ini_file((util::core_file&)file, OPTION_PRIORITY_MAME_INI, OPTION_PRIORITY_MAME_INI < OPTION_PRIORITY_DRIVER_INI, true); } - catch(options_error_exception &) + catch (options_error_exception &) { osd_printf_error("**Error loading %s.ini**\n", emulator_info::get_configname()); return; @@ -2165,7 +2599,7 @@ void mame_ui_manager::save_main_option() for (const auto &f_entry : machine().options().entries()) { const char *value = f_entry->value(); - if (value && options.exists(f_entry->name()) && strcmp(value, options.value(f_entry->name().c_str()))) + if (value && options.exists(f_entry->name()) && strcmp(value, options.value(f_entry->name()))) { options.set_value(f_entry->name(), *f_entry->value(), OPTION_PRIORITY_CMDLINE); } @@ -2174,26 +2608,77 @@ void mame_ui_manager::save_main_option() // attempt to open the output file { emu_file file(machine().options().ini_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); - if (file.open(emulator_info::get_configname(), ".ini") == osd_file::error::NONE) + if (!file.open(std::string(emulator_info::get_configname()) + ".ini")) { // generate the updated INI - std::string initext = options.output_ini(); - file.puts(initext.c_str()); + file.puts(options.output_ini()); file.close(); } - else { + else + { machine().popmessage(_("**Error saving %s.ini**"), emulator_info::get_configname()); return; } } - popup_time(3, "%s", _("\n Configuration saved \n\n")); + popup_time(3, "%s", _("\n Settings saved \n\n")); } void mame_ui_manager::menu_reset() { - ui::menu::stack_reset(machine()); + ui::menu::stack_reset(*this); } + +bool mame_ui_manager::set_ui_event_handler(std::function<bool ()> &&handler) +{ + // only allow takeover if there's nothing else happening + if (ui_callback_type::GENERAL != m_handler_callback_type) + return false; + + for (auto *target = machine().render().first_target(); target; target = target->next()) + { + if (!target->hidden()) + target->forget_pointers(); + } + + set_handler( + ui_callback_type::CUSTOM, + handler_callback_func( + [cb = std::move(handler)] (render_container &container) -> uint32_t + { + return !cb() ? HANDLER_CANCEL : 0; + })); + return true; +} + + +//------------------------------------------------- +// get_general_input_setting - get the current +// default setting for an input type (useful for +// prompting the user) +//------------------------------------------------- + +std::string mame_ui_manager::get_general_input_setting(ioport_type type, int player, input_seq_type seqtype) +{ + input_seq seq(machine().ioport().type_seq(type, player, seqtype)); + input_code codes[16]; // TODO: remove magic number + unsigned len(0U); + for (unsigned i = 0U; std::size(codes) > i; ++i) + { + if (input_seq::not_code == seq[i]) + ++i; + else + codes[len++] = seq[i]; + if (input_seq::end_code == seq[i]) + break; + } + seq.reset(); + for (unsigned i = 0U; len > i; ++i) + seq += codes[i]; + return machine().input().seq_name(seq); +} + + void ui_colors::refresh(const ui_options &options) { m_border_color = options.border_color(); diff --git a/src/frontend/mame/ui/ui.h b/src/frontend/mame/ui/ui.h index 71f6a5ab8b8..ed5fa97156d 100644 --- a/src/frontend/mame/ui/ui.h +++ b/src/frontend/mame/ui/ui.h @@ -13,22 +13,42 @@ #pragma once -#include "render.h" -#include "moptions.h" #include "language.h" #include "ui/uimain.h" #include "ui/menuitem.h" +#include "ui/moptions.h" #include "ui/slider.h" #include "ui/text.h" +#include "render.h" + +#include "interface/uievents.h" + +#include <any> +#include <cassert> +#include <chrono> +#include <ctime> #include <functional> +#include <set> +#include <string> +#include <string_view> +#include <typeindex> +#include <typeinfo> +#include <unordered_map> +#include <utility> #include <vector> + namespace ui { + class menu_item; class machine_info; + } // namespace ui +class laserdisc_device; + + /*************************************************************************** CONSTANTS ***************************************************************************/ @@ -39,81 +59,15 @@ class machine_info; #define UI_LINE_WIDTH (1.0f / 500.0f) /* handy colors */ -#define UI_GREEN_COLOR rgb_t(0xef,0x10,0x60,0x10) -#define UI_YELLOW_COLOR rgb_t(0xef,0x60,0x60,0x10) -#define UI_RED_COLOR rgb_t(0xf0,0x60,0x10,0x10) - -/* cancel return value for a UI handler */ -#define UI_HANDLER_CANCEL ((uint32_t)~0) - -#define SLIDER_DEVICE_SPACING 0x0ff -#define SLIDER_SCREEN_SPACING 0x0f -#define SLIDER_INPUT_SPACING 0x0f - -enum -{ - SLIDER_ID_VOLUME = 0, - SLIDER_ID_MIXERVOL, - SLIDER_ID_MIXERVOL_LAST = SLIDER_ID_MIXERVOL + SLIDER_DEVICE_SPACING, - SLIDER_ID_ADJUSTER, - SLIDER_ID_ADJUSTER_LAST = SLIDER_ID_ADJUSTER + SLIDER_DEVICE_SPACING, - SLIDER_ID_OVERCLOCK, - SLIDER_ID_OVERCLOCK_LAST = SLIDER_ID_OVERCLOCK + SLIDER_DEVICE_SPACING, - SLIDER_ID_REFRESH, - SLIDER_ID_REFRESH_LAST = SLIDER_ID_REFRESH + SLIDER_SCREEN_SPACING, - SLIDER_ID_BRIGHTNESS, - SLIDER_ID_BRIGHTNESS_LAST = SLIDER_ID_BRIGHTNESS + SLIDER_SCREEN_SPACING, - SLIDER_ID_CONTRAST, - SLIDER_ID_CONTRAST_LAST = SLIDER_ID_CONTRAST + SLIDER_SCREEN_SPACING, - SLIDER_ID_GAMMA, - SLIDER_ID_GAMMA_LAST = SLIDER_ID_GAMMA + SLIDER_SCREEN_SPACING, - SLIDER_ID_XSCALE, - SLIDER_ID_XSCALE_LAST = SLIDER_ID_XSCALE + SLIDER_SCREEN_SPACING, - SLIDER_ID_YSCALE, - SLIDER_ID_YSCALE_LAST = SLIDER_ID_YSCALE + SLIDER_SCREEN_SPACING, - SLIDER_ID_XOFFSET, - SLIDER_ID_XOFFSET_LAST = SLIDER_ID_XOFFSET + SLIDER_SCREEN_SPACING, - SLIDER_ID_YOFFSET, - SLIDER_ID_YOFFSET_LAST = SLIDER_ID_YOFFSET + SLIDER_SCREEN_SPACING, - SLIDER_ID_OVERLAY_XSCALE, - SLIDER_ID_OVERLAY_XSCALE_LAST = SLIDER_ID_OVERLAY_XSCALE + SLIDER_SCREEN_SPACING, - SLIDER_ID_OVERLAY_YSCALE, - SLIDER_ID_OVERLAY_YSCALE_LAST = SLIDER_ID_OVERLAY_YSCALE + SLIDER_SCREEN_SPACING, - SLIDER_ID_OVERLAY_XOFFSET, - SLIDER_ID_OVERLAY_XOFFSET_LAST = SLIDER_ID_OVERLAY_XOFFSET + SLIDER_SCREEN_SPACING, - SLIDER_ID_OVERLAY_YOFFSET, - SLIDER_ID_OVERLAY_YOFFSET_LAST = SLIDER_ID_OVERLAY_YOFFSET + SLIDER_SCREEN_SPACING, - SLIDER_ID_FLICKER, - SLIDER_ID_FLICKER_LAST = SLIDER_ID_FLICKER + SLIDER_SCREEN_SPACING, - SLIDER_ID_BEAM_WIDTH_MIN, - SLIDER_ID_BEAM_WIDTH_MIN_LAST = SLIDER_ID_BEAM_WIDTH_MIN + SLIDER_SCREEN_SPACING, - SLIDER_ID_BEAM_WIDTH_MAX, - SLIDER_ID_BEAM_WIDTH_MAX_LAST = SLIDER_ID_BEAM_WIDTH_MAX + SLIDER_SCREEN_SPACING, - SLIDER_ID_BEAM_INTENSITY, - SLIDER_ID_BEAM_INTENSITY_LAST = SLIDER_ID_BEAM_INTENSITY + SLIDER_SCREEN_SPACING, - SLIDER_ID_CROSSHAIR_SCALE, - SLIDER_ID_CROSSHAIR_SCALE_LAST = SLIDER_ID_CROSSHAIR_SCALE + SLIDER_INPUT_SPACING, - SLIDER_ID_CROSSHAIR_OFFSET, - SLIDER_ID_CROSSHAIR_OFFSET_LAST = SLIDER_ID_CROSSHAIR_OFFSET + SLIDER_INPUT_SPACING, - - SLIDER_ID_CORE_LAST = SLIDER_ID_CROSSHAIR_OFFSET, - SLIDER_ID_CORE_COUNT -}; +#define UI_GREEN_COLOR rgb_t(0xef,0x0a,0x66,0x0a) +#define UI_YELLOW_COLOR rgb_t(0xef,0xcc,0x7a,0x28) +#define UI_RED_COLOR rgb_t(0xef,0xb2,0x00,0x00) /*************************************************************************** TYPE DEFINITIONS ***************************************************************************/ class mame_ui_manager; -typedef uint32_t (*ui_callback)(mame_ui_manager &, render_container &, uint32_t); - -enum class ui_callback_type -{ - GENERAL, - MODAL, - MENU, - VIEWER -}; // ======================> ui_colors @@ -160,9 +114,15 @@ private: // ======================> mame_ui_manager -class mame_ui_manager : public ui_manager, public slider_changed_notifier +class mame_ui_manager : public ui_manager { public: + enum : uint32_t + { + HANDLER_UPDATE = 1U << 0, // force video update + HANDLER_CANCEL = 1U << 1 // return to in-game event handler + }; + enum draw_mode { NONE, @@ -170,6 +130,18 @@ public: OPAQUE_ }; + struct display_pointer + { + std::reference_wrapper<render_target> target; + osd::ui_event_handler::pointer type; + float x, y; + + bool operator!=(display_pointer const &that) const noexcept + { + return (&target.get() != &that.target.get()) || (type != that.type) || (x != that.x) || (y != that.y); + } + }; + // construction/destruction mame_ui_manager(running_machine &machine); ~mame_ui_manager(); @@ -190,22 +162,30 @@ public: void initialize(running_machine &machine); std::vector<ui::menu_item> slider_init(running_machine &machine); - void set_handler(ui_callback_type callback_type, const std::function<uint32_t (render_container &)> &&callback); - void display_startup_screens(bool first_time); virtual void set_startup_text(const char *text, bool force) override; - void update_and_render(render_container &container); + bool update_and_render(render_container &container); + + // getting display font and metrics render_font *get_font(); - float get_line_height(); + float get_line_height(float scale = 1.0F); + float target_font_height() const { return m_target_font_height; } float get_char_width(char32_t ch); - float get_string_width(const char *s, float text_size = 1.0f); + float get_string_width(std::string_view s); + float get_string_width(std::string_view s, float text_size); + float box_lr_border() const { return target_font_height() * 0.25f; } + float box_tb_border() const { return target_font_height() * 0.25f; } + + // drawing boxes and text void draw_outlined_box(render_container &container, float x0, float y0, float x1, float y1, rgb_t backcolor); void draw_outlined_box(render_container &container, float x0, float y0, float x1, float y1, rgb_t fgcolor, rgb_t bgcolor); - void draw_text(render_container &container, const char *buf, float x, float y); - void draw_text_full(render_container &container, const char *origs, float x, float y, float origwrapwidth, ui::text_layout::text_justify justify, ui::text_layout::word_wrapping wrap, draw_mode draw, rgb_t fgcolor, rgb_t bgcolor, float *totalwidth = nullptr, float *totalheight = nullptr, float text_size = 1.0f); - void draw_text_box(render_container &container, const char *text, ui::text_layout::text_justify justify, float xpos, float ypos, rgb_t backcolor); + void draw_textured_box(render_container &container, float x0, float y0, float x1, float y1, rgb_t backcolor, rgb_t linecolor, render_texture *texture = nullptr, uint32_t flags = PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + void draw_text(render_container &container, std::string_view buf, float x, float y); + void draw_text_full(render_container &container, std::string_view origs, float x, float y, float origwrapwidth, ui::text_layout::text_justify justify, ui::text_layout::word_wrapping wrap, draw_mode draw, rgb_t fgcolor, rgb_t bgcolor, float *totalwidth = nullptr, float *totalheight = nullptr); + void draw_text_full(render_container &container, std::string_view origs, float x, float y, float origwrapwidth, ui::text_layout::text_justify justify, ui::text_layout::word_wrapping wrap, draw_mode draw, rgb_t fgcolor, rgb_t bgcolor, float *totalwidth, float *totalheight, float text_size); + void draw_text_box(render_container &container, std::string_view text, ui::text_layout::text_justify justify, float xpos, float ypos, rgb_t backcolor); void draw_text_box(render_container &container, ui::text_layout &layout, float xpos, float ypos, rgb_t backcolor); - void draw_message_window(render_container &container, const char *text); + void draw_message_window(render_container &container, std::string_view text); // load/save options to file void load_ui_options(); @@ -213,6 +193,8 @@ public: void save_main_option(); template <typename Format, typename... Params> void popup_time(int seconds, Format &&fmt, Params &&... args); + void set_ui_active(bool active) { m_ui_active = active; } + bool ui_active() const { return m_ui_active; } void show_fps_temp(double seconds); void set_show_fps(bool show); bool show_fps() const; @@ -220,110 +202,177 @@ public: void set_show_profiler(bool show); bool show_profiler() const; void show_menu(); - void show_mouse(bool status); virtual bool is_menu_active() override; bool can_paste(); void image_handler_ingame(); - void increase_frameskip(); - void decrease_frameskip(); void request_quit(); + void set_pointer_activity_timeout(int target, std::chrono::steady_clock::duration timeout) noexcept; + void set_hide_inactive_pointers(int target, bool hide) noexcept; + void restore_initial_pointer_options(int target) noexcept; + std::chrono::steady_clock::duration pointer_activity_timeout(int target) const noexcept; + bool hide_inactive_pointers(int target) const noexcept; + + // drawing informational overlays void draw_fps_counter(render_container &container); - void draw_timecode_counter(render_container &container); - void draw_timecode_total(render_container &container); void draw_profiler(render_container &container); - void start_save_state(); - void start_load_state(); + + // pointer display for UI handlers + template <typename T> + void set_pointers(T first, T last) + { + auto dest = m_display_pointers.begin(); + while ((m_display_pointers.end() != dest) && (first != last)) + { + if (*first != *dest) + { + *dest = *first; + m_pointers_changed = true; + } + ++dest; + ++first; + } + if (m_display_pointers.end() != dest) + { + m_display_pointers.erase(dest, m_display_pointers.end()); + m_pointers_changed = true; + } + else + { + while (first != last) + { + m_display_pointers.emplace_back(*first); + m_pointers_changed = true; + ++first; + } + } + } // slider controls - std::vector<ui::menu_item>& get_slider_list(void); + std::vector<ui::menu_item> &get_slider_list(); // metrics - float target_font_height() const { return m_target_font_height; } - float box_lr_border() const { return target_font_height() * 0.25f; } - float box_tb_border() const { return target_font_height() * 0.25f; } void update_target_font_height(); // other - void process_natural_keyboard(); - ui::text_layout create_layout(render_container &container, float width = 1.0, ui::text_layout::text_justify justify = ui::text_layout::LEFT, ui::text_layout::word_wrapping wrap = ui::text_layout::WORD); - - // word wrap - int wrap_text(render_container &container, const char *origs, float x, float y, float origwrapwidth, std::vector<int> &xstart, std::vector<int> &xend, float text_size = 1.0f); - - // draw an outlined box with given line color and filled with a texture - void draw_textured_box(render_container &container, float x0, float y0, float x1, float y1, rgb_t backcolor, rgb_t linecolor, render_texture *texture = nullptr, uint32_t flags = PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + void process_ui_events(); + ui::text_layout create_layout(render_container &container, float width = 1.0, ui::text_layout::text_justify justify = ui::text_layout::text_justify::LEFT, ui::text_layout::word_wrapping wrap = ui::text_layout::word_wrapping::WORD); + void set_image_display_enabled(bool image_display_enabled) { m_image_display_enabled = image_display_enabled; } + bool image_display_enabled() const { return m_image_display_enabled; } virtual void popup_time_string(int seconds, std::string message) override; virtual void menu_reset() override; + virtual bool set_ui_event_handler(std::function<bool ()> &&handler) override; + + template <typename Owner, typename Data, typename... Param> + Data &get_session_data(Param &&... args) + { + auto const ins(m_session_data.try_emplace(typeid(Owner))); + assert(!ins.first->second.has_value() == ins.second); + if (ins.second) + return ins.first->second.emplace<Data>(std::forward<Param>(args)...); + Data *const result(std::any_cast<Data>(&ins.first->second)); + assert(result); + return *result; + } + + // helper for getting a general input setting - used for instruction text + std::string get_general_input_setting(ioport_type type, int player = 0, input_seq_type seqtype = SEQ_TYPE_STANDARD); private: + enum class ui_callback_type : int; + + struct active_pointer; + class pointer_options; + + using handler_callback_func = delegate<uint32_t (render_container &)>; + using device_feature_set = std::set<std::pair<std::string, std::string> >; + using session_data_map = std::unordered_map<std::type_index, std::any>; + using active_pointer_vector = std::vector<active_pointer>; + using pointer_options_vector = std::vector<pointer_options>; + using display_pointer_vector = std::vector<display_pointer>; + // instance variables - render_font * m_font; - std::function<uint32_t (render_container &)> m_handler_callback; + std::unique_ptr<render_font> m_font; + handler_callback_func m_handler_callback; ui_callback_type m_handler_callback_type; - uint32_t m_handler_param; + bool m_ui_active; bool m_single_step; bool m_showfps; osd_ticks_t m_showfps_end; bool m_show_profiler; osd_ticks_t m_popup_text_end; - std::unique_ptr<uint8_t[]> m_non_char_keys_down; + std::unique_ptr<uint8_t []> m_non_char_keys_down; + + pointer_options_vector m_pointer_options; + active_pointer_vector m_active_pointers; + display_pointer_vector m_display_pointers; bitmap_argb32 m_mouse_bitmap; render_texture * m_mouse_arrow_texture; - bool m_mouse_show; + bool m_pointers_changed; + ui_options m_ui_options; ui_colors m_ui_colors; float m_target_font_height; + bool m_unthrottle_mute; + bool m_image_display_enabled; std::unique_ptr<ui::machine_info> m_machine_info; + device_feature_set m_unemulated_features; + device_feature_set m_imperfect_features; + std::time_t m_last_launch_time; + std::time_t m_last_warning_time; + + session_data_map m_session_data; // static variables static std::string messagebox_text; static std::string messagebox_poptext; - static rgb_t messagebox_backcolor; static std::vector<ui::menu_item> slider_list; - static slider_state *slider_current; // UI handlers - uint32_t handler_messagebox(render_container &container); - uint32_t handler_messagebox_anykey(render_container &container); uint32_t handler_ingame(render_container &container); - uint32_t handler_load_save(render_container &container, uint32_t state); - uint32_t handler_confirm_quit(render_container &container); // private methods + void set_handler(ui_callback_type callback_type, handler_callback_func &&callback); + void frame_update(); void exit(); - std::unique_ptr<slider_state> slider_alloc(int id, const char *title, int32_t minval, int32_t defval, int32_t maxval, int32_t incval, void *arg); + void increase_frameskip(); + void decrease_frameskip(); + void config_load_warnings(config_type cfg_type, config_level cfg_level, util::xml::data_node const *parentnode); + void config_save_warnings(config_type cfg_type, util::xml::data_node *parentnode); + void config_load_pointers(config_type cfg_type, config_level cfg_level, util::xml::data_node const *parentnode); + void config_save_pointers(config_type cfg_type, util::xml::data_node *parentnode); + template <typename... Params> void slider_alloc(Params &&...args) { m_sliders.push_back(std::make_unique<slider_state>(std::forward<Params>(args)...)); } // slider controls - virtual int32_t slider_changed(running_machine &machine, void *arg, int id, std::string *str, int32_t newval) override; - - int32_t slider_volume(running_machine &machine, void *arg, int id, std::string *str, int32_t newval); - int32_t slider_mixervol(running_machine &machine, void *arg, int id, std::string *str, int32_t newval); - int32_t slider_adjuster(running_machine &machine, void *arg, int id, std::string *str, int32_t newval); - int32_t slider_overclock(running_machine &machine, void *arg, int id, std::string *str, int32_t newval); - int32_t slider_refresh(running_machine &machine, void *arg, int id, std::string *str, int32_t newval); - int32_t slider_brightness(running_machine &machine, void *arg, int id, std::string *str, int32_t newval); - int32_t slider_contrast(running_machine &machine, void *arg, int id, std::string *str, int32_t newval); - int32_t slider_gamma(running_machine &machine, void *arg, int id, std::string *str, int32_t newval); - int32_t slider_xscale(running_machine &machine, void *arg, int id, std::string *str, int32_t newval); - int32_t slider_yscale(running_machine &machine, void *arg, int id, std::string *str, int32_t newval); - int32_t slider_xoffset(running_machine &machine, void *arg, int id, std::string *str, int32_t newval); - int32_t slider_yoffset(running_machine &machine, void *arg, int id, std::string *str, int32_t newval); - int32_t slider_overxscale(running_machine &machine, void *arg, int id, std::string *str, int32_t newval); - int32_t slider_overyscale(running_machine &machine, void *arg, int id, std::string *str, int32_t newval); - int32_t slider_overxoffset(running_machine &machine, void *arg, int id, std::string *str, int32_t newval); - int32_t slider_overyoffset(running_machine &machine, void *arg, int id, std::string *str, int32_t newval); - int32_t slider_flicker(running_machine &machine, void *arg, int id, std::string *str, int32_t newval); - int32_t slider_beam_width_min(running_machine &machine, void *arg, int id, std::string *str, int32_t newval); - int32_t slider_beam_width_max(running_machine &machine, void *arg, int id, std::string *str, int32_t newval); - int32_t slider_beam_intensity_weight(running_machine &machine, void *arg, int id, std::string *str, int32_t newval); + int32_t slider_volume(std::string *str, int32_t newval); + int32_t slider_devvol(device_sound_interface *snd, std::string *str, int32_t newval); + int32_t slider_devvol_chan(device_sound_interface *snd, int channel, std::string *str, int32_t newval); + int32_t slider_adjuster(ioport_field &field, std::string *str, int32_t newval); + int32_t slider_overclock(device_t &device, std::string *str, int32_t newval); + int32_t slider_refresh(screen_device &screen, std::string *str, int32_t newval); + int32_t slider_brightness(screen_device &screen, std::string *str, int32_t newval); + int32_t slider_contrast(screen_device &screen, std::string *str, int32_t newval); + int32_t slider_gamma(screen_device &screen, std::string *str, int32_t newval); + int32_t slider_xscale(screen_device &screen, std::string *str, int32_t newval); + int32_t slider_yscale(screen_device &screen, std::string *str, int32_t newval); + int32_t slider_xoffset(screen_device &screen, std::string *str, int32_t newval); + int32_t slider_yoffset(screen_device &screen, std::string *str, int32_t newval); + int32_t slider_overxscale(laserdisc_device &laserdisc, std::string *str, int32_t newval); + int32_t slider_overyscale(laserdisc_device &laserdisc, std::string *str, int32_t newval); + int32_t slider_overxoffset(laserdisc_device &laserdisc, std::string *str, int32_t newval); + int32_t slider_overyoffset(laserdisc_device &laserdisc, std::string *str, int32_t newval); + int32_t slider_flicker(screen_device &screen, std::string *str, int32_t newval); + int32_t slider_beam_width_min(screen_device &screen, std::string *str, int32_t newval); + int32_t slider_beam_width_max(screen_device &screen, std::string *str, int32_t newval); + int32_t slider_beam_dot_size(screen_device &screen, std::string *str, int32_t newval); + int32_t slider_beam_intensity_weight(screen_device &screen, std::string *str, int32_t newval); std::string slider_get_screen_desc(screen_device &screen); - #ifdef MAME_DEBUG - int32_t slider_crossscale(running_machine &machine, void *arg, int id, std::string *str, int32_t newval); - int32_t slider_crossoffset(running_machine &machine, void *arg, int id, std::string *str, int32_t newval); - #endif +#ifdef MAME_DEBUG + int32_t slider_crossscale(ioport_field &field, std::string *str, int32_t newval); + int32_t slider_crossoffset(ioport_field &field, std::string *str, int32_t newval); +#endif std::vector<std::unique_ptr<slider_state>> m_sliders; }; diff --git a/src/frontend/mame/ui/utils.cpp b/src/frontend/mame/ui/utils.cpp index 58ee1e09f8d..8acd4a70b5e 100644 --- a/src/frontend/mame/ui/utils.cpp +++ b/src/frontend/mame/ui/utils.cpp @@ -14,13 +14,15 @@ #include "ui/inifile.h" #include "ui/selector.h" +#include "infoxml.h" #include "language.h" #include "mame.h" #include "drivenum.h" #include "rendfont.h" #include "romload.h" -#include "softlist.h" + +#include "corestr.h" #include <atomic> #include <bitset> @@ -29,6 +31,7 @@ #include <cstring> #include <iterator> #include <unordered_set> +#include <utility> namespace ui { @@ -56,46 +59,88 @@ constexpr char const *SOFTWARE_REGIONS[] = { "tha", "tpe", "tw", "uk", "ukr", "usa" }; +// must be sorted in std::string comparison order +constexpr std::pair<char const *, char const *> SOFTWARE_INFO_NAMES[] = { + { "alt_title", N_p("swlist-info", "Alternate Title") }, + { "author", N_p("swlist-info", "Author") }, + { "barcode", N_p("swlist-info", "Barcode Number") }, + { "developer", N_p("swlist-info", "Developer") }, + { "distributor", N_p("swlist-info", "Distributor") }, + { "install", N_p("swlist-info", "Installation Instructions") }, + { "isbn", N_p("swlist-info", "ISBN") }, + { "oem", N_p("swlist-info", "OEM") }, + { "original_publisher", N_p("swlist-info", "Original Publisher") }, + { "partno", N_p("swlist-info", "Part Number") }, + { "pcb", N_p("swlist-info", "PCB") }, + { "programmer", N_p("swlist-info", "Programmer") }, + { "release", N_p("swlist-info", "Release Date") }, + { "serial", N_p("swlist-info", "Serial Number") }, + { "usage", N_p("swlist-info", "Usage Instructions") }, + { "version", N_p("swlist-info", "Version") } }; + + + +// must be in sync with the machine_filter::type enum constexpr char const *MACHINE_FILTER_NAMES[machine_filter::COUNT] = { - __("Unfiltered"), - __("Available"), - __("Unavailable"), - __("Working"), - __("Not Working"), - __("Mechanical"), - __("Not Mechanical"), - __("Category"), - __("Favorites"), - __("BIOS"), - __("Not BIOS"), - __("Parents"), - __("Clones"), - __("Manufacturer"), - __("Year"), - __("Save Supported"), - __("Save Unsupported"), - __("CHD Required"), - __("No CHD Required"), - __("Vertical Screen"), - __("Horizontal Screen"), - __("Custom Filter") }; - + N_p("machine-filter", "Unfiltered"), + N_p("machine-filter", "Available"), + N_p("machine-filter", "Unavailable"), + N_p("machine-filter", "Working"), + N_p("machine-filter", "Not Working"), + N_p("machine-filter", "Mechanical"), + N_p("machine-filter", "Not Mechanical"), + N_p("machine-filter", "Category"), + N_p("machine-filter", "Favorites"), + N_p("machine-filter", "BIOS"), + N_p("machine-filter", "Not BIOS"), + N_p("machine-filter", "Parents"), + N_p("machine-filter", "Clones"), + N_p("machine-filter", "Manufacturer"), + N_p("machine-filter", "Year"), + N_p("machine-filter", "Source File"), + N_p("machine-filter", "Save Supported"), + N_p("machine-filter", "Save Unsupported"), + N_p("machine-filter", "CHD Required"), + N_p("machine-filter", "No CHD Required"), + N_p("machine-filter", "Vertical Screen"), + N_p("machine-filter", "Horizontal Screen"), + N_p("machine-filter", "Custom Filter") }; + +// must be in sync with the software_filter::type enum constexpr char const *SOFTWARE_FILTER_NAMES[software_filter::COUNT] = { - __("Unfiltered"), - __("Available"), - __("Unavailable"), - __("Favorites"), - __("Parents"), - __("Clones"), - __("Year"), - __("Publisher"), - __("Supported"), - __("Partially Supported"), - __("Unsupported"), - __("Release Region"), - __("Device Type"), - __("Software List"), - __("Custom Filter") }; + N_p("software-filter", "Unfiltered"), + N_p("software-filter", "Available"), + N_p("software-filter", "Unavailable"), + N_p("software-filter", "Favorites"), + N_p("software-filter", "Parents"), + N_p("software-filter", "Clones"), + N_p("software-filter", "Year"), + N_p("software-filter", "Publisher"), + N_p("software-filter", "Developer"), + N_p("software-filter", "Distributor"), + N_p("software-filter", "Author"), + N_p("software-filter", "Programmer"), + N_p("software-filter", "Supported"), + N_p("software-filter", "Partially Supported"), + N_p("software-filter", "Unsupported"), + N_p("software-filter", "Release Region"), + N_p("software-filter", "Device Type"), + N_p("software-filter", "Software List"), + N_p("software-filter", "Custom Filter") }; + + + +//------------------------------------------------- +// helper for building a sorted vector +//------------------------------------------------- + +template <typename T> +void add_info_value(std::vector<std::string> &items, T &&value) +{ + std::vector<std::string>::iterator const pos(std::lower_bound(items.begin(), items.end(), value)); + if ((items.end() == pos) || (*pos != value)) + items.emplace(pos, std::forward<T>(value)); +} @@ -122,9 +167,9 @@ public: virtual bool adjust_left() override { return false; } virtual bool adjust_right() override { return false; } - virtual void save_ini(emu_file &file, unsigned indent) const override + virtual void save_ini(util::core_file &file, unsigned indent) const override { - file.puts(util::string_format("%2$*1$s%3$s = 1\n", 2 * indent, "", config_name()).c_str()); + file.puts(util::string_format("%2$*1$s%3$s = 1\n", 2 * indent, "", config_name())); } virtual typename Base::type get_type() const override { return Type; } @@ -133,10 +178,7 @@ public: { std::string result; if (Type == n) - { - result = "_> "; - convert_command_glyph(result); - } + result = convert_command_glyph("_> "); result.append(Base::display_name(n)); return result; } @@ -162,22 +204,16 @@ public: virtual void show_ui(mame_ui_manager &mui, render_container &container, std::function<void (Base &)> &&handler) override { - if (m_choices.empty()) - { - handler(*this); - } - else - { - menu::stack_push<menu_selector>( - mui, container, - std::vector<std::string>(m_choices), // ouch, a vector copy! - m_selection, - [this, cb = std::move(handler)] (int selection) - { - m_selection = selection; - cb(*this); - }); - } + menu::stack_push<menu_selector>( + mui, container, + _("Filter"), // TODO: get localised name of filter in here somehow + std::vector<std::string>(m_choices), // ouch, a vector copy! + m_selection, + [this, cb = std::move(handler)] (int selection) + { + m_selection = selection; + cb(*this); + }); } virtual bool wants_adjuster() const override { return have_choices(); } @@ -203,10 +239,10 @@ public: return true; } - virtual void save_ini(emu_file &file, unsigned indent) const override + virtual void save_ini(util::core_file &file, unsigned indent) const override { char const *const text(filter_text()); - file.puts(util::string_format("%2$*1$s%3$s = %4$s\n", 2 * indent, "", this->config_name(), text ? text : "").c_str()); + file.puts(util::string_format("%2$*1$s%3$s = %4$s\n", 2 * indent, "", this->config_name(), text ? text : "")); } protected: @@ -215,11 +251,14 @@ protected: , m_selection(0U) { if (value) - { - std::vector<std::string>::const_iterator const found(std::find(choices.begin(), choices.end(), value)); - if (choices.end() != found) - m_selection = std::distance(choices.begin(), found); - } + set_value(value); + } + + void set_value(char const *value) + { + auto const found(std::find(m_choices.begin(), m_choices.end(), value)); + if (m_choices.end() != found) + m_selection = std::distance(m_choices.begin(), found); } bool have_choices() const { return !m_choices.empty(); } @@ -247,10 +286,10 @@ public: virtual bool wants_adjuster() const override { return true; } virtual char const *adjust_text() const override { return _("<set up filters>"); } - virtual void save_ini(emu_file &file, unsigned indent) const override + virtual void save_ini(util::core_file &file, unsigned indent) const override { auto const tail(std::find_if(std::begin(m_filters), std::end(m_filters), [] (typename Base::ptr const &flt) { return !flt; })); - file.puts(util::string_format("%2$*1$s%3$s = %4$d\n", 2 * indent, "", this->config_name(), std::distance(std::begin(m_filters), tail)).c_str()); + file.puts(util::string_format("%2$*1$s%3$s = %4$d\n", 2 * indent, "", this->config_name(), std::distance(std::begin(m_filters), tail))); for (auto it = std::begin(m_filters); tail != it; ++it) (*it)->save_ini(file, indent + 1); } @@ -259,18 +298,14 @@ public: { std::string result; if (Type == n) - { - result = "_> "; - convert_command_glyph(result); - } + result = convert_command_glyph("_> "); else { for (unsigned i = 0; (MAX > i) && m_filters[i]; ++i) { if (m_filters[i]->get_type() == n) { - result = util::string_format("@custom%u ", i + 1); - convert_command_glyph(result); + result = convert_command_glyph(util::string_format("@custom%u ", i + 1)); break; } } @@ -307,12 +342,12 @@ public: protected: composite_filter_impl_base() { } - void populate(char const *value, emu_file *file, unsigned indent) + void populate(char const *value, util::core_file *file, unsigned indent) { // try to load filters from a file if (value && file) { - unsigned const cnt(unsigned((std::max)(std::min(int(MAX), std::atoi(value)), 0))); + unsigned const cnt(std::clamp<int>(std::atoi(value), 0, MAX)); for (unsigned i = 0; cnt > i; ++i) { typename Base::ptr flt(static_cast<Impl &>(*this).create(*file, indent + 1)); @@ -346,21 +381,12 @@ private: , m_handler(std::move(handler)) , m_added(false) { + set_process_flags(PROCESS_LR_REPEAT); + set_heading(_("Select Filters")); } virtual ~menu_configure() override { m_handler(m_parent); } - protected: - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override - { - char const *const text[] = { _("Select custom filters:") }; - draw_text_box( - std::begin(text), std::end(text), - x, x2, y - top, y - ui().box_tb_border(), - ui::text_layout::CENTER, ui::text_layout::NEVER, false, - ui().colors().text_color(), UI_GREEN_COLOR, 1.0f); - } - private: enum : uintptr_t { @@ -372,8 +398,8 @@ private: ADD_FILTER }; - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; + virtual bool handle(event const *ev) override; bool set_filter_type(unsigned pos, typename Base::type n) { @@ -498,7 +524,7 @@ void composite_filter_impl_base<Impl, Base, Type>::show_ui( template <class Impl, class Base, typename Base::type Type> -void composite_filter_impl_base<Impl, Base, Type>::menu_configure::populate(float &customtop, float &custombottom) +void composite_filter_impl_base<Impl, Base, Type>::menu_configure::populate() { // add items for each active filter unsigned i = 0; @@ -509,8 +535,7 @@ void composite_filter_impl_base<Impl, Base, Type>::menu_configure::populate(floa set_selected_index(item_count() - 2); if (m_parent.m_filters[i]->wants_adjuster()) { - std::string name("^!"); - convert_command_glyph(name); + std::string name(convert_command_glyph("^!")); item_append(name, m_parent.m_filters[i]->adjust_text(), m_parent.m_filters[i]->arrow_flags(), (void *)(ADJUST_FIRST + i)); } item_append(menu_item_type::SEPARATOR); @@ -519,120 +544,118 @@ void composite_filter_impl_base<Impl, Base, Type>::menu_configure::populate(floa // add remove/add handlers if (1 < i) - item_append(_("Remove last filter"), "", 0, (void *)REMOVE_FILTER); + item_append(_("Remove last filter"), 0, (void *)REMOVE_FILTER); if (MAX > i) - item_append(_("Add filter"), "", 0, (void *)ADD_FILTER); + item_append(_("Add filter"), 0, (void *)ADD_FILTER); item_append(menu_item_type::SEPARATOR); - - // leave space for heading - customtop = ui().get_line_height() + 3.0f * ui().box_tb_border(); } template <class Impl, class Base, typename Base::type Type> -void composite_filter_impl_base<Impl, Base, Type>::menu_configure::handle() +bool composite_filter_impl_base<Impl, Base, Type>::menu_configure::handle(event const *ev) { - const event *menu_event = process(PROCESS_LR_REPEAT); - if (menu_event && menu_event->itemref) + if (!ev || !ev->itemref) + return false; + + m_added = false; + bool changed(false); + uintptr_t const ref(reinterpret_cast<uintptr_t>(ev->itemref)); + switch (ev->iptkey) { - m_added = false; - bool changed(false); - uintptr_t const ref(reinterpret_cast<uintptr_t>(menu_event->itemref)); - switch (menu_event->iptkey) + case IPT_UI_LEFT: + case IPT_UI_RIGHT: + if ((FILTER_FIRST <= ref) && (FILTER_LAST >= ref)) { - case IPT_UI_LEFT: - case IPT_UI_RIGHT: - if ((FILTER_FIRST <= ref) && (FILTER_LAST >= ref)) + // change filter type + unsigned const pos(ref - FILTER_FIRST); + typename Base::type const current(m_parent.m_filters[pos]->get_type()); + if (IPT_UI_LEFT == ev->iptkey) { - // change filter type - unsigned const pos(ref - FILTER_FIRST); - typename Base::type const current(m_parent.m_filters[pos]->get_type()); - if (IPT_UI_LEFT == menu_event->iptkey) - { - typename Base::type n(current); - while ((Base::FIRST < n) && !changed) - { - if (m_parent.check_type(pos, --n)) - changed = set_filter_type(pos, n); - } - } - else + typename Base::type n(current); + while ((Base::FIRST < n) && !changed) { - typename Base::type n(current); - while ((Base::LAST > n) && !changed) - { - if (m_parent.check_type(pos, ++n)) - changed = set_filter_type(pos, n); - } + if (m_parent.check_type(pos, --n)) + changed = set_filter_type(pos, n); } } - else if ((ADJUST_FIRST <= ref) && (ADJUST_LAST >= ref)) + else { - // change filter value - Base &pos(*m_parent.m_filters[ref - ADJUST_FIRST]); - changed = (IPT_UI_LEFT == menu_event->iptkey) ? pos.adjust_left() : pos.adjust_right(); + typename Base::type n(current); + while ((Base::LAST > n) && !changed) + { + if (m_parent.check_type(pos, ++n)) + changed = set_filter_type(pos, n); + } } - break; + } + else if ((ADJUST_FIRST <= ref) && (ADJUST_LAST >= ref)) + { + // change filter value + Base &pos(*m_parent.m_filters[ref - ADJUST_FIRST]); + changed = (IPT_UI_LEFT == ev->iptkey) ? pos.adjust_left() : pos.adjust_right(); + } + break; - case IPT_UI_SELECT: - if ((FILTER_FIRST <= ref) && (FILTER_LAST >= ref)) + case IPT_UI_SELECT: + if ((FILTER_FIRST <= ref) && (FILTER_LAST >= ref)) + { + // show selector with non-contradictory types + std::vector<typename Base::type> types; + std::vector<std::string> names; + types.reserve(Base::COUNT); + names.reserve(Base::COUNT); + int sel(-1); + unsigned const pos(ref - FILTER_FIRST); + typename Base::type const current(m_parent.m_filters[pos]->get_type()); + for (typename Base::type candidate = Base::FIRST; Base::COUNT > candidate; ++candidate) { - // show selector with non-contradictory types - std::vector<typename Base::type> types; - std::vector<std::string> names; - types.reserve(Base::COUNT); - names.reserve(Base::COUNT); - int sel(-1); - unsigned const pos(ref - FILTER_FIRST); - typename Base::type const current(m_parent.m_filters[pos]->get_type()); - for (typename Base::type candidate = Base::FIRST; Base::COUNT > candidate; ++candidate) + if (Impl::type_allowed(pos, candidate)) { - if (Impl::type_allowed(pos, candidate)) + if (current == candidate) + sel = types.size(); + unsigned i = 0; + while ((MAX > i) && m_parent.m_filters[i] && ((pos == i) || !Impl::types_contradictory(m_parent.m_filters[i]->get_type(), candidate))) + ++i; + if ((MAX <= i) || !m_parent.m_filters[i]) { - if (current == candidate) - sel = types.size(); - unsigned i = 0; - while ((MAX > i) && m_parent.m_filters[i] && ((pos == i) || !Impl::types_contradictory(m_parent.m_filters[i]->get_type(), candidate))) - ++i; - if ((MAX <= i) || !m_parent.m_filters[i]) - { - types.emplace_back(candidate); - names.emplace_back(Base::display_name(candidate)); - } + types.emplace_back(candidate); + names.emplace_back(Base::display_name(candidate)); } } - menu::stack_push<menu_selector>( - ui(), - container(), - std::move(names), - sel, - [this, pos, t = std::move(types)] (int selection) - { - if (set_filter_type(pos, t[selection])) - reset(reset_options::REMEMBER_REF); - }); - } - else if ((ADJUST_FIRST <= ref) && (ADJUST_LAST >= ref)) - { - // show selected filter's UI - m_parent.m_filters[ref - ADJUST_FIRST]->show_ui(ui(), container(), [this] (Base &filter) { reset(reset_options::REMEMBER_REF); }); - } - else if (REMOVE_FILTER == ref) - { - changed = drop_last_filter(); - } - else if (ADD_FILTER == ref) - { - m_added = append_filter(); } - break; + menu::stack_push<menu_selector>( + ui(), + container(), + std::string(ev->item->text()), + std::move(names), + sel, + [this, pos, t = std::move(types)] (int selection) + { + if (set_filter_type(pos, t[selection])) + reset(reset_options::REMEMBER_REF); + }); } - - // rebuild if anything changed - if (changed) - reset(reset_options::REMEMBER_REF); - else if (m_added) - reset(reset_options::SELECT_FIRST); + else if ((ADJUST_FIRST <= ref) && (ADJUST_LAST >= ref)) + { + // show selected filter's UI + m_parent.m_filters[ref - ADJUST_FIRST]->show_ui(ui(), container(), [this] (Base &filter) { reset(reset_options::REMEMBER_REF); }); + } + else if (REMOVE_FILTER == ref) + { + changed = drop_last_filter(); + } + else if (ADD_FILTER == ref) + { + m_added = append_filter(); + } + break; } + + // rebuild if anything changed + if (changed) + reset(reset_options::REMEMBER_REF); + else if (m_added) + reset(reset_options::SELECT_FIRST); + return false; } @@ -645,7 +668,7 @@ template <machine_filter::type Type = machine_filter::AVAILABLE> class available_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type> { public: - available_machine_filter_impl(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { } + available_machine_filter_impl(machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { } virtual bool apply(ui_system_info const &system) const override { return system.available; } }; @@ -655,9 +678,9 @@ template <machine_filter::type Type = machine_filter::WORKING> class working_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type> { public: - working_machine_filter_impl(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { } + working_machine_filter_impl(machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { } - virtual bool apply(ui_system_info const &system) const override { return !(system.driver->flags & machine_flags::NOT_WORKING); } + virtual bool apply(ui_system_info const &system) const override { return !(system.driver->type.emulation_flags() & device_t::flags::NOT_WORKING); } }; @@ -665,7 +688,7 @@ template <machine_filter::type Type = machine_filter::MECHANICAL> class mechanical_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type> { public: - mechanical_machine_filter_impl(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { } + mechanical_machine_filter_impl(machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { } virtual bool apply(ui_system_info const &system) const override { return system.driver->flags & machine_flags::MECHANICAL; } }; @@ -675,7 +698,7 @@ template <machine_filter::type Type = machine_filter::BIOS> class bios_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type> { public: - bios_machine_filter_impl(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { } + bios_machine_filter_impl(machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { } virtual bool apply(ui_system_info const &system) const override { return system.driver->flags & machine_flags::IS_BIOS_ROOT; } }; @@ -685,7 +708,7 @@ template <machine_filter::type Type = machine_filter::PARENTS> class parents_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type> { public: - parents_machine_filter_impl(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { } + parents_machine_filter_impl(machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { } virtual bool apply(ui_system_info const &system) const override { @@ -700,7 +723,7 @@ template <machine_filter::type Type = machine_filter::CHD> class chd_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type> { public: - chd_machine_filter_impl(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { } + chd_machine_filter_impl(machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { } virtual bool apply(ui_system_info const &system) const override { @@ -718,9 +741,9 @@ template <machine_filter::type Type = machine_filter::SAVE> class save_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type> { public: - save_machine_filter_impl(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { } + save_machine_filter_impl(machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { } - virtual bool apply(ui_system_info const &system) const override { return system.driver->flags & machine_flags::SUPPORTS_SAVE; } + virtual bool apply(ui_system_info const &system) const override { return !(system.driver->type.emulation_flags() & device_t::flags::SAVE_UNSUPPORTED); } }; @@ -728,7 +751,7 @@ template <machine_filter::type Type = machine_filter::VERTICAL> class vertical_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type> { public: - vertical_machine_filter_impl(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { } + vertical_machine_filter_impl(machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { } virtual bool apply(ui_system_info const &system) const override { return system.driver->flags & machine_flags::SWAP_XY; } }; @@ -742,7 +765,7 @@ public: class manufacturer_machine_filter : public choice_filter_impl_base<machine_filter, machine_filter::MANUFACTURER> { public: - manufacturer_machine_filter(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) + manufacturer_machine_filter(machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) : choice_filter_impl_base<machine_filter, machine_filter::MANUFACTURER>(data.manufacturers(), value) { } @@ -763,7 +786,7 @@ public: class year_machine_filter : public choice_filter_impl_base<machine_filter, machine_filter::YEAR> { public: - year_machine_filter(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) + year_machine_filter(machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) : choice_filter_impl_base<machine_filter, machine_filter::YEAR>(data.years(), value) { } @@ -772,6 +795,18 @@ public: }; +class source_file_machine_filter : public choice_filter_impl_base<machine_filter, machine_filter::SOURCE_FILE> +{ +public: + source_file_machine_filter(machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) + : choice_filter_impl_base<machine_filter, machine_filter::SOURCE_FILE>(data.source_files(), value) + { + } + + virtual bool apply(ui_system_info const &system) const override { return !have_choices() || (selection_valid() && (selection_text() == info_xml_creator::format_sourcefile(system.driver->type.source()))); } +}; + + //------------------------------------------------- // complementary machine filters @@ -781,7 +816,7 @@ template <template <machine_filter::type T> class Base, machine_filter::type Typ class inverted_machine_filter : public Base<Type> { public: - inverted_machine_filter(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) + inverted_machine_filter(machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) : Base<Type>(data, value, file, indent) { } @@ -817,7 +852,7 @@ template <machine_filter::type Type> class inclusive_machine_filter_impl : public simple_filter_impl_base<machine_filter, Type> { public: - inclusive_machine_filter_impl(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) { } + inclusive_machine_filter_impl(machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { } virtual bool apply(ui_system_info const &system) const override { return true; } }; @@ -834,7 +869,7 @@ using favorite_machine_filter = inclusive_machine_filter_impl<machine_filt class category_machine_filter : public simple_filter_impl_base<machine_filter, machine_filter::CATEGORY> { public: - category_machine_filter(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) + category_machine_filter(machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) : m_ini(0) , m_group(0) , m_include_clones(false) @@ -845,21 +880,18 @@ public: inifile_manager const &mgr(mame_machine_manager::instance()->inifile()); if (value) { - char const *const split(std::strchr(value, '/')); - std::string ini; - if (split) - ini.assign(value, split); - else - ini.assign(value); + std::string_view const s(value); + std::string_view::size_type const split(s.find('/')); + std::string_view const ini(s.substr(0, split)); for (unsigned i = 0; mgr.get_file_count() > i; ++i) { if (mgr.get_file_name(i) == ini) { m_ini = i; - if (split) + if (std::string_view::npos != split) { - std::string const group(split + 1); + std::string_view const group(s.substr(split + 1)); for (unsigned j = 0; mgr.get_category_count(i) > j; ++j) { if (mgr.get_category_name(i, j) == group) @@ -891,10 +923,10 @@ public: virtual bool wants_adjuster() const override { return mame_machine_manager::instance()->inifile().get_file_count(); } virtual char const *adjust_text() const override { return m_adjust_text.c_str(); } - virtual void save_ini(emu_file &file, unsigned indent) const override + virtual void save_ini(util::core_file &file, unsigned indent) const override { char const *const text(filter_text()); - file.puts(util::string_format("%2$*1$s%3$s = %4$s\n", 2 * indent, "", this->config_name(), text ? text : "").c_str()); + file.puts(util::string_format("%2$*1$s%3$s = %4$s\n", 2 * indent, "", this->config_name(), text ? text : "")); } virtual bool apply(ui_system_info const &system) const override @@ -915,7 +947,7 @@ public: if (m_include_clones) { int const found(driver_list::find(system.driver->parent)); - return m_cache.end() != m_cache.find(&driver_list::driver(found)); + return found >= 0 && m_cache.end() != m_cache.find(&driver_list::driver(found)); } return false; @@ -936,6 +968,9 @@ private: , m_state(std::make_unique<std::pair<unsigned, bool> []>(mame_machine_manager::instance()->inifile().get_file_count())) , m_ini(parent.m_ini) { + set_process_flags(PROCESS_LR_REPEAT); + set_heading("Select Category"); + inifile_manager const &mgr(mame_machine_manager::instance()->inifile()); for (size_t i = 0; mgr.get_file_count() > i; ++i) { @@ -960,17 +995,6 @@ private: m_handler(m_parent); } - protected: - virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override - { - char const *const text[] = { _("Select category:") }; - draw_text_box( - std::begin(text), std::end(text), - x, x2, y - top, y - ui().box_tb_border(), - ui::text_layout::CENTER, ui::text_layout::NEVER, false, - ui().colors().text_color(), UI_GREEN_COLOR, 1.0f); - } - private: enum : uintptr_t { @@ -979,8 +1003,8 @@ private: INCLUDE_CLONES }; - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; + virtual bool handle(event const *ev) override; category_machine_filter &m_parent; std::function<void (machine_filter &)> m_handler; @@ -1012,9 +1036,10 @@ private: } } - static bool include_clones_default(std::string const &name) + static bool include_clones_default(std::string_view name) { - return !core_stricmp(name.c_str(), "category.ini") || !core_stricmp(name.c_str(), "alltime.ini"); + using namespace std::literals; + return util::streqlower(name, "category.ini"sv) || util::streqlower(name, "alltime.ini"sv); } unsigned m_ini, m_group; @@ -1030,13 +1055,13 @@ void category_machine_filter::show_ui(mame_ui_manager &mui, render_container &co } -void category_machine_filter::menu_configure::populate(float &customtop, float &custombottom) +void category_machine_filter::menu_configure::populate() { inifile_manager const &mgr(mame_machine_manager::instance()->inifile()); unsigned const filecnt(mgr.get_file_count()); if (!filecnt) { - item_append(_("No category INI files found"), "", FLAG_DISABLE, nullptr); + item_append(_("No category INI files found"), FLAG_DISABLE, nullptr); } else { @@ -1045,7 +1070,7 @@ void category_machine_filter::menu_configure::populate(float &customtop, float & unsigned const groupcnt(mgr.get_category_count(m_ini)); if (!groupcnt) { - item_append(_("No groups found in category file"), "", FLAG_DISABLE, nullptr); + item_append(_("No groups found in category file"), FLAG_DISABLE, nullptr); } else { @@ -1055,107 +1080,108 @@ void category_machine_filter::menu_configure::populate(float &customtop, float & } } item_append(menu_item_type::SEPARATOR); - customtop = ui().get_line_height() + 3.0f * ui().box_tb_border(); } -void category_machine_filter::menu_configure::handle() +bool category_machine_filter::menu_configure::handle(event const *ev) { - const event *menu_event = process(PROCESS_LR_REPEAT); - if (menu_event && menu_event->itemref) + if (!ev || !ev->itemref) + return false; + + bool changed(false); + uintptr_t const ref(reinterpret_cast<uintptr_t>(ev->itemref)); + inifile_manager const &mgr(mame_machine_manager::instance()->inifile()); + switch (ev->iptkey) { - bool changed(false); - uintptr_t const ref(reinterpret_cast<uintptr_t>(menu_event->itemref)); - inifile_manager const &mgr(mame_machine_manager::instance()->inifile()); - switch (menu_event->iptkey) + case IPT_UI_LEFT: + if ((INI_FILE == ref) && m_ini) { - case IPT_UI_LEFT: - if ((INI_FILE == ref) && m_ini) - { - --m_ini; - changed = true; - } - else if ((SYSTEM_GROUP == ref) && m_state[m_ini].first) - { - --m_state[m_ini].first; - changed = true; - } - else if ((INCLUDE_CLONES == ref) && m_state[m_ini].second) - { - m_state[m_ini].second = false; - changed = true; - } - break; - case IPT_UI_RIGHT: - if ((INI_FILE == ref) && (mgr.get_file_count() > (m_ini + 1))) - { - ++m_ini; - changed = true; - } - else if ((SYSTEM_GROUP == ref) && (mgr.get_category_count(m_ini) > (m_state[m_ini].first + 1))) - { - ++m_state[m_ini].first; - changed = true; - } - else if ((INCLUDE_CLONES == ref) && !m_state[m_ini].second) - { - m_state[m_ini].second = true; - changed = true; - } - break; + --m_ini; + changed = true; + } + else if ((SYSTEM_GROUP == ref) && m_state[m_ini].first) + { + --m_state[m_ini].first; + changed = true; + } + else if ((INCLUDE_CLONES == ref) && m_state[m_ini].second) + { + m_state[m_ini].second = false; + changed = true; + } + break; + case IPT_UI_RIGHT: + if ((INI_FILE == ref) && (mgr.get_file_count() > (m_ini + 1))) + { + ++m_ini; + changed = true; + } + else if ((SYSTEM_GROUP == ref) && (mgr.get_category_count(m_ini) > (m_state[m_ini].first + 1))) + { + ++m_state[m_ini].first; + changed = true; + } + else if ((INCLUDE_CLONES == ref) && !m_state[m_ini].second) + { + m_state[m_ini].second = true; + changed = true; + } + break; - case IPT_UI_SELECT: - if (INI_FILE == ref) - { - std::vector<std::string> choices; - choices.reserve(mgr.get_file_count()); - for (size_t i = 0; mgr.get_file_count() > i; ++i) - choices.emplace_back(mgr.get_file_name(i)); - menu::stack_push<menu_selector>( - ui(), - container(), - std::move(choices), - m_ini, - [this] (int selection) + case IPT_UI_SELECT: + if (INI_FILE == ref) + { + std::vector<std::string> choices; + choices.reserve(mgr.get_file_count()); + for (size_t i = 0; mgr.get_file_count() > i; ++i) + choices.emplace_back(mgr.get_file_name(i)); + menu::stack_push<menu_selector>( + ui(), + container(), + _("Category File"), + std::move(choices), + m_ini, + [this] (int selection) + { + if (selection != m_ini) { - if (selection != m_ini) - { - m_ini = selection; - reset(reset_options::REMEMBER_REF); - } - }); - } - else if (SYSTEM_GROUP == ref) - { - std::vector<std::string> choices; - choices.reserve(mgr.get_category_count(m_ini)); - for (size_t i = 0; mgr.get_category_count(m_ini) > i; ++i) - choices.emplace_back(mgr.get_category_name(m_ini, i)); - menu::stack_push<menu_selector>( - ui(), - container(), - std::move(choices), - m_state[m_ini].first, - [this] (int selection) + m_ini = selection; + reset(reset_options::REMEMBER_REF); + } + }); + } + else if (SYSTEM_GROUP == ref) + { + std::vector<std::string> choices; + choices.reserve(mgr.get_category_count(m_ini)); + for (size_t i = 0; mgr.get_category_count(m_ini) > i; ++i) + choices.emplace_back(mgr.get_category_name(m_ini, i)); + menu::stack_push<menu_selector>( + ui(), + container(), + _("Group"), + std::move(choices), + m_state[m_ini].first, + [this] (int selection) + { + if (selection != m_state[m_ini].first) { - if (selection != m_state[m_ini].first) - { - m_state[m_ini].first = selection; - reset(reset_options::REMEMBER_REF); - } - }); - } - else if (INCLUDE_CLONES == ref) - { - m_state[m_ini].second = !m_state[m_ini].second; - reset(reset_options::REMEMBER_REF); - } - break; + m_state[m_ini].first = selection; + reset(reset_options::REMEMBER_REF); + } + }); } - - // rebuild if anything changed - if (changed) + else if (INCLUDE_CLONES == ref) + { + m_state[m_ini].second = !m_state[m_ini].second; reset(reset_options::REMEMBER_REF); + } + break; } + + // rebuild if anything changed + if (changed) + reset(reset_options::REMEMBER_REF); + return false; } @@ -1167,7 +1193,7 @@ void category_machine_filter::menu_configure::handle() class custom_machine_filter : public composite_filter_impl_base<custom_machine_filter, machine_filter, machine_filter::CUSTOM> { public: - custom_machine_filter(machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) + custom_machine_filter(machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) : composite_filter_impl_base<custom_machine_filter, machine_filter, machine_filter::CUSTOM>() , m_data(data) { @@ -1175,7 +1201,7 @@ public: } ptr create(type n) const { return machine_filter::create(n, m_data); } - ptr create(emu_file &file, unsigned indent) const { return machine_filter::create(file, m_data, indent); } + ptr create(util::core_file &file, unsigned indent) const { return machine_filter::create(file, m_data, indent); } static bool type_allowed(unsigned pos, type n) { @@ -1208,6 +1234,7 @@ public: case FAVORITE: case MANUFACTURER: case YEAR: + case SOURCE_FILE: case CUSTOM: case COUNT: break; @@ -1217,7 +1244,17 @@ public: static bool is_inclusion(type n) { - return (CATEGORY == n) || (MANUFACTURER == n) || (YEAR == n); + switch (n) + { + case CATEGORY: + case MANUFACTURER: + case YEAR: + case SOURCE_FILE: + return true; + + default: + return false; + } } private: @@ -1233,7 +1270,7 @@ private: class all_software_filter : public simple_filter_impl_base<software_filter, software_filter::ALL> { public: - all_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent) { } + all_software_filter(software_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { } virtual bool apply(ui_software_info const &info) const override { return true; } }; @@ -1242,7 +1279,7 @@ public: class available_software_filter : public simple_filter_impl_base<software_filter, software_filter::AVAILABLE> { public: - available_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent) { } + available_software_filter(software_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { } virtual bool apply(ui_software_info const &info) const override { return info.available; } }; @@ -1251,7 +1288,7 @@ public: class unavailable_software_filter : public simple_filter_impl_base<software_filter, software_filter::UNAVAILABLE> { public: - unavailable_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent) { } + unavailable_software_filter(software_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { } virtual bool apply(ui_software_info const &info) const override { return !info.available; } }; @@ -1260,7 +1297,7 @@ public: class favorite_software_filter : public simple_filter_impl_base<software_filter, software_filter::FAVORITE> { public: - favorite_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent) + favorite_software_filter(software_filter_data const &data, char const *value, util::core_file *file, unsigned indent) : m_manager(mame_machine_manager::instance()->favorite()) { } @@ -1275,7 +1312,7 @@ private: class parents_software_filter : public simple_filter_impl_base<software_filter, software_filter::PARENTS> { public: - parents_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent) { } + parents_software_filter(software_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { } virtual bool apply(ui_software_info const &info) const override { return info.parentname.empty(); } }; @@ -1284,7 +1321,7 @@ public: class clones_software_filter : public simple_filter_impl_base<software_filter, software_filter::CLONES> { public: - clones_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent) { } + clones_software_filter(software_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { } virtual bool apply(ui_software_info const &info) const override { return !info.parentname.empty(); } }; @@ -1293,7 +1330,7 @@ public: class years_software_filter : public choice_filter_impl_base<software_filter, software_filter::YEAR> { public: - years_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent) + years_software_filter(software_filter_data const &data, char const *value, util::core_file *file, unsigned indent) : choice_filter_impl_base<software_filter, software_filter::YEAR>(data.years(), value) { } @@ -1305,7 +1342,7 @@ public: class publishers_software_filter : public choice_filter_impl_base<software_filter, software_filter::PUBLISHERS> { public: - publishers_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent) + publishers_software_filter(software_filter_data const &data, char const *value, util::core_file *file, unsigned indent) : choice_filter_impl_base<software_filter, software_filter::PUBLISHERS>(data.publishers(), value) { } @@ -1326,9 +1363,9 @@ public: class supported_software_filter : public simple_filter_impl_base<software_filter, software_filter::SUPPORTED> { public: - supported_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent) { } + supported_software_filter(software_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { } - virtual bool apply(ui_software_info const &info) const override { return SOFTWARE_SUPPORTED_YES == info.supported; } + virtual bool apply(ui_software_info const &info) const override { return software_support::SUPPORTED == info.supported; } }; @@ -1336,25 +1373,25 @@ public: class partial_supported_software_filter : public simple_filter_impl_base<software_filter, software_filter::PARTIAL_SUPPORTED> { public: - partial_supported_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent) { } + partial_supported_software_filter(software_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { } - virtual bool apply(ui_software_info const &info) const override { return SOFTWARE_SUPPORTED_PARTIAL == info.supported; } + virtual bool apply(ui_software_info const &info) const override { return software_support::PARTIALLY_SUPPORTED == info.supported; } }; class unsupported_software_filter : public simple_filter_impl_base<software_filter, software_filter::UNSUPPORTED> { public: - unsupported_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent) { } + unsupported_software_filter(software_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { } - virtual bool apply(ui_software_info const &info) const override { return SOFTWARE_SUPPORTED_NO == info.supported; } + virtual bool apply(ui_software_info const &info) const override { return software_support::UNSUPPORTED == info.supported; } }; class region_software_filter : public choice_filter_impl_base<software_filter, software_filter::REGION> { public: - region_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent) + region_software_filter(software_filter_data const &data, char const *value, util::core_file *file, unsigned indent) : choice_filter_impl_base<software_filter, software_filter::REGION>(data.regions(), value) { } @@ -1375,7 +1412,7 @@ public: class device_type_software_filter : public choice_filter_impl_base<software_filter, software_filter::DEVICE_TYPE> { public: - device_type_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent) + device_type_software_filter(software_filter_data const &data, char const *value, util::core_file *file, unsigned indent) : choice_filter_impl_base<software_filter, software_filter::DEVICE_TYPE>(data.device_types(), value) { } @@ -1387,7 +1424,7 @@ public: class list_software_filter : public choice_filter_impl_base<software_filter, software_filter::LIST> { public: - list_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent) + list_software_filter(software_filter_data const &data, char const *value, util::core_file *file, unsigned indent) : choice_filter_impl_base<software_filter, software_filter::LIST>(data.list_descriptions(), value) , m_data(data) { @@ -1405,13 +1442,100 @@ private: //------------------------------------------------- +// software info filters +//------------------------------------------------- + +template <software_filter::type Type> +class software_info_filter_base : public choice_filter_impl_base<software_filter, Type> +{ +public: + virtual bool apply(ui_software_info const &info) const override + { + if (!this->have_choices()) + { + return true; + } + else if (!this->selection_valid()) + { + return false; + } + else + { + auto const found( + std::find_if( + info.info.begin(), + info.info.end(), + [this] (software_info_item const &i) { return this->apply(i); })); + return info.info.end() != found; + } + } + +protected: + software_info_filter_base(char const *type, std::vector<std::string> const &choices, char const *value) + : choice_filter_impl_base<software_filter, Type>(choices, value) + , m_info_type(type) + { + } + +private: + bool apply(software_info_item const &info) const + { + return (info.name() == m_info_type) && (info.value() == this->selection_text()); + } + + char const *const m_info_type; +}; + + +class developer_software_filter : public software_info_filter_base<software_filter::DEVELOPERS> +{ +public: + developer_software_filter(software_filter_data const &data, char const *value, util::core_file *file, unsigned indent) + : software_info_filter_base<software_filter::DEVELOPERS>("developer", data.developers(), value) + { + } +}; + + +class distributor_software_filter : public software_info_filter_base<software_filter::DISTRIBUTORS> +{ +public: + distributor_software_filter(software_filter_data const &data, char const *value, util::core_file *file, unsigned indent) + : software_info_filter_base<software_filter::DISTRIBUTORS>("distributor", data.distributors(), value) + { + } +}; + + +class author_software_filter : public software_info_filter_base<software_filter::AUTHORS> +{ +public: + author_software_filter(software_filter_data const &data, char const *value, util::core_file *file, unsigned indent) + : software_info_filter_base<software_filter::AUTHORS>("author", data.authors(), value) + { + } +}; + + +class programmer_software_filter : public software_info_filter_base<software_filter::PROGRAMMERS> +{ +public: + programmer_software_filter(software_filter_data const &data, char const *value, util::core_file *file, unsigned indent) + : software_info_filter_base<software_filter::PROGRAMMERS>("programmer", data.programmers(), value) + { + } +}; + + + +//------------------------------------------------- // composite software filter //------------------------------------------------- class custom_software_filter : public composite_filter_impl_base<custom_software_filter, software_filter, software_filter::CUSTOM> { public: - custom_software_filter(software_filter_data const &data, char const *value, emu_file *file, unsigned indent) + custom_software_filter(software_filter_data const &data, char const *value, util::core_file *file, unsigned indent) : composite_filter_impl_base<custom_software_filter, software_filter, software_filter::CUSTOM>() , m_data(data) { @@ -1419,7 +1543,7 @@ public: } ptr create(type n) const { return software_filter::create(n, m_data); } - ptr create(emu_file &file, unsigned indent) const { return software_filter::create(file, m_data, indent); } + ptr create(util::core_file &file, unsigned indent) const { return software_filter::create(file, m_data, indent); } static bool type_allowed(unsigned pos, type n) { @@ -1442,6 +1566,10 @@ public: case FAVORITE: case YEAR: case PUBLISHERS: + case DEVELOPERS: + case DISTRIBUTORS: + case AUTHORS: + case PROGRAMMERS: case REGION: case DEVICE_TYPE: case LIST: @@ -1454,7 +1582,15 @@ public: static bool is_inclusion(type n) { - return (YEAR == n) || (PUBLISHERS == n) || (REGION == n) || (DEVICE_TYPE == n) || (LIST == n); + return (YEAR == n) + || (PUBLISHERS == n) + || (DEVELOPERS == n) + || (DISTRIBUTORS == n) + || (AUTHORS == n) + || (PROGRAMMERS == n) + || (REGION == n) + || (DEVICE_TYPE == n) + || (LIST == n); } private: @@ -1484,10 +1620,21 @@ void machine_filter_data::add_year(std::string const &year) m_years.emplace(pos, year); } +void machine_filter_data::add_source_file(std::string_view path) +{ + std::vector<std::string>::iterator const pos(std::lower_bound(m_source_files.begin(), m_source_files.end(), path)); + if ((m_source_files.end() == pos) || (*pos != path)) + m_source_files.emplace(pos, path); +} + void machine_filter_data::finalise() { + for (std::string &path : m_source_files) + path = info_xml_creator::format_sourcefile(path); + std::stable_sort(m_manufacturers.begin(), m_manufacturers.end()); std::stable_sort(m_years.begin(), m_years.end()); + std::stable_sort(m_source_files.begin(), m_source_files.end()); } std::string machine_filter_data::extract_manufacturer(std::string const &manufacturer) @@ -1528,7 +1675,7 @@ std::string machine_filter_data::get_config_string() const } } -bool machine_filter_data::load_ini(emu_file &file) +bool machine_filter_data::load_ini(util::core_file &file) { machine_filter::ptr flt(machine_filter::create(file, *this)); if (flt) @@ -1552,31 +1699,35 @@ bool machine_filter_data::load_ini(emu_file &file) void software_filter_data::add_region(std::string const &longname) { std::string name(extract_region(longname)); - std::vector<std::string>::iterator const pos(std::lower_bound(m_regions.begin(), m_regions.end(), name)); - if ((m_regions.end() == pos) || (*pos != name)) - m_regions.emplace(pos, std::move(name)); + add_info_value(m_regions, std::move(name)); } void software_filter_data::add_publisher(std::string const &publisher) { std::string name(extract_publisher(publisher)); - std::vector<std::string>::iterator const pos(std::lower_bound(m_publishers.begin(), m_publishers.end(), name)); - if ((m_publishers.end() == pos) || (*pos != name)) - m_publishers.emplace(pos, std::move(name)); + add_info_value(m_publishers, std::move(name)); } void software_filter_data::add_year(std::string const &year) { - std::vector<std::string>::iterator const pos(std::lower_bound(m_years.begin(), m_years.end(), year)); - if ((m_years.end() == pos) || (*pos != year)) - m_years.emplace(pos, year); + add_info_value(m_years, year); +} + +void software_filter_data::add_info(software_info_item const &info) +{ + if (info.name() == "developer") + add_info_value(m_developers, info.value()); + else if (info.name() == "distributor") + add_info_value(m_distributors, info.value()); + else if (info.name() == "author") + add_info_value(m_authors, info.value()); + else if (info.name() == "programmer") + add_info_value(m_programmers, info.value()); } void software_filter_data::add_device_type(std::string const &device_type) { - std::vector<std::string>::iterator const pos(std::lower_bound(m_device_types.begin(), m_device_types.end(), device_type)); - if ((m_device_types.end() == pos) || (*pos != device_type)) - m_device_types.emplace(pos, device_type); + add_info_value(m_device_types, device_type); } void software_filter_data::add_list(std::string const &name, std::string const &description) @@ -1595,13 +1746,12 @@ void software_filter_data::finalise() std::string software_filter_data::extract_region(std::string const &longname) { - std::string fullname(longname); - strmakelower(fullname); + std::string fullname(strmakelower(longname)); std::string::size_type const found(fullname.find('(')); if (found != std::string::npos) { std::string::size_type const ends(fullname.find_first_not_of("abcdefghijklmnopqrstuvwxyz", found + 1)); - std::string const temp(fullname.substr(found + 1, ends - found - 1)); + std::string_view const temp(std::string_view(fullname).substr(found + 1, ends - found - 1)); auto const match(std::find_if( std::begin(SOFTWARE_REGIONS), std::end(SOFTWARE_REGIONS), @@ -1624,7 +1774,7 @@ std::string software_filter_data::extract_publisher(std::string const &publisher // public machine filter interface //------------------------------------------------- -machine_filter::ptr machine_filter::create(type n, machine_filter_data const &data, char const *value, emu_file *file, unsigned indent) +machine_filter::ptr machine_filter::create(type n, machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { assert(COUNT > n); switch (n) @@ -1659,6 +1809,8 @@ machine_filter::ptr machine_filter::create(type n, machine_filter_data const &da return std::make_unique<manufacturer_machine_filter>(data, value, file, indent); case YEAR: return std::make_unique<year_machine_filter>(data, value, file, indent); + case SOURCE_FILE: + return std::make_unique<source_file_machine_filter>(data, value, file, indent); case SAVE: return std::make_unique<save_machine_filter>(data, value, file, indent); case NOSAVE: @@ -1679,26 +1831,26 @@ machine_filter::ptr machine_filter::create(type n, machine_filter_data const &da return nullptr; } -machine_filter::ptr machine_filter::create(emu_file &file, machine_filter_data const &data, unsigned indent) +machine_filter::ptr machine_filter::create(util::core_file &file, machine_filter_data const &data, unsigned indent) { char buffer[MAX_CHAR_INFO]; - if (!file.gets(buffer, ARRAY_LENGTH(buffer))) + if (!file.gets(buffer, std::size(buffer))) return nullptr; // split it into a key/value or bail - std::string key(buffer); - for (std::string::size_type i = 0; (2 * indent) > i; ++i) + std::string_view key(buffer); + for (std::string_view::size_type i = 0; (2 * indent) > i; ++i) { if ((key.length() <= i) || (' ' != key[i])) return nullptr; } key = key.substr(2 * indent); - std::string::size_type const split(key.find(" = ")); - if (std::string::npos == split) + std::string_view::size_type const split(key.find(" = ")); + if (std::string_view::npos == split) return nullptr; - std::string::size_type const nl(key.find_first_of("\r\n", split)); - std::string const value(key.substr(split + 3, (std::string::npos == nl) ? nl : (nl - split - 3))); - key.resize(split); + std::string_view::size_type const nl(key.find_first_of("\r\n", split)); + std::string const value(key.substr(split + 3, (std::string_view::npos == nl) ? nl : (nl - split - 3))); + key = key.substr(0, split); // look for a filter type that matches for (type n = FIRST; COUNT > n; ++n) @@ -1718,7 +1870,7 @@ char const *machine_filter::config_name(type n) char const *machine_filter::display_name(type n) { assert(COUNT > n); - return _(MACHINE_FILTER_NAMES[n]); + return _("machine-filter", MACHINE_FILTER_NAMES[n]); } machine_filter::machine_filter() @@ -1739,14 +1891,14 @@ char const *software_filter::config_name(type n) char const *software_filter::display_name(type n) { assert(COUNT > n); - return _(SOFTWARE_FILTER_NAMES[n]); + return _("software-filter", SOFTWARE_FILTER_NAMES[n]); } software_filter::software_filter() { } -software_filter::ptr software_filter::create(type n, software_filter_data const &data, char const *value, emu_file *file, unsigned indent) +software_filter::ptr software_filter::create(type n, software_filter_data const &data, char const *value, util::core_file *file, unsigned indent) { assert(COUNT > n); switch (n) @@ -1767,6 +1919,14 @@ software_filter::ptr software_filter::create(type n, software_filter_data const return std::make_unique<years_software_filter>(data, value, file, indent); case PUBLISHERS: return std::make_unique<publishers_software_filter>(data, value, file, indent); + case DEVELOPERS: + return std::make_unique<developer_software_filter>(data, value, file, indent); + case DISTRIBUTORS: + return std::make_unique<distributor_software_filter>(data, value, file, indent); + case AUTHORS: + return std::make_unique<author_software_filter>(data, value, file, indent); + case PROGRAMMERS: + return std::make_unique<programmer_software_filter>(data, value, file, indent); case SUPPORTED: return std::make_unique<supported_software_filter>(data, value, file, indent); case PARTIAL_SUPPORTED: @@ -1787,26 +1947,26 @@ software_filter::ptr software_filter::create(type n, software_filter_data const return nullptr; } -software_filter::ptr software_filter::create(emu_file &file, software_filter_data const &data, unsigned indent) +software_filter::ptr software_filter::create(util::core_file &file, software_filter_data const &data, unsigned indent) { char buffer[MAX_CHAR_INFO]; - if (!file.gets(buffer, ARRAY_LENGTH(buffer))) + if (!file.gets(buffer, std::size(buffer))) return nullptr; // split it into a key/value or bail - std::string key(buffer); - for (std::string::size_type i = 0; (2 * indent) > i; ++i) + std::string_view key(buffer); + for (std::string_view::size_type i = 0; (2 * indent) > i; ++i) { if ((key.length() <= i) || (' ' != key[i])) return nullptr; } key = key.substr(2 * indent); - std::string::size_type const split(key.find(" = ")); - if (std::string::npos == split) + std::string_view::size_type const split(key.find(" = ")); + if (std::string_view::npos == split) return nullptr; - std::string::size_type const nl(key.find_first_of("\r\n", split)); - std::string const value(key.substr(split + 3, (std::string::npos == nl) ? nl : (nl - split - 3))); - key.resize(split); + std::string_view::size_type const nl(key.find_first_of("\r\n", split)); + std::string const value(key.substr(split + 3, (std::string_view::npos == nl) ? nl : (nl - split - 3))); + key = key.substr(0, split); // look for a filter type that matches for (type n = FIRST; COUNT > n; ++n) @@ -1824,15 +1984,11 @@ extern const char UI_VERSION_TAG[]; const char UI_VERSION_TAG[] = "# UI INFO "; // Globals -uint8_t ui_globals::rpanel = 0; uint8_t ui_globals::curdats_view = 0; uint8_t ui_globals::cur_sw_dats_total = 0; uint8_t ui_globals::curdats_total = 0; uint8_t ui_globals::cur_sw_dats_view = 0; bool ui_globals::reset = false; -int ui_globals::visible_main_lines = 0; -int ui_globals::visible_sw_lines = 0; -uint16_t ui_globals::panels_status = 0; char* chartrimcarriage(char str[]) { @@ -1845,11 +2001,6 @@ char* chartrimcarriage(char str[]) return str; } -const char* strensure(const char* s) -{ - return s == nullptr ? "" : s; -} - int getprecisionchr(const char* s) { int precision = 1; @@ -1877,31 +2028,58 @@ std::vector<std::string> tokenize(const std::string &text, char sep) ui_software_info::ui_software_info( - software_info const &info, + software_info const &sw, software_part const &p, game_driver const &d, std::string const &li, std::string const &is, std::string const &de) - : shortname(info.shortname()), longname(info.longname()), parentname(info.parentname()) - , year(info.year()), publisher(info.publisher()) - , supported(info.supported()) + : shortname(sw.shortname()), longname(sw.longname()), parentname(sw.parentname()) + , year(sw.year()), publisher(sw.publisher()) + , supported(sw.supported()) , part(p.name()) , driver(&d) , listname(li), interface(p.interface()), instance(is) , startempty(0) , parentlongname() - , usage() + , infotext() , devicetype(de) + , info() + , alttitles() , available(false) { - for (feature_list_item const &feature : info.other_info()) + // show the list/item here + infotext.append(longname); + infotext.append(2, '\n'); + infotext.append(_("swlist-info", "Software list/item")); + infotext.append(1, '\n'); + infotext.append(listname); + infotext.append(1, ':'); + infotext.append(shortname); + + info.reserve(sw.info().size()); + for (software_info_item const &feature : sw.info()) { - if (feature.name() == "usage") - { - usage = feature.value(); - break; - } + // add info for the internal UI, localising recognised keys + infotext.append(2, '\n'); + auto const found = std::lower_bound( + std::begin(ui::SOFTWARE_INFO_NAMES), + std::end(ui::SOFTWARE_INFO_NAMES), + feature.name().c_str(), + [] (std::pair<char const *, char const *> const &a, char const *b) + { + return 0 > std::strcmp(a.first, b); + }); + if ((std::end(ui::SOFTWARE_INFO_NAMES) != found) && (feature.name() == found->first)) + infotext.append(_("swlist-info", found->second)); + else + infotext.append(feature.name()); + infotext.append(1, '\n').append(feature.value()); + + // keep references to stuff for filtering and searching + auto const &ins = info.emplace_back(feature.name(), feature.value()); + if (feature.name() == "alt_title") + alttitles.emplace_back(ins.value()); } } @@ -1910,3 +2088,88 @@ ui_software_info::ui_software_info(game_driver const &d) : shortname(d.name), longname(d.type.fullname()), driver(&d), startempty(1), available(true) { } + +ui_software_info::ui_software_info(ui_software_info const &that) + : shortname(that.shortname) + , longname(that.longname) + , parentname(that.parentname) + , year(that.year) + , publisher(that.publisher) + , supported(that.supported) + , part(that.part) + , driver(that.driver) + , listname(that.listname) + , interface(that.interface) + , instance(that.instance) + , startempty(that.startempty) + , parentlongname(that.parentlongname) + , infotext(that.infotext) + , devicetype(that.devicetype) + , info(that.info) + , alttitles() + , available(that.available) +{ + // build self-referencing member + alttitles.reserve(that.alttitles.size()); + for (software_info_item const &feature : info) + { + if (feature.name() == "alt_title") + alttitles.emplace_back(feature.value()); + } +} + +ui_software_info &ui_software_info::operator=(ui_software_info const &that) +{ + if (&that != this) + { + // copy simple stuff + shortname = that.shortname; + longname = that.longname; + parentname = that.parentname; + year = that.year; + publisher = that.publisher; + supported = that.supported; + part = that.part; + driver = that.driver; + listname = that.listname; + interface = that.interface; + instance = that.instance; + startempty = that.startempty; + parentlongname = that.parentlongname; + infotext = that.infotext; + devicetype = that.devicetype; + info = that.info; + alttitles.clear(); + available = that.available; + + // build self-referencing member + alttitles.reserve(that.alttitles.size()); + for (software_info_item const &feature : info) + { + if (feature.name() == "alt_title") + alttitles.emplace_back(feature.value()); + } + } + return *this; +} + + +void swap(ui_system_info &a, ui_system_info &b) noexcept +{ + using std::swap; + swap(a.driver, b.driver); + swap(a.index, b.index); + swap(a.is_clone, b.is_clone); + swap(a.available, b.available); + swap(a.description, b.description); + swap(a.parent, b.parent); + swap(a.reading_description, b.reading_description); + swap(a.reading_parent, b.reading_parent); + swap(a.ucs_shortname, b.ucs_shortname); + swap(a.ucs_description, b.ucs_description); + swap(a.ucs_reading_description, b.ucs_reading_description); + swap(a.ucs_manufacturer_description, b.ucs_manufacturer_description); + swap(a.ucs_manufacturer_reading_description, b.ucs_manufacturer_reading_description); + swap(a.ucs_default_description, b.ucs_default_description); + swap(a.ucs_manufacturer_default_description, b.ucs_manufacturer_default_description); +} diff --git a/src/frontend/mame/ui/utils.h b/src/frontend/mame/ui/utils.h index e6f2acd6bc8..04cb25a840c 100644 --- a/src/frontend/mame/ui/utils.h +++ b/src/frontend/mame/ui/utils.h @@ -12,13 +12,20 @@ #pragma once +#include "softlist.h" #include "unicode.h" +// FIXME: allow OSD module headers to be included in a less ugly way +#include "../osd/modules/lib/osdlib.h" + #include <algorithm> +#include <functional> #include <limits> #include <memory> #include <string> +#include <string_view> #include <unordered_map> +#include <utility> #include <vector> @@ -30,16 +37,32 @@ class render_container; struct ui_system_info { + ui_system_info(ui_system_info const &) = default; + ui_system_info(ui_system_info &&) = default; + ui_system_info &operator=(ui_system_info const &) = default; + ui_system_info &operator=(ui_system_info &&) = default; + ui_system_info() { } ui_system_info(game_driver const &d, int i, bool a) : driver(&d), index(i), available(a) { } game_driver const *driver = nullptr; int index; + bool is_clone = false; bool available = false; + std::string description; + std::string parent; + + std::wstring reading_description; + std::wstring reading_parent; + std::u32string ucs_shortname; std::u32string ucs_description; + std::u32string ucs_reading_description; std::u32string ucs_manufacturer_description; + std::u32string ucs_manufacturer_reading_description; + std::u32string ucs_default_description; + std::u32string ucs_manufacturer_default_description; }; struct ui_software_info @@ -48,7 +71,7 @@ struct ui_software_info // info for software list item ui_software_info( - software_info const &info, + software_info const &sw, software_part const &p, game_driver const &d, std::string const &li, @@ -59,19 +82,19 @@ struct ui_software_info ui_software_info(game_driver const &d); // copyable/movable - ui_software_info(ui_software_info const &) = default; - ui_software_info(ui_software_info &&) = default; - ui_software_info &operator=(ui_software_info const &) = default; + ui_software_info(ui_software_info const &that); + ui_software_info(ui_software_info &&that) = default; + ui_software_info &operator=(ui_software_info const &that); ui_software_info &operator=(ui_software_info &&) = default; bool operator==(ui_software_info const &r) const { - // compares all fields except available - return shortname == r.shortname && longname == r.longname && parentname == r.parentname - && year == r.year && publisher == r.publisher && supported == r.supported - && part == r.part && driver == r.driver && listname == r.listname - && interface == r.interface && instance == r.instance && startempty == r.startempty - && parentlongname == r.parentlongname && usage == r.usage && devicetype == r.devicetype; + // compares all fields except info (fragile), alttitles (included in info) and available (environmental) + return (shortname == r.shortname) && (longname == r.longname) && (parentname == r.parentname) + && (year == r.year) && (publisher == r.publisher) && (supported == r.supported) + && (part == r.part) && (driver == r.driver) && (listname == r.listname) + && (interface == r.interface) && (instance == r.instance) && (startempty == r.startempty) + && (parentlongname == r.parentlongname) && (devicetype == r.devicetype); } std::string shortname; @@ -79,7 +102,7 @@ struct ui_software_info std::string parentname; std::string year; std::string publisher; - uint8_t supported = 0; + software_support supported = software_support::SUPPORTED; std::string part; game_driver const *driver = nullptr; std::string listname; @@ -87,12 +110,17 @@ struct ui_software_info std::string instance; uint8_t startempty = 0; std::string parentlongname; - std::string usage; + std::string infotext; std::string devicetype; + std::vector<software_info_item> info; + std::vector<std::reference_wrapper<std::string const> > alttitles; bool available = false; }; +void swap(ui_system_info &a, ui_system_info &b) noexcept; + + namespace ui { class machine_filter_data; @@ -121,7 +149,7 @@ public: virtual bool adjust_left() = 0; virtual bool adjust_right() = 0; - virtual void save_ini(emu_file &file, unsigned indent) const = 0; + virtual void save_ini(util::core_file &file, unsigned indent) const = 0; template <typename InputIt, class OutputIt> void apply(InputIt first, InputIt last, OutputIt dest) const @@ -158,6 +186,7 @@ public: CLONES, MANUFACTURER, YEAR, + SOURCE_FILE, SAVE, NOSAVE, CHD, @@ -175,7 +204,7 @@ public: virtual std::string adorned_display_name(type n) const = 0; static ptr create(type n, machine_filter_data const &data) { return create(n, data, nullptr, nullptr, 0); } - static ptr create(emu_file &file, machine_filter_data const &data) { return create(file, data, 0); } + static ptr create(util::core_file &file, machine_filter_data const &data) { return create(file, data, 0); } static char const *config_name(type n); static char const *display_name(type n); @@ -185,8 +214,8 @@ public: protected: machine_filter(); - static ptr create(type n, machine_filter_data const &data, char const *value, emu_file *file, unsigned indent); - static ptr create(emu_file &file, machine_filter_data const &data, unsigned indent); + static ptr create(type n, machine_filter_data const &data, char const *value, util::core_file *file, unsigned indent); + static ptr create(util::core_file &file, machine_filter_data const &data, unsigned indent); }; DECLARE_ENUM_INCDEC_OPERATORS(machine_filter::type) @@ -205,6 +234,10 @@ public: CLONES, YEAR, PUBLISHERS, + DEVELOPERS, + DISTRIBUTORS, + AUTHORS, + PROGRAMMERS, SUPPORTED, PARTIAL_SUPPORTED, UNSUPPORTED, @@ -222,7 +255,7 @@ public: virtual std::string adorned_display_name(type n) const = 0; static ptr create(type n, software_filter_data const &data) { return create(n, data, nullptr, nullptr, 0); } - static ptr create(emu_file &file, software_filter_data const &data) { return create(file, data, 0); } + static ptr create(util::core_file &file, software_filter_data const &data) { return create(file, data, 0); } static char const *config_name(type n); static char const *display_name(type n); @@ -232,8 +265,8 @@ public: protected: software_filter(); - static ptr create(type n, software_filter_data const &data, char const *value, emu_file *file, unsigned indent); - static ptr create(emu_file &file, software_filter_data const &data, unsigned indent); + static ptr create(type n, software_filter_data const &data, char const *value, util::core_file *file, unsigned indent); + static ptr create(util::core_file &file, software_filter_data const &data, unsigned indent); }; DECLARE_ENUM_INCDEC_OPERATORS(software_filter::type) @@ -244,10 +277,13 @@ class machine_filter_data public: std::vector<std::string> const &manufacturers() const { return m_manufacturers; } std::vector<std::string> const &years() const { return m_years; } + std::vector<std::string> const &source_files() const { return m_source_files; } // adding entries void add_manufacturer(std::string const &manufacturer); void add_year(std::string const &year); + void add_source_file(std::string_view path); + void finalise(); // use heuristics to extract meaningful parts from machine metadata @@ -267,13 +303,14 @@ public: return (m_filters.end() != it) ? it->second.get() : nullptr; } std::string get_config_string() const; - bool load_ini(emu_file &file); + bool load_ini(util::core_file &file); private: using filter_map = std::map<machine_filter::type, machine_filter::ptr>; std::vector<std::string> m_manufacturers; std::vector<std::string> m_years; + std::vector<std::string> m_source_files; machine_filter::type m_current_filter = machine_filter::ALL; filter_map m_filters; @@ -286,6 +323,10 @@ public: std::vector<std::string> const ®ions() const { return m_regions; } std::vector<std::string> const &publishers() const { return m_publishers; } std::vector<std::string> const &years() const { return m_years; } + std::vector<std::string> const &developers() const { return m_developers; } + std::vector<std::string> const &distributors() const { return m_distributors; } + std::vector<std::string> const &authors() const { return m_authors; } + std::vector<std::string> const &programmers() const { return m_programmers; } std::vector<std::string> const &device_types() const { return m_device_types; } std::vector<std::string> const &list_names() const { return m_list_names; } std::vector<std::string> const &list_descriptions() const { return m_list_descriptions; } @@ -294,6 +335,7 @@ public: void add_region(std::string const &longname); void add_publisher(std::string const &publisher); void add_year(std::string const &year); + void add_info(software_info_item const &info); void add_device_type(std::string const &device_type); void add_list(std::string const &name, std::string const &description); void finalise(); @@ -306,6 +348,10 @@ private: std::vector<std::string> m_regions; std::vector<std::string> m_publishers; std::vector<std::string> m_years; + std::vector<std::string> m_developers; + std::vector<std::string> m_distributors; + std::vector<std::string> m_authors; + std::vector<std::string> m_programmers; std::vector<std::string> m_device_types; std::vector<std::string> m_list_names, m_list_descriptions; }; @@ -316,58 +362,29 @@ private: enum { - RP_FIRST = 0, - RP_IMAGES = RP_FIRST, - RP_INFOS, - RP_LAST = RP_INFOS -}; - -enum -{ SHOW_PANELS = 0, HIDE_LEFT_PANEL, HIDE_RIGHT_PANEL, HIDE_BOTH }; -enum -{ - HOVER_DAT_UP = -1000, - HOVER_DAT_DOWN, - HOVER_UI_LEFT, - HOVER_UI_RIGHT, - HOVER_ARROW_UP, - HOVER_ARROW_DOWN, - HOVER_B_FAV, - HOVER_B_EXPORT, - HOVER_B_DATS, - HOVER_RPANEL_ARROW, - HOVER_LPANEL_ARROW, - HOVER_FILTER_FIRST, - HOVER_FILTER_LAST = HOVER_FILTER_FIRST + std::max<int>(ui::machine_filter::COUNT, ui::software_filter::COUNT), - HOVER_RP_FIRST, - HOVER_RP_LAST = HOVER_RP_FIRST + 1 + RP_LAST, - HOVER_INFO_TEXT -}; - // FIXME: this stuff shouldn't all be globals // GLOBAL CLASS struct ui_globals { - static uint8_t curdats_view, curdats_total, cur_sw_dats_view, cur_sw_dats_total, rpanel; - static bool default_image, reset; - static int visible_main_lines, visible_sw_lines; - static uint16_t panels_status; + static uint8_t curdats_view, curdats_total, cur_sw_dats_view, cur_sw_dats_total; + static bool reset; }; // GLOBAL FUNCTIONS char* chartrimcarriage(char str[]); -const char* strensure(const char* s); int getprecisionchr(const char* s); std::vector<std::string> tokenize(const std::string &text, char sep); +namespace ui { + //------------------------------------------------- // input_character - inputs a typed character // into a buffer @@ -376,31 +393,30 @@ std::vector<std::string> tokenize(const std::string &text, char sep); template <typename F> bool input_character(std::string &buffer, std::string::size_type size, char32_t unichar, F &&filter) { - bool result = false; - auto buflen = buffer.size(); - + auto const buflen(buffer.length()); if ((unichar == 8) || (unichar == 0x7f)) { // backspace if (0 < buflen) { - auto buffer_oldend = buffer.c_str() + buflen; - auto buffer_newend = utf8_previous_char(buffer_oldend); + auto const buffer_oldend(buffer.c_str() + buflen); + auto const buffer_newend(utf8_previous_char(buffer_oldend)); buffer.resize(buffer_newend - buffer.c_str()); - result = true; + return true; } } else if ((unichar >= ' ') && filter(unichar)) { // append this character - check against the size first - std::string utf8_char = utf8_from_uchar(unichar); - if ((buffer.size() + utf8_char.size()) <= size) + char utf8char[UTF8_CHAR_MAX]; + auto const utf8len(utf8_from_uchar(utf8char, std::size(utf8char), unichar)); + if ((0 < utf8len) && (size >= utf8len) && ((size - utf8len) >= buflen)) { - buffer += utf8_char; - result = true; + buffer.append(utf8char, utf8len); + return true; } } - return result; + return false; } @@ -412,9 +428,61 @@ bool input_character(std::string &buffer, std::string::size_type size, char32_t template <typename F> bool input_character(std::string &buffer, char32_t unichar, F &&filter) { - auto size = std::numeric_limits<std::string::size_type>::max(); - return input_character(buffer, size, unichar, filter); + auto const size(std::numeric_limits<std::string::size_type>::max()); + return input_character(buffer, size, unichar, std::forward<F>(filter)); } +//------------------------------------------------- +// paste_text - paste text from clipboard into a +// buffer, ignoring invalid characters +//------------------------------------------------- + +template <typename F> +bool paste_text(std::string &buffer, std::string::size_type size, F &&filter) +{ + std::string const clip(osd_get_clipboard_text()); + std::string_view text(clip); + bool updated(false); + int codelength; + char32_t unichar; + while ((codelength = uchar_from_utf8(&unichar, text)) != 0) + { + text.remove_prefix((0 < codelength) ? codelength : 1); + if ((0 < codelength) && filter(unichar)) + { + char utf8char[UTF8_CHAR_MAX]; + auto const utf8len(utf8_from_uchar(utf8char, std::size(utf8char), unichar)); + if (0 < utf8len) + { + if ((size < utf8len) || ((size - utf8len) < buffer.length())) + { + return updated; + } + else + { + buffer.append(utf8char, utf8len); + updated = true; + } + } + } + } + return updated; +} + + +//------------------------------------------------- +// paste_text - paste text from clipboard into a +// buffer, ignoring invalid characters +//------------------------------------------------- + +template <typename F> +bool paste_text(std::string &buffer, F &&filter) +{ + auto const size(std::numeric_limits<std::string::size_type>::max()); + return paste_text(buffer, size, std::forward<F>(filter)); +} + +} // namespace ui + #endif // MAME_FRONTEND_UI_UTILS_H diff --git a/src/frontend/mame/ui/videoopt.cpp b/src/frontend/mame/ui/videoopt.cpp index baa0a265cb6..5b8afb77fbf 100644 --- a/src/frontend/mame/ui/videoopt.cpp +++ b/src/frontend/mame/ui/videoopt.cpp @@ -9,84 +9,255 @@ *********************************************************************/ #include "emu.h" - #include "ui/videoopt.h" +#include "rendfont.h" +#include "rendlay.h" #include "rendutil.h" +#include <chrono> + + namespace ui { + +namespace { + +constexpr uintptr_t ITEM_ROTATE = 0x00000100; +constexpr uintptr_t ITEM_ZOOM = 0x00000101; +constexpr uintptr_t ITEM_UNEVENSTRETCH = 0x00000102; +constexpr uintptr_t ITEM_KEEPASPECT = 0x00000103; +constexpr uintptr_t ITEM_POINTERTIMEOUT = 0x00000104; +constexpr uintptr_t ITEM_TOGGLE_FIRST = 0x00000200; +constexpr uintptr_t ITEM_VIEW_FIRST = 0x00000300; + +} // anonymous namespace + + +/*------------------------------------------------- + menu_video_targets_populate - populate the + video targets menu +-------------------------------------------------*/ + +menu_video_targets::menu_video_targets(mame_ui_manager &mui, render_container &container) + : menu(mui, container) +{ + set_heading(_("Video Options")); +} + +menu_video_targets::~menu_video_targets() +{ +} + +void menu_video_targets::populate() +{ + // find the targets + for (unsigned targetnum = 0; ; targetnum++) + { + // stop when we run out + render_target *const target = machine().render().target_by_index(targetnum); + if (!target) + break; + + // add a menu item + item_append(util::string_format(_("Screen #%d"), targetnum), 0, target); + } + + // add option for snapshot target + item_append("Snapshot", 0, &machine().video().snapshot_target()); + item_append(menu_item_type::SEPARATOR); +} + /*------------------------------------------------- menu_video_targets - handle the video targets menu -------------------------------------------------*/ -void menu_video_targets::handle() +bool menu_video_targets::handle(event const *ev) { - /* process the menu */ - const event *menu_event = process(0); - if (menu_event != nullptr && menu_event->iptkey == IPT_UI_SELECT) - menu::stack_push<menu_video_options>(ui(), container(), static_cast<render_target *>(menu_event->itemref)); + if (ev && (ev->iptkey == IPT_UI_SELECT)) + { + render_target *const target = reinterpret_cast<render_target *>(ev->itemref); + menu::stack_push<menu_video_options>( + ui(), + container(), + std::string(selected_item().text()), + *target, + &machine().video().snapshot_target() == target); + } + + return false; } /*------------------------------------------------- - menu_video_targets_populate - populate the - video targets menu + menu_video_options_populate - populate the + video options menu -------------------------------------------------*/ -menu_video_targets::menu_video_targets(mame_ui_manager &mui, render_container &container) : menu(mui, container) +menu_video_options::menu_video_options( + mame_ui_manager &mui, + render_container &container, + std::string_view title, + render_target &target, + bool snapshot) + : menu(mui, container) + , m_target(target) + , m_snapshot(snapshot) { + set_heading(util::string_format(_("Video Options: %1$s"), title)); + + if (!m_snapshot || !machine().video().snap_native()) + { + set_selected_index(target.view()); + reset(reset_options::REMEMBER_POSITION); + } } -void menu_video_targets::populate(float &customtop, float &custombottom) +menu_video_options::~menu_video_options() { - int targetnum; +} + +void menu_video_options::populate() +{ + uintptr_t ref; + + // add items for each view + if (!m_snapshot || !machine().video().snap_native()) + { + for (char const *name = m_target.view_name(ref = 0); name; name = m_target.view_name(++ref)) + item_append(name, convert_command_glyph(ref == m_target.view() ? "_>" : "_<"), 0, reinterpret_cast<void *>(ITEM_VIEW_FIRST + ref)); + item_append(menu_item_type::SEPARATOR); + } + + // add items for visibility toggles + layout_view const &curview = m_target.current_view(); + auto const &toggles = curview.visibility_toggles(); + if (!toggles.empty()) + { + ref = 0U; + auto const current_mask(m_target.visibility_mask()); + for (auto toggle = toggles.begin(); toggles.end() != toggle; ++toggle, ++ref) + { + auto const toggle_mask(toggle->mask()); + bool const enabled(BIT(current_mask, ref)); + bool eclipsed(false); + for (auto it = toggles.begin(); !eclipsed && (toggle != it); ++it) + eclipsed = ((current_mask & it->mask()) != it->mask()) && ((toggle_mask & it->mask()) == it->mask()); + item_append_on_off(toggle->name(), enabled, eclipsed ? (FLAG_INVERT | FLAG_DISABLE) : 0U, reinterpret_cast<void *>(ITEM_TOGGLE_FIRST + ref)); + } + item_append(menu_item_type::SEPARATOR); + } + + const char *subtext = ""; + + // add a rotate item + switch (m_target.orientation()) + { + case ROT0: subtext = "None"; break; + case ROT90: subtext = u8"CW 90°"; break; + case ROT180: subtext = u8"180°"; break; + case ROT270: subtext = u8"CCW 90°"; break; + } + item_append(_("Rotate"), subtext, FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW, reinterpret_cast<void *>(ITEM_ROTATE)); - /* find the targets */ - for (targetnum = 0; ; targetnum++) + // cropping + bool const canzoom(curview.has_art() && !curview.visible_screens().empty()); + item_append_on_off(_("Zoom to Screen Area"), m_target.zoom_to_screen(), canzoom ? 0U : (FLAG_INVERT | FLAG_DISABLE), reinterpret_cast<void *>(ITEM_ZOOM)); + + if (!m_snapshot) { - render_target *target = machine().render().target_by_index(targetnum); - char buffer[40]; + // uneven stretch + switch (m_target.scale_mode()) + { + case SCALE_FRACTIONAL: + subtext = _("On"); + break; + + case SCALE_FRACTIONAL_X: + subtext = _("X Only"); + break; + + case SCALE_FRACTIONAL_Y: + subtext = _("Y Only"); + break; - /* stop when we run out */ - if (target == nullptr) + case SCALE_FRACTIONAL_AUTO: + subtext = _("X or Y (Auto)"); break; - /* add a menu item */ - sprintf(buffer, _("Screen #%d"), targetnum); - item_append(buffer, "", 0, target); + case SCALE_INTEGER: + subtext = _("Off"); + break; + } + item_append(_("Non-Integer Scaling"), subtext, FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW, reinterpret_cast<void *>(ITEM_UNEVENSTRETCH)); + + // keep aspect + item_append_on_off(_("Maintain Aspect Ratio"), m_target.keepaspect(), 0, reinterpret_cast<void *>(ITEM_KEEPASPECT)); } -} -menu_video_targets::~menu_video_targets() -{ + // add pointer display options + if (!m_target.hidden()) + { + item_append(menu_item_type::SEPARATOR); + + // use millisecond precision for timeout display + auto const timeout = std::chrono::duration_cast<std::chrono::milliseconds>(ui().pointer_activity_timeout(m_target.index())); + bool const hide = ui().hide_inactive_pointers(m_target.index()); + if (hide) + { + if (timeout.count()) + { + int const precision = (timeout.count() % 10) ? 3 : (timeout.count() % 100) ? 2 : 1; + item_append( + _("Hide Inactive Pointers After Delay"), + util::string_format(_("%1$.*2$f s"), timeout.count() * 1e-3, precision), + ((timeout >= std::chrono::milliseconds(100)) ? FLAG_LEFT_ARROW : 0) | FLAG_RIGHT_ARROW, + reinterpret_cast<void *>(ITEM_POINTERTIMEOUT)); + } + else + item_append(_("Hide Inactive Pointers After Delay"), _("Always"), FLAG_RIGHT_ARROW, reinterpret_cast<void *>(ITEM_POINTERTIMEOUT)); + } + else + item_append(_("Hide Inactive Pointers After Delay"), _("Never"), FLAG_LEFT_ARROW, reinterpret_cast<void *>(ITEM_POINTERTIMEOUT)); + } + + item_append(menu_item_type::SEPARATOR); } + /*------------------------------------------------- menu_video_options - handle the video options menu -------------------------------------------------*/ -void menu_video_options::handle() +bool menu_video_options::handle(event const *ev) { - bool changed = false; + auto const lockout_popup( + [this] () + { + machine().popmessage(_("Cannot change options while recording!")); + return true; + }); + bool const snap_lockout(m_snapshot && machine().video().is_recording()); + bool changed(false); + set_process_flags((reinterpret_cast<uintptr_t>(get_selection_ref()) == ITEM_POINTERTIMEOUT) ? PROCESS_LR_REPEAT : 0); // process the menu - const event *menu_event = process(0); - if (menu_event != nullptr && menu_event->itemref != nullptr) + if (ev && uintptr_t(ev->itemref)) { - switch ((uintptr_t)menu_event->itemref) + switch (reinterpret_cast<uintptr_t>(ev->itemref)) { // rotate adds rotation depending on the direction - case VIDEO_ITEM_ROTATE: - if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) + case ITEM_ROTATE: + if (ev->iptkey == IPT_UI_LEFT || ev->iptkey == IPT_UI_RIGHT) { - int delta = (menu_event->iptkey == IPT_UI_LEFT) ? ROT270 : ROT90; - target->set_orientation(orientation_add(delta, target->orientation())); - if (target->is_ui_target()) + if (snap_lockout) + return lockout_popup(); + int const delta((ev->iptkey == IPT_UI_LEFT) ? ROT270 : ROT90); + m_target.set_orientation(orientation_add(delta, m_target.orientation())); + if (m_target.is_ui_target()) { - render_container::user_settings settings; - container().get_user_settings(settings); + render_container::user_settings settings = container().get_user_settings(); settings.m_orientation = orientation_add(delta ^ ROT180, settings.m_orientation); container().set_user_settings(settings); } @@ -95,80 +266,182 @@ void menu_video_options::handle() break; // layer config bitmasks handle left/right keys the same (toggle) - case VIDEO_ITEM_ZOOM: - if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) + case ITEM_ZOOM: + if ((ev->iptkey == IPT_UI_LEFT) || (ev->iptkey == IPT_UI_RIGHT)) { - target->set_zoom_to_screen(!target->zoom_to_screen()); + if (snap_lockout) + return lockout_popup(); + m_target.set_zoom_to_screen(ev->iptkey == IPT_UI_RIGHT); changed = true; } break; - // anything else is a view item - default: - if (menu_event->iptkey == IPT_UI_SELECT && (int)(uintptr_t)menu_event->itemref >= VIDEO_ITEM_VIEW) + // non-integer scaling: rotate through options + case ITEM_UNEVENSTRETCH: + if (ev->iptkey == IPT_UI_LEFT) { - target->set_view((uintptr_t)menu_event->itemref - VIDEO_ITEM_VIEW); + if (snap_lockout) + return lockout_popup(); + switch (m_target.scale_mode()) + { + case SCALE_FRACTIONAL: + m_target.set_scale_mode(SCALE_INTEGER); + break; + + case SCALE_FRACTIONAL_X: + m_target.set_scale_mode(SCALE_FRACTIONAL); + break; + + case SCALE_FRACTIONAL_Y: + m_target.set_scale_mode(SCALE_FRACTIONAL_X); + break; + + case SCALE_FRACTIONAL_AUTO: + m_target.set_scale_mode(SCALE_FRACTIONAL_Y); + break; + + case SCALE_INTEGER: + m_target.set_scale_mode(SCALE_FRACTIONAL_AUTO); + break; + } changed = true; } - break; - } - } - - /* if something changed, rebuild the menu */ - if (changed) - reset(reset_options::REMEMBER_REF); -} + else if (ev->iptkey == IPT_UI_RIGHT) + { + if (snap_lockout) + return lockout_popup(); + switch (m_target.scale_mode()) + { + case SCALE_FRACTIONAL: + m_target.set_scale_mode(SCALE_FRACTIONAL_X); + break; + case SCALE_FRACTIONAL_X: + m_target.set_scale_mode(SCALE_FRACTIONAL_Y); + break; -/*------------------------------------------------- - menu_video_options_populate - populate the - video options menu --------------------------------------------------*/ + case SCALE_FRACTIONAL_Y: + m_target.set_scale_mode(SCALE_FRACTIONAL_AUTO); + break; -menu_video_options::menu_video_options(mame_ui_manager &mui, render_container &container, render_target *_target) : menu(mui, container) -{ - target = _target; -} + case SCALE_FRACTIONAL_AUTO: + m_target.set_scale_mode(SCALE_INTEGER); + break; -void menu_video_options::populate(float &customtop, float &custombottom) -{ - const char *subtext = ""; - std::string tempstring; - int enabled; + case SCALE_INTEGER: + m_target.set_scale_mode(SCALE_FRACTIONAL); + break; + } + changed = true; + } + break; - // add items for each view - for (int viewnum = 0; ; viewnum++) - { - const char *name = target->view_name(viewnum); - if (name == nullptr) + // keep aspect handles left/right keys identically (toggle) + case ITEM_KEEPASPECT: + if ((ev->iptkey == IPT_UI_LEFT) || (ev->iptkey == IPT_UI_RIGHT)) + { + if (snap_lockout) + return lockout_popup(); + m_target.set_keepaspect(ev->iptkey == IPT_UI_RIGHT); + changed = true; + } break; - // create a string for the item, replacing underscores with spaces - tempstring.assign(name); - strreplace(tempstring, "_", " "); - item_append(tempstring, "", 0, (void *)(uintptr_t)(VIDEO_ITEM_VIEW + viewnum)); - } + // pointer inactivity timeout + case ITEM_POINTERTIMEOUT: + switch (ev->iptkey) + { + // decrease value + case IPT_UI_LEFT: + if (!ui().hide_inactive_pointers(m_target.index())) + { + ui().set_hide_inactive_pointers(m_target.index(), true); + ui().set_pointer_activity_timeout(m_target.index(), std::chrono::milliseconds(10'000)); + changed = true; + } + else + { + auto timeout = ui().pointer_activity_timeout(m_target.index()); + if (timeout >= std::chrono::milliseconds(100)) + { + bool const shift_pressed = machine().input().code_pressed(KEYCODE_LSHIFT) || machine().input().code_pressed(KEYCODE_RSHIFT); + std::chrono::milliseconds const increment(shift_pressed ? 100 : 1'000); + auto const remainder = timeout % increment; + timeout -= remainder.count() ? remainder : increment; + ui().set_pointer_activity_timeout(m_target.index(), timeout); + changed = true; + + if (!timeout.count()) + machine().popmessage(_("Clickable artwork is still active when pointer is hidden.")); + } + } + break; - // add a separator - item_append(menu_item_type::SEPARATOR); + // increase value + case IPT_UI_RIGHT: + if (ui().hide_inactive_pointers(m_target.index())) + { + auto const timeout = ui().pointer_activity_timeout(m_target.index()); + if (std::chrono::milliseconds(10'000) <= timeout) + { + ui().set_hide_inactive_pointers(m_target.index(), false); + } + else + { + bool const shift_pressed = machine().input().code_pressed(KEYCODE_LSHIFT) || machine().input().code_pressed(KEYCODE_RSHIFT); + int const increment(shift_pressed ? 100 : 1'000); + ui().set_pointer_activity_timeout( + m_target.index(), + std::chrono::milliseconds((1 + (timeout / std::chrono::milliseconds(increment))) * increment)); + } + changed = true; + } + break; - // add a rotate item - switch (target->orientation()) - { - case ROT0: subtext = "None"; break; - case ROT90: subtext = "CW 90" UTF8_DEGREES; break; - case ROT180: subtext = "180" UTF8_DEGREES; break; - case ROT270: subtext = "CCW 90" UTF8_DEGREES; break; - } - item_append(_("Rotate"), subtext, FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW, (void *)VIDEO_ITEM_ROTATE); + // toggle hide after delay + case IPT_UI_SELECT: + ui().set_hide_inactive_pointers(m_target.index(), !ui().hide_inactive_pointers(m_target.index())); + changed = true; + break; - // cropping - enabled = target->zoom_to_screen(); - item_append(_("View"), enabled ? _("Cropped") : _("Full"), enabled ? FLAG_RIGHT_ARROW : FLAG_LEFT_ARROW, (void *)VIDEO_ITEM_ZOOM); -} + // restore initial setting + case IPT_UI_CLEAR: + ui().restore_initial_pointer_options(m_target.index()); + changed = true; + break; + } + break; -menu_video_options::~menu_video_options() -{ + // anything else is a view item + default: + if (reinterpret_cast<uintptr_t>(ev->itemref) >= ITEM_VIEW_FIRST) + { + if (snap_lockout) + return lockout_popup(); + if (ev->iptkey == IPT_UI_SELECT) + { + m_target.set_view(reinterpret_cast<uintptr_t>(ev->itemref) - ITEM_VIEW_FIRST); + changed = true; + } + } + else if (reinterpret_cast<uintptr_t>(ev->itemref) >= ITEM_TOGGLE_FIRST) + { + if (snap_lockout) + return lockout_popup(); + if ((ev->iptkey == IPT_UI_LEFT) || (ev->iptkey == IPT_UI_RIGHT)) + { + m_target.set_visibility_toggle(reinterpret_cast<uintptr_t>(ev->itemref) - ITEM_TOGGLE_FIRST, ev->iptkey == IPT_UI_RIGHT); + changed = true; + } + } + break; + } + } + + // if something changed, rebuild the menu + if (changed) + reset(reset_options::REMEMBER_REF); + return false; } } // namespace ui diff --git a/src/frontend/mame/ui/videoopt.h b/src/frontend/mame/ui/videoopt.h index c26281ffd1b..a725cba0f71 100644 --- a/src/frontend/mame/ui/videoopt.h +++ b/src/frontend/mame/ui/videoopt.h @@ -7,15 +7,18 @@ Internal menus for video options ***************************************************************************/ - -#pragma once - #ifndef MAME_FRONTEND_UI_VIDEOOPT_H #define MAME_FRONTEND_UI_VIDEOOPT_H +#pragma once + #include "ui/menu.h" +#include <string_view> + + namespace ui { + class menu_video_targets : public menu { public: @@ -23,29 +26,25 @@ public: virtual ~menu_video_targets() 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; }; + class menu_video_options : public menu { public: - menu_video_options(mame_ui_manager &mui, render_container &container, render_target *target); + menu_video_options(mame_ui_manager &mui, render_container &container, std::string_view title, render_target &target, bool snapshot); virtual ~menu_video_options() override; private: - enum { - VIDEO_ITEM_ROTATE = 0x80000000, - VIDEO_ITEM_ZOOM, - VIDEO_ITEM_VIEW - }; - - virtual void populate(float &customtop, float &custombottom) override; - virtual void handle() override; + virtual void populate() override; + virtual bool handle(event const *ev) override; - render_target *target; + render_target &m_target; + bool const m_snapshot; }; } // namespace ui -#endif /* MAME_FRONTEND_UI_VIDEOOPT_H */ +#endif // MAME_FRONTEND_UI_VIDEOOPT_H diff --git a/src/frontend/mame/ui/viewgfx.cpp b/src/frontend/mame/ui/viewgfx.cpp index c8caca655d3..1d772da645d 100644 --- a/src/frontend/mame/ui/viewgfx.cpp +++ b/src/frontend/mame/ui/viewgfx.cpp @@ -9,386 +9,1003 @@ *********************************************************************/ #include "emu.h" +#include "ui/viewgfx.h" + #include "emupal.h" -#include "ui/ui.h" -#include "uiinput.h" #include "render.h" #include "rendfont.h" #include "rendutil.h" +#include "screen.h" #include "tilemap.h" -#include "ui/viewgfx.h" +#include "uiinput.h" +#include "util/unicode.h" +#include "osdepend.h" -/*************************************************************************** - CONSTANTS -***************************************************************************/ +#include <cmath> +#include <vector> + + +namespace { -enum ui_gfx_modes +class gfx_viewer { - UI_GFX_PALETTE = 0, - UI_GFX_GFXSET, - UI_GFX_TILEMAP -}; +public: + gfx_viewer(running_machine &machine) : + m_machine(machine), + m_palette(machine), + m_gfxset(machine), + m_tilemap(machine) + { + } -const uint8_t MAX_GFX_DECODERS = 8; + // copy constructor needed to make std::any happy + gfx_viewer(gfx_viewer const &that) : + gfx_viewer(that.m_machine) + { + } + ~gfx_viewer() + { + if (m_texture) + m_machine.render().texture_free(m_texture); + } + uint32_t handle(mame_ui_manager &mui, render_container &container, bool uistate) + { + // implicitly cancel if there's nothing to display + if (!is_relevant()) + return cancel(uistate); -/*************************************************************************** - TYPE DEFINITIONS -***************************************************************************/ + // let the OSD do its thing + mui.machine().osd().check_osd_inputs(); -// information about a single gfx device -struct ui_gfx_info -{ - device_gfx_interface *interface; // pointer to device's gfx interface - uint8_t setcount; // how many gfx sets device has - uint8_t rotate[MAX_GFX_ELEMENTS]; // current rotation (orientation) value - uint8_t columns[MAX_GFX_ELEMENTS]; // number of items per row - int offset[MAX_GFX_ELEMENTS]; // current offset of top,left item - int color[MAX_GFX_ELEMENTS]; // current color selected - device_palette_interface *palette[MAX_GFX_ELEMENTS]; // associated palette (maybe multiple choice one day?) - int color_count[MAX_GFX_ELEMENTS]; // Range of color values -}; + // always mark the bitmap dirty if not paused + if (!m_machine.paused()) + m_bitmap_dirty = true; -struct ui_gfx_state -{ - bool started; // have we called ui_gfx_count_devices() yet? - uint8_t mode; // which mode are we in? + // handle pointer events to show hover info + ui_event event; + while (m_machine.ui_input().pop_event(&event)) + { + switch (event.event_type) + { + case ui_event::type::POINTER_UPDATE: + { + // ignore pointer input in windows other than the one that displays the UI + render_target &target(m_machine.render().ui_target()); + if (&target != event.target) + break; + + // don't change if the current pointer has buttons pressed and this one doesn't + if (event.pointer_id == m_current_pointer) + { + assert(m_pointer_type == event.pointer_type); + m_pointer_buttons = event.pointer_buttons; + m_pointer_inside = target.map_point_container( + event.pointer_x, + event.pointer_y, + container, + m_pointer_x, + m_pointer_y); + } + else if ((0 > m_current_pointer) || (!m_pointer_buttons && (!m_pointer_inside || event.pointer_buttons))) + { + float x, y; + bool const inside(target.map_point_container(event.pointer_x, event.pointer_y, container, x, y)); + if ((0 > m_current_pointer) || event.pointer_buttons || (!m_pointer_inside && inside)) + { + m_current_pointer = event.pointer_id; + m_pointer_type = event.pointer_type; + m_pointer_buttons = event.pointer_buttons; + m_pointer_x = x; + m_pointer_y = y; + m_pointer_inside = inside; + } + } + } + break; - // intermediate bitmaps - bool bitmap_dirty; // is the bitmap dirty? - bitmap_rgb32 * bitmap; // bitmap for drawing gfx and tilemaps - render_texture *texture; // texture for rendering the above bitmap + case ui_event::type::POINTER_LEAVE: + case ui_event::type::POINTER_ABORT: + { + // if this was our pointer, we've lost it + render_target &target(m_machine.render().ui_target()); + if ((&target == event.target) && (event.pointer_id == m_current_pointer)) + { + // keep the pointer position and type so we can show touch locations after release + m_current_pointer = -1; + m_pointer_buttons = 0U; + m_pointer_inside = target.map_point_container( + event.pointer_x, + event.pointer_y, + container, + m_pointer_x, + m_pointer_y); + } + } + break; - // palette-specific data - struct - { - device_palette_interface *interface; // pointer to current palette - int devcount; // how many palette devices exist - int devindex; // which palette device is visible - uint8_t which; // which subset (pens or indirect colors)? - uint8_t columns; // number of items per row - int offset; // current offset of top left item - } palette; - - // graphics-specific data - struct - { - uint8_t devcount; // how many gfx devices exist - uint8_t devindex; // which device is visible - uint8_t set; // which set is visible - } gfxset; + // ignore anything that isn't pointer-related + default: + break; + } + } + + // always draw non-touch pointer + mame_ui_manager::display_pointer pointers[1]{ { m_machine.render().ui_target(), m_pointer_type, m_pointer_x, m_pointer_y } }; + if (m_pointer_inside && (0 <= m_current_pointer) && (ui_event::pointer::TOUCH != m_pointer_type)) + mui.set_pointers(std::begin(pointers), std::end(pointers)); + else + mui.set_pointers(std::begin(pointers), std::begin(pointers)); - // information about each gfx device - ui_gfx_info gfxdev[MAX_GFX_DECODERS]; + // try to display the selected view + while (true) + { + switch (m_mode) + { + case view::PALETTE: + if (m_palette.interface()) + return handle_palette(mui, container, uistate); + m_mode = view::GFXSET; + break; + + case view::GFXSET: + if (m_gfxset.has_gfx()) + return handle_gfxset(mui, container, uistate); + m_mode = view::TILEMAP; + break; - // tilemap-specific data - struct + case view::TILEMAP: + if (m_machine.tilemap().count()) + return handle_tilemap(mui, container, uistate); + m_mode = view::PALETTE; + break; + } + } + } + +private: + enum class view { - int which; // which tilemap are we viewing? - int xoffs; // current X offset - int yoffs; // current Y offset - int zoom; // zoom factor - uint8_t rotate; // current rotation (orientation) value - uint32_t flags; // render flags - } tilemap; -}; + PALETTE = 0, + GFXSET, + TILEMAP + }; + class palette + { + public: + enum class subset + { + PENS, + INDIRECT + }; + palette(running_machine &machine) : + m_count(palette_interface_enumerator(machine.root_device()).count()) + { + if (m_count) + set_device(machine); + } -/*************************************************************************** - GLOBAL VARIABLES -***************************************************************************/ + device_palette_interface *interface() const noexcept + { + return m_interface; + } -static ui_gfx_state ui_gfx; + bool indirect() const noexcept + { + return subset::INDIRECT == m_which; + } + unsigned columns() const noexcept + { + return m_columns; + } + unsigned index(unsigned x, unsigned y) const noexcept + { + return m_offset + (y * m_columns) + x; + } -/*************************************************************************** - FUNCTION PROTOTYPES -***************************************************************************/ + void handle_keys(running_machine &machine); -static void ui_gfx_count_devices(running_machine &machine, ui_gfx_state &state); -static void ui_gfx_exit(running_machine &machine); + private: + void set_device(running_machine &machine) + { + m_interface = palette_interface_enumerator(machine.root_device()).byindex(m_index); + } + + void next_group(running_machine &machine) noexcept + { + if ((subset::PENS == m_which) && m_interface->indirect_entries()) + { + m_which = subset::INDIRECT; + } + else if ((m_count - 1) > m_index) + { + ++m_index; + set_device(machine); + m_which = subset::PENS; + } + } -// palette handling -static void palette_set_device(running_machine &machine, ui_gfx_state &state); -static void palette_handle_keys(running_machine &machine, ui_gfx_state &state); -static void palette_handler(mame_ui_manager &mui, render_container &container, ui_gfx_state &state); + void prev_group(running_machine &machine) noexcept + { + if (subset::INDIRECT == m_which) + { + m_which = subset::PENS; + } + else if (0 < m_index) + { + --m_index; + set_device(machine); + m_which = m_interface->indirect_entries() ? subset::INDIRECT : subset::PENS; + } + } -// graphics set handling -static void gfxset_handle_keys(running_machine &machine, ui_gfx_state &state, int xcells, int ycells); -static void gfxset_draw_item(running_machine &machine, gfx_element &gfx, int index, bitmap_rgb32 &bitmap, int dstx, int dsty, int color, int rotate, device_palette_interface *dpalette); -static void gfxset_update_bitmap(running_machine &machine, ui_gfx_state &state, int xcells, int ycells, gfx_element &gfx); -static void gfxset_handler(mame_ui_manager &mui, render_container &container, ui_gfx_state &state); + device_palette_interface *m_interface = nullptr; + unsigned const m_count; + unsigned m_index = 0U; + subset m_which = subset::PENS; + unsigned m_columns = 16U; + int m_offset = 0; + }; -// tilemap handling -static void tilemap_handle_keys(running_machine &machine, ui_gfx_state &state, int viswidth, int visheight); -static void tilemap_update_bitmap(running_machine &machine, ui_gfx_state &state, int width, int height); -static void tilemap_handler(mame_ui_manager &mui, render_container &container, ui_gfx_state &state); + class gfxset + { + public: + struct setinfo + { + void next_color() noexcept + { + if ((m_color_count - 1) > m_color) + ++m_color; + else + m_color = 0U; + } + void prev_color() noexcept + { + if (m_color) + --m_color; + else + m_color = m_color_count - 1; + } + device_palette_interface *m_palette = nullptr; + int m_offset = 0; + unsigned m_color = 0; + unsigned m_color_count = 0U; + uint8_t m_rotate = 0U; + uint8_t m_columns = 16U; + bool m_integer_scale = false; + }; -/*************************************************************************** - CORE IMPLEMENTATION -***************************************************************************/ + class devinfo + { + public: + devinfo(device_gfx_interface &interface, device_palette_interface *first_palette, u8 rotate) : + m_interface(&interface), + m_setcount(0U) + { + for (gfx_element *gfx; (MAX_GFX_ELEMENTS > m_setcount) && ((gfx = interface.gfx(m_setcount)) != nullptr); ++m_setcount) + { + auto &set = m_sets[m_setcount]; + if (gfx->has_palette()) + { + set.m_palette = &gfx->palette(); + set.m_color_count = gfx->colors(); + } + else + { + set.m_palette = first_palette; + set.m_color_count = first_palette->entries() / gfx->granularity(); + if (!set.m_color_count) + set.m_color_count = 1U; + } + set.m_rotate = rotate; + } + } -//------------------------------------------------- -// ui_gfx_init - initialize the graphics viewer -//------------------------------------------------- + device_gfx_interface &interface() const noexcept + { + return *m_interface; + } -void ui_gfx_init(running_machine &machine) -{ - ui_gfx_state *state = &ui_gfx; - uint8_t rotate = machine.system().flags & machine_flags::MASK_ORIENTATION; + unsigned setcount() const noexcept + { + return m_setcount; + } - // make sure we clean up after ourselves - machine.add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(&ui_gfx_exit, &machine)); + setinfo const &set(unsigned index) const noexcept + { + return m_sets[index]; + } - // initialize our global state - memset(state, 0, sizeof(*state)); + setinfo &set(unsigned index) noexcept + { + return m_sets[index]; + } - // set up the palette state - state->palette.columns = 16; + private: + device_gfx_interface *m_interface; + unsigned m_setcount; + setinfo m_sets[MAX_GFX_ELEMENTS]; + }; - // set up the graphics state - for (uint8_t i = 0; i < MAX_GFX_DECODERS; i++) - for (uint8_t j = 0; j < MAX_GFX_ELEMENTS; j++) + gfxset(running_machine &machine) { - state->gfxdev[i].rotate[j] = rotate; - state->gfxdev[i].columns[j] = 16; + // get useful defaults + uint8_t const rotate = machine.system().flags & machine_flags::MASK_ORIENTATION; + device_palette_interface *const first_palette = palette_interface_enumerator(machine.root_device()).first(); + + // iterate over graphics decoders + for (device_gfx_interface &interface : gfx_interface_enumerator(machine.root_device())) + { + // if there are any exposed graphics sets, add the device + if (interface.gfx(0U)) + m_devices.emplace_back(interface, first_palette, rotate); + } } - // set up the tilemap state - state->tilemap.rotate = rotate; - state->tilemap.flags = TILEMAP_DRAW_ALL_CATEGORIES; -} + bool has_gfx() const noexcept + { + return !m_devices.empty(); + } + bool handle_keys(running_machine &machine, int xcells, int ycells); -//------------------------------------------------- -// ui_gfx_count_devices - count the palettes, -// gfx decoders and gfx sets in the machine -//------------------------------------------------- + std::vector<devinfo> m_devices; + unsigned m_device = 0U; + unsigned m_set = 0U; -static void ui_gfx_count_devices(running_machine &machine, ui_gfx_state &state) -{ - // count the palette devices - state.palette.devcount = palette_interface_iterator(machine.root_device()).count(); + private: + bool next_group() noexcept + { + if ((m_devices[m_device].setcount() - 1) > m_set) + { + ++m_set; + return true; + } + else if ((m_devices.size() - 1) > m_device) + { + ++m_device; + m_set = 0U; + return true; + } + else + { + return false; + } + } - // set the pointer to the first palette - if (state.palette.devcount > 0) - palette_set_device(machine, state); + bool prev_group() noexcept + { + if (m_set) + { + --m_set; + return true; + } + else if (m_device) + { + --m_device; + m_set = m_devices[m_device].setcount() - 1; + return true; + } + else + { + return false; + } + } + }; - // count the gfx devices - state.gfxset.devcount = 0; - for (device_gfx_interface &interface : gfx_interface_iterator(machine.root_device())) + class tilemap { - // count the gfx sets in each device, skipping devices with none - uint8_t count = 0; - while (count < MAX_GFX_ELEMENTS && interface.gfx(count) != nullptr) - count++; + public: + tilemap(running_machine &machine) + { + uint8_t const rotate = machine.system().flags & machine_flags::MASK_ORIENTATION; + m_info.resize(machine.tilemap().count()); + for (auto &info : m_info) + info.m_rotate = rotate; + } - // count = index of first nullptr - if (count > 0) + unsigned index() const noexcept { - state.gfxdev[state.gfxset.devcount].interface = &interface; - state.gfxdev[state.gfxset.devcount].setcount = count; - for (uint8_t slot = 0; slot != count; slot++) { - auto gfx = interface.gfx(slot); - if (gfx->has_palette()) + return m_index; + } + + float zoom_scale() const noexcept + { + auto const &info = m_info[m_index]; + return info.m_zoom_frac ? (1.0f / float(info.m_zoom)) : float(info.m_zoom); + } + + bool auto_zoom() const noexcept + { + return m_info[m_index].m_auto_zoom; + } + + uint8_t rotate() const noexcept + { + return m_info[m_index].m_rotate; + } + + uint32_t flags() const noexcept + { + return m_info[m_index].m_flags; + } + + int xoffs() const noexcept + { + return m_info[m_index].m_xoffs; + } + + int yoffs() const noexcept + { + return m_info[m_index].m_yoffs; + } + + bool handle_keys(running_machine &machine, float pixelscale); + + private: + static constexpr int MAX_ZOOM_LEVEL = 8; // maximum tilemap zoom ratio screen:native + static constexpr int MIN_ZOOM_LEVEL = 8; // minimum tilemap zoom ratio native:screen + + struct info + { + bool zoom_in(float pixelscale) noexcept + { + if (m_auto_zoom) + { + // auto zoom never uses fractional factors + m_zoom = std::min<int>(std::lround(pixelscale) + 1, MAX_ZOOM_LEVEL); + m_zoom_frac = false; + m_auto_zoom = false; + return true; + } + else if (m_zoom_frac) { - state.gfxdev[state.gfxset.devcount].palette[slot] = &gfx->palette(); - state.gfxdev[state.gfxset.devcount].color_count[slot] = gfx->colors(); + m_zoom--; + if (m_zoom == 1) + m_zoom_frac = false; // entering integer zoom range + return true; + } + else if (MAX_ZOOM_LEVEL > m_zoom) + { + m_zoom++; // remaining in integer zoom range + return true; } else { - state.gfxdev[state.gfxset.devcount].palette[slot] = state.palette.interface; - state.gfxdev[state.gfxset.devcount].color_count[slot] = state.palette.interface->entries() / gfx->granularity(); - if (!state.gfxdev[state.gfxset.devcount].color_count[slot]) - state.gfxdev[state.gfxset.devcount].color_count[slot] = 1; + return false; } } - if (++state.gfxset.devcount == MAX_GFX_DECODERS) - break; + + bool zoom_out(float pixelscale) noexcept + { + if (m_auto_zoom) + { + // auto zoom never uses fractional factors + m_zoom = std::lround(pixelscale) - 1; + m_zoom_frac = !m_zoom; + if (m_zoom_frac) + m_zoom = 2; + m_auto_zoom = false; + return true; + } + else if (!m_zoom_frac) + { + if (m_zoom == 1) + { + m_zoom++; + m_zoom_frac = true; // entering fractional zoom range + } + else + { + m_zoom--; // remaining in integer zoom range + } + return true; + } + else if (MIN_ZOOM_LEVEL > m_zoom) + { + m_zoom++; // remaining in fractional zoom range + return true; + } + else + { + return false; + } + } + + bool next_category() noexcept + { + if (TILEMAP_DRAW_ALL_CATEGORIES == m_flags) + { + m_flags = 0U; + return true; + } + else if (TILEMAP_DRAW_CATEGORY_MASK > m_flags) + { + ++m_flags; + return true; + } + else + { + return false; + } + } + + bool prev_catagory() noexcept + { + if (!m_flags) + { + m_flags = TILEMAP_DRAW_ALL_CATEGORIES; + return true; + } + else if (TILEMAP_DRAW_ALL_CATEGORIES != m_flags) + { + --m_flags; + return true; + } + else + { + return false; + } + } + + int m_xoffs = 0; + int m_yoffs = 0; + unsigned m_zoom = 1U; + bool m_zoom_frac = false; + bool m_auto_zoom = true; + uint8_t m_rotate = 0U; + uint32_t m_flags = TILEMAP_DRAW_ALL_CATEGORIES; + }; + + static int scroll_step(running_machine &machine) + { + auto &input = machine.input(); + if (input.code_pressed(KEYCODE_LCONTROL) || input.code_pressed(KEYCODE_RCONTROL)) + return 64; + else if (input.code_pressed(KEYCODE_LSHIFT) || input.code_pressed(KEYCODE_RSHIFT)) + return 1; + else + return 8; } + + std::vector<info> m_info; + unsigned m_index = 0U; + }; + + bool is_relevant() const noexcept + { + return m_palette.interface() || m_gfxset.has_gfx() || m_machine.tilemap().count(); } - state.started = true; -} + uint32_t handle_general_keys(bool uistate) + { + auto &input = m_machine.ui_input(); + // UI select cycles through views + if (input.pressed(IPT_UI_SELECT)) + { + m_mode = view((int(m_mode) + 1) % 3); + if (0 > m_current_pointer) + { + m_pointer_type = ui_event::pointer::UNKNOWN; + m_pointer_x = -1.0F; + m_pointer_x = -1.0F; + m_pointer_inside = false; + } + m_bitmap_dirty = true; + } -//------------------------------------------------- -// ui_gfx_exit - clean up after ourselves -//------------------------------------------------- + // pause does what you'd expect + if (input.pressed(IPT_UI_PAUSE)) + { + if (m_machine.paused()) + m_machine.resume(); + else + m_machine.pause(); + } -static void ui_gfx_exit(running_machine &machine) -{ - // free the texture - machine.render().texture_free(ui_gfx.texture); - ui_gfx.texture = nullptr; + // cancel or graphics viewer dismisses the viewer + if (input.pressed(IPT_UI_BACK) || input.pressed(IPT_UI_SHOW_GFX)) + return cancel(uistate); - // free the bitmap - global_free(ui_gfx.bitmap); - ui_gfx.bitmap = nullptr; -} + return uistate; + } + uint32_t cancel(bool uistate) + { + if (!uistate) + m_machine.resume(); + m_machine.ui_input().reset(); + m_current_pointer = -1; + m_pointer_type = ui_event::pointer::UNKNOWN; + m_pointer_buttons = 0U; + m_pointer_x = -1.0F; + m_pointer_y = -1.0F; + m_pointer_inside = false; + m_bitmap_dirty = true; + return mame_ui_manager::HANDLER_CANCEL; + } -//------------------------------------------------- -// ui_gfx_is_relevant - returns 'true' if the -// internal graphics viewer has relevance -// -// NOTE: this must not be called before machine -// initialization is complete, as some drivers -// create or modify gfx sets in VIDEO_START -//------------------------------------------------- + uint32_t handle_palette(mame_ui_manager &mui, render_container &container, bool uistate); + uint32_t handle_gfxset(mame_ui_manager &mui, render_container &container, bool uistate); + uint32_t handle_tilemap(mame_ui_manager &mui, render_container &container, bool uistate); -bool ui_gfx_is_relevant(running_machine &machine) -{ - ui_gfx_state &state = ui_gfx; + void update_gfxset_bitmap(int xcells, int ycells, gfx_element &gfx); + void update_tilemap_bitmap(int width, int height); - if (!state.started) - ui_gfx_count_devices(machine, state); + void gfxset_draw_item(gfx_element &gfx, int index, int dstx, int dsty, gfxset::setinfo const &info); - return state.palette.devcount > 0 - || state.gfxset.devcount > 0 - || machine.tilemap().count() > 0; -} + void draw_text(mame_ui_manager &mui, render_container &container, std::string_view str, float x, float y) + { + render_font *const font = mui.get_font(); + float const height = mui.get_line_height(); + float const aspect = m_machine.render().ui_aspect(&container); + rgb_t const color = mui.colors().text_color(); + + int n; + char32_t ch; + while ((n = uchar_from_utf8(&ch, str)) != 0) + { + if (0 > n) + ch = 0xfffd; + str.remove_prefix((0 > n) ? 1 : n); + container.add_char(x, y, height, aspect, color, *font, ch); + x += font->char_width(height, aspect, ch); + } + } + void resize_bitmap(int32_t width, int32_t height) + { + if (!m_bitmap.valid() || !m_texture || (m_bitmap.width() != width) || (m_bitmap.height() != height)) + { + // free the old stuff + if (m_texture) + m_machine.render().texture_free(m_texture); -//------------------------------------------------- -// ui_gfx_ui_handler - primary UI handler -//------------------------------------------------- + // allocate new stuff + m_bitmap.resize(width, height); + m_texture = m_machine.render().texture_alloc(); + m_texture->set_bitmap(m_bitmap, m_bitmap.cliprect(), TEXFORMAT_ARGB32); -uint32_t ui_gfx_ui_handler(render_container &container, mame_ui_manager &mui, bool uistate) + // force a redraw + m_bitmap_dirty = true; + } + } + + bool map_mouse(render_container &container, render_bounds const &clip, float &x, float &y) const + { + if (((0 > m_current_pointer) && (m_pointer_type != ui_event::pointer::TOUCH)) || !m_pointer_inside) + return false; + + x = m_pointer_x; + y = m_pointer_y; + return clip.includes(x, y); + } + + running_machine &m_machine; + view m_mode = view::PALETTE; + + s32 m_current_pointer = -1; + ui_event::pointer m_pointer_type = ui_event::pointer::UNKNOWN; + u32 m_pointer_buttons = 0U; + float m_pointer_x = -1.0F; + float m_pointer_y = -1.0F; + bool m_pointer_inside = false; + + bitmap_rgb32 m_bitmap; + render_texture *m_texture = nullptr; + bool m_bitmap_dirty = false; + + palette m_palette; + gfxset m_gfxset; + tilemap m_tilemap; +}; + + +void gfx_viewer::palette::handle_keys(running_machine &machine) { - ui_gfx_state &state = ui_gfx; + auto &input = machine.ui_input(); - // if we have nothing, implicitly cancel - if (!ui_gfx_is_relevant(mui.machine())) - goto cancel; + // handle zoom (minus,plus) + if (input.pressed(IPT_UI_ZOOM_OUT)) + m_columns = std::min<unsigned>(m_columns * 2, 64); + if (input.pressed(IPT_UI_ZOOM_IN)) + m_columns = std::max<unsigned>(m_columns / 2, 4); + if (input.pressed(IPT_UI_ZOOM_DEFAULT)) + m_columns = 16; - // if we're not paused, mark the bitmap dirty - if (!mui.machine().paused()) - state.bitmap_dirty = true; + // handle colormap selection (open bracket,close bracket) + if (input.pressed(IPT_UI_PREV_GROUP)) + prev_group(machine); + if (input.pressed(IPT_UI_NEXT_GROUP)) + next_group(machine); - // switch off the state to display something -again: - switch (state.mode) - { - case UI_GFX_PALETTE: - // if we have a palette, display it - if (state.palette.devcount > 0) - { - palette_handler(mui, container, state); - break; - } + // cache some info in locals + int const total = (subset::INDIRECT == m_which) ? m_interface->indirect_entries() : m_interface->entries(); - // fall through... - state.mode++; + // determine number of entries per row and total + int const rowcount = m_columns; + int const screencount = rowcount * rowcount; - case UI_GFX_GFXSET: - // if we have graphics sets, display them - if (state.gfxset.devcount > 0) - { - gfxset_handler(mui, container, state); - break; - } + // handle keyboard navigation + if (input.pressed_repeat(IPT_UI_UP, 4)) + m_offset -= rowcount; + if (input.pressed_repeat(IPT_UI_DOWN, 4)) + m_offset += rowcount; + if (input.pressed_repeat(IPT_UI_PAGE_UP, 6)) + m_offset -= screencount; + if (input.pressed_repeat(IPT_UI_PAGE_DOWN, 6)) + m_offset += screencount; + if (input.pressed_repeat(IPT_UI_HOME, 4)) + m_offset = 0; + if (input.pressed_repeat(IPT_UI_END, 4)) + m_offset = total; - // fall through... - state.mode++; + // clamp within range + if (m_offset + screencount > ((total + rowcount - 1) / rowcount) * rowcount) + m_offset = ((total + rowcount - 1) / rowcount) * rowcount - screencount; + if (m_offset < 0) + m_offset = 0; +} - case UI_GFX_TILEMAP: - // if we have tilemaps, display them - if (mui.machine().tilemap().count() > 0) - { - tilemap_handler(mui, container, state); - break; - } - state.mode = UI_GFX_PALETTE; - goto again; +bool gfx_viewer::gfxset::handle_keys(running_machine &machine, int xcells, int ycells) +{ + auto &input = machine.ui_input(); + bool const shift_pressed = machine.input().code_pressed(KEYCODE_LSHIFT) || machine.input().code_pressed(KEYCODE_RSHIFT); + bool result = false; + + // handle previous/next group + if (input.pressed(IPT_UI_PREV_GROUP) && prev_group()) + result = true; + if (input.pressed(IPT_UI_NEXT_GROUP) && next_group()) + result = true; + + auto &info = m_devices[m_device]; + auto &set = info.set(m_set); + auto &gfx = *info.interface().gfx(m_set); + + // handle cells per line (0/-/=) + if (input.pressed(IPT_UI_ZOOM_OUT) && (xcells < 128)) + { + set.m_columns = xcells + 1; + set.m_integer_scale = shift_pressed; + result = true; + } + if (input.pressed(IPT_UI_ZOOM_IN) && (xcells > 2)) + { + set.m_columns = xcells - 1; + set.m_integer_scale = shift_pressed; + result = true; + } + if (input.pressed(IPT_UI_ZOOM_DEFAULT) && ((xcells != 16) || (set.m_integer_scale != shift_pressed))) + { + set.m_columns = 16; + set.m_integer_scale = shift_pressed; + result = true; } - // handle keys - if (mui.machine().ui_input().pressed(IPT_UI_SELECT)) + // handle rotation (R) + if (input.pressed(IPT_UI_ROTATE)) { - state.mode = (state.mode + 1) % 3; - state.bitmap_dirty = true; + set.m_rotate = orientation_add(ROT90, set.m_rotate); + result = true; } - if (mui.machine().ui_input().pressed(IPT_UI_PAUSE)) + // handle navigation within the cells (up,down,pgup,pgdown) + if (input.pressed_repeat(IPT_UI_UP, 4)) { - if (mui.machine().paused()) - mui.machine().resume(); - else - mui.machine().pause(); + set.m_offset -= xcells; + result = true; + } + if (input.pressed_repeat(IPT_UI_DOWN, 4)) + { + set.m_offset += xcells; + result = true; + } + if (input.pressed_repeat(IPT_UI_PAGE_UP, 6)) + { + set.m_offset -= xcells * ycells; + result = true; + } + if (input.pressed_repeat(IPT_UI_PAGE_DOWN, 6)) + { + set.m_offset += xcells * ycells; + result = true; + } + if (input.pressed_repeat(IPT_UI_HOME, 4)) + { + set.m_offset = 0; + result = true; + } + if (input.pressed_repeat(IPT_UI_END, 4)) + { + set.m_offset = gfx.elements(); + result = true; } - if (mui.machine().ui_input().pressed(IPT_UI_CANCEL) || mui.machine().ui_input().pressed(IPT_UI_SHOW_GFX)) - goto cancel; + // clamp within range + if (set.m_offset + xcells * ycells > ((gfx.elements() + xcells - 1) / xcells) * xcells) + { + set.m_offset = ((gfx.elements() + xcells - 1) / xcells) * xcells - xcells * ycells; + result = true; + } + if (set.m_offset < 0) + { + set.m_offset = 0; + result = true; + } - return uistate; + // handle color selection (left,right) + if (input.pressed_repeat(IPT_UI_LEFT, 4)) + { + set.prev_color(); + result = true; + } + if (input.pressed_repeat(IPT_UI_RIGHT, 4)) + { + set.next_color(); + result = true; + } -cancel: - if (!uistate) - mui.machine().resume(); - state.bitmap_dirty = true; - return UI_HANDLER_CANCEL; + return result; } +bool gfx_viewer::tilemap::handle_keys(running_machine &machine, float pixelscale) +{ + auto &input = machine.ui_input(); + bool result = false; -/*************************************************************************** - PALETTE VIEWER -***************************************************************************/ + // handle tilemap selection (open bracket,close bracket) + if (input.pressed(IPT_UI_PREV_GROUP) && m_index > 0) + { + m_index--; + result = true; + } + if (input.pressed(IPT_UI_NEXT_GROUP) && ((m_info.size() - 1) > m_index)) + { + m_index++; + result = true; + } -//------------------------------------------------- -// palette_set_device - set the pointer to the -// current palette device -//------------------------------------------------- + auto &info = m_info[m_index]; -static void palette_set_device(running_machine &machine, ui_gfx_state &state) -{ - palette_interface_iterator pal_iter(machine.root_device()); - state.palette.interface = pal_iter.byindex(state.palette.devindex); -} + // handle zoom (minus,plus) + if (input.pressed(IPT_UI_ZOOM_OUT) && info.zoom_out(pixelscale)) + { + result = true; + machine.popmessage(info.m_zoom_frac ? _("gfxview", "Zoom = 1/%1$d") : _("gfxview", "Zoom = %1$d"), info.m_zoom); + } + if (input.pressed(IPT_UI_ZOOM_IN) && info.zoom_in(pixelscale)) + { + result = true; + machine.popmessage(info.m_zoom_frac ? _("gfxview", "Zoom = 1/%1$d") : _("gfxview", "Zoom = %1$d"), info.m_zoom); + } + if (input.pressed(IPT_UI_ZOOM_DEFAULT) && !info.m_auto_zoom) + { + info.m_auto_zoom = true; + machine.popmessage(_("gfxview", "Expand to fit")); + } + // handle rotation (R) + if (input.pressed(IPT_UI_ROTATE)) + { + info.m_rotate = orientation_add(ROT90, info.m_rotate); + result = true; + } -//------------------------------------------------- -// palette_handler - handler for the palette -// viewer -//------------------------------------------------- + // return to (0,0) (HOME) + if (input.pressed(IPT_UI_HOME)) + { + info.m_xoffs = 0; + info.m_yoffs = 0; + result = true; + } + + // handle flags (category) + if (input.pressed(IPT_UI_PAGE_UP) && info.prev_catagory()) + { + result = true; + if (TILEMAP_DRAW_ALL_CATEGORIES == info.m_flags) + machine.popmessage(_("gfxview", "All categories")); + else + machine.popmessage(_("gfxview", "Category %1$d"), info.m_flags); + } + if (input.pressed(IPT_UI_PAGE_DOWN) && info.next_category()) + { + result = true; + machine.popmessage(_("gfxview", "Category %1$d"), info.m_flags); + } -static void palette_handler(mame_ui_manager &mui, render_container &container, ui_gfx_state &state) + // handle navigation (up,down,left,right), taking orientation into account + int const step = scroll_step(machine); // this may be applied more than once if multiple directions are pressed + if (input.pressed_repeat(IPT_UI_UP, 4)) + { + if (info.m_rotate & ORIENTATION_SWAP_XY) + info.m_xoffs -= (info.m_rotate & ORIENTATION_FLIP_Y) ? -step : step; + else + info.m_yoffs -= (info.m_rotate & ORIENTATION_FLIP_Y) ? -step : step; + result = true; + } + if (input.pressed_repeat(IPT_UI_DOWN, 4)) + { + if (info.m_rotate & ORIENTATION_SWAP_XY) + info.m_xoffs += (info.m_rotate & ORIENTATION_FLIP_Y) ? -step : step; + else + info.m_yoffs += (info.m_rotate & ORIENTATION_FLIP_Y) ? -step : step; + result = true; + } + if (input.pressed_repeat(IPT_UI_LEFT, 6)) + { + if (info.m_rotate & ORIENTATION_SWAP_XY) + info.m_yoffs -= (info.m_rotate & ORIENTATION_FLIP_X) ? -step : step; + else + info.m_xoffs -= (info.m_rotate & ORIENTATION_FLIP_X) ? -step : step; + result = true; + } + if (input.pressed_repeat(IPT_UI_RIGHT, 6)) + { + if (info.m_rotate & ORIENTATION_SWAP_XY) + info.m_yoffs += (info.m_rotate & ORIENTATION_FLIP_X) ? -step : step; + else + info.m_xoffs += (info.m_rotate & ORIENTATION_FLIP_X) ? -step : step; + result = true; + } + + // cache some info in locals + tilemap_t *const tilemap = machine.tilemap().find(m_index); + uint32_t const mapwidth = tilemap->width(); + uint32_t const mapheight = tilemap->height(); + + // clamp within range + while (info.m_xoffs < 0) + info.m_xoffs += mapwidth; + while (info.m_xoffs >= mapwidth) + info.m_xoffs -= mapwidth; + while (info.m_yoffs < 0) + info.m_yoffs += mapheight; + while (info.m_yoffs >= mapheight) + info.m_yoffs -= mapheight; + + return result; +} + + +uint32_t gfx_viewer::handle_palette(mame_ui_manager &mui, render_container &container, bool uistate) { - device_palette_interface *palette = state.palette.interface; - palette_device *paldev = dynamic_cast<palette_device *>(&palette->device()); - - int total = state.palette.which ? palette->indirect_entries() : palette->entries(); - const rgb_t *raw_color = palette->palette()->entry_list_raw(); - render_font *ui_font = mui.get_font(); - float chwidth, chheight; - float titlewidth; - float x0, y0; - render_bounds cellboxbounds; - render_bounds boxbounds; - int x, y, skip; + device_palette_interface &palette = *m_palette.interface(); + palette_device *const paldev = dynamic_cast<palette_device *>(&palette.device()); + + bool const indirect = m_palette.indirect(); + unsigned const total = indirect ? palette.indirect_entries() : palette.entries(); + rgb_t const *const raw_color = palette.palette()->entry_list_raw(); // add a half character padding for the box - chheight = mui.get_line_height(); - chwidth = ui_font->char_width(chheight, mui.machine().render().ui_aspect(), '0'); - boxbounds.x0 = 0.0f + 0.5f * chwidth; - boxbounds.x1 = 1.0f - 0.5f * chwidth; - boxbounds.y0 = 0.0f + 0.5f * chheight; - boxbounds.y1 = 1.0f - 0.5f * chheight; + render_font *const ui_font = mui.get_font(); + float const aspect = m_machine.render().ui_aspect(&container); + float const chheight = mui.get_line_height(); + float const chwidth = ui_font->char_width(chheight, aspect, '0'); + render_bounds const boxbounds{ + 0.0f + (0.5f * chwidth), + 0.0f + (0.5f * chheight), + 1.0f - (0.5f * chwidth), + 1.0f - (0.5f * chheight) }; // the character cell box bounds starts a half character in from the box - cellboxbounds = boxbounds; + render_bounds cellboxbounds = boxbounds; cellboxbounds.x0 += 0.5f * chwidth; - cellboxbounds.x1 -= 0.5f * chwidth; cellboxbounds.y0 += 0.5f * chheight; + cellboxbounds.x1 -= 0.5f * chwidth; cellboxbounds.y1 -= 0.5f * chheight; // add space on the left for 5 characters of text, plus a half character of padding @@ -398,41 +1015,57 @@ static void palette_handler(mame_ui_manager &mui, render_container &container, u cellboxbounds.y0 += 3.0f * chheight; // compute the cell size - float cellwidth = (cellboxbounds.x1 - cellboxbounds.x0) / (float)state.palette.columns; - float cellheight = (cellboxbounds.y1 - cellboxbounds.y0) / (float)state.palette.columns; + float const cellwidth = (cellboxbounds.x1 - cellboxbounds.x0) / float(m_palette.columns()); + float const cellheight = (cellboxbounds.y1 - cellboxbounds.y0) / float(m_palette.columns()); // figure out the title std::ostringstream title_buf; - util::stream_format(title_buf, "'%s'", palette->device().tag()); - if (palette->indirect_entries() > 0) - title_buf << (state.palette.which ? _(" COLORS") : _(" PENS")); + util::stream_format(title_buf, + !palette.indirect_entries() ? _("gfxview", "[root%1$s]") : indirect ? _("gfxview", "[root%1$s] colors") : _("gfxview", "[root%1$s] pens"), + palette.device().tag()); // if the mouse pointer is over one of our cells, add some info about the corresponding palette entry - int32_t mouse_target_x, mouse_target_y; float mouse_x, mouse_y; - bool mouse_button; - render_target *mouse_target = mui.machine().ui_input().find_mouse(&mouse_target_x, &mouse_target_y, &mouse_button); - if (mouse_target != nullptr && mouse_target->map_point_container(mouse_target_x, mouse_target_y, container, mouse_x, mouse_y) - && cellboxbounds.x0 <= mouse_x && cellboxbounds.x1 > mouse_x - && cellboxbounds.y0 <= mouse_y && cellboxbounds.y1 > mouse_y) + if (map_mouse(container, cellboxbounds, mouse_x, mouse_y)) { - int index = state.palette.offset + int((mouse_x - cellboxbounds.x0) / cellwidth) + int((mouse_y - cellboxbounds.y0) / cellheight) * state.palette.columns; + int const index = m_palette.index(int((mouse_x - cellboxbounds.x0) / cellwidth), int((mouse_y - cellboxbounds.y0) / cellheight)); if (index < total) { - util::stream_format(title_buf, " #%X", index); - if (palette->indirect_entries() > 0 && !state.palette.which) - util::stream_format(title_buf, " => %X", palette->pen_indirect(index)); - else if (paldev != nullptr && paldev->basemem().base() != nullptr) - util::stream_format(title_buf, " = %X", paldev->read_entry(index)); - - rgb_t col = state.palette.which ? palette->indirect_color(index) : raw_color[index]; - util::stream_format(title_buf, " (A:%X R:%X G:%X B:%X)", col.a(), col.r(), col.g(), col.b()); + rgb_t const col = indirect ? palette.indirect_color(index) : raw_color[index]; + if (palette.indirect_entries() && !indirect) + { + util::stream_format(title_buf, + _("gfxview", u8" #%1$X \u2192 %2$X (A:%3$02X R:%4$02X G:%5$02X B:%6$02X)"), + index, palette.pen_indirect(index), + col.a(), col.r(), col.g(), col.b()); + } + else if (paldev && paldev->basemem().base()) + { + util::stream_format(title_buf, + _("gfxview", " #%1$X = %2$X (A:%3$02X R:%4$02X G:%5$02X B:%6$02X)"), + index, paldev->read_entry(index), + col.a(), col.r(), col.g(), col.b()); + } + else + { + util::stream_format(title_buf, + _("gfxview", " #%1$X (A:%2$02X R:%3$02X G:%4$02X B:%5$02X)"), + index, + col.a(), col.r(), col.g(), col.b()); + } + + // keep touch pointer displayed after release so they know what it's pointing at + mame_ui_manager::display_pointer pointers[1]{ { m_machine.render().ui_target(), m_pointer_type, m_pointer_x, m_pointer_y } }; + if (ui_event::pointer::TOUCH == m_pointer_type) + mui.set_pointers(std::begin(pointers), std::end(pointers)); } } + float x0, y0; + // expand the outer box to fit the title - const std::string title = title_buf.str(); - titlewidth = ui_font->string_width(chheight, mui.machine().render().ui_aspect(), title.c_str()); + std::string const title = std::move(title_buf).str(); + float const titlewidth = ui_font->string_width(chheight, aspect, title); x0 = 0.0f; if (boxbounds.x1 - boxbounds.x0 < titlewidth + chwidth) x0 = boxbounds.x0 - (0.5f - 0.5f * (titlewidth + chwidth)); @@ -441,196 +1074,97 @@ static void palette_handler(mame_ui_manager &mui, render_container &container, u mui.draw_outlined_box(container, boxbounds.x0 - x0, boxbounds.y0, boxbounds.x1 + x0, boxbounds.y1, mui.colors().gfxviewer_bg_color()); // draw the title - x0 = 0.5f - 0.5f * titlewidth; - y0 = boxbounds.y0 + 0.5f * chheight; - for (auto ch : title) - { - container.add_char(x0, y0, chheight, mui.machine().render().ui_aspect(), rgb_t::white(), *ui_font, ch); - x0 += ui_font->char_width(chheight, mui.machine().render().ui_aspect(), ch); - } + draw_text(mui, container, title, 0.5f - 0.5f * titlewidth, boxbounds.y0 + 0.5f * chheight); // draw the top column headers - skip = (int)(chwidth / cellwidth); - for (x = 0; x < state.palette.columns; x += 1 + skip) + int const rowskip = int(chwidth / cellwidth); + for (int x = 0; x < m_palette.columns(); x += 1 + rowskip) { - x0 = boxbounds.x0 + 6.0f * chwidth + (float)x * cellwidth; + x0 = boxbounds.x0 + 6.0f * chwidth + float(x) * cellwidth; y0 = boxbounds.y0 + 2.0f * chheight; - container.add_char(x0 + 0.5f * (cellwidth - chwidth), y0, chheight, mui.machine().render().ui_aspect(), rgb_t::white(), *ui_font, "0123456789ABCDEF"[x & 0xf]); + container.add_char(x0 + 0.5f * (cellwidth - chwidth), y0, chheight, aspect, rgb_t::white(), *ui_font, "0123456789ABCDEF"[x & 0xf]); - // if we're skipping, draw a point between the character and the box to indicate which - // one it's referring to - if (skip != 0) + // if we're skipping, draw a point between the character and the box to indicate which one it's referring to + if (rowskip) container.add_point(x0 + 0.5f * cellwidth, 0.5f * (y0 + chheight + cellboxbounds.y0), UI_LINE_WIDTH, rgb_t::white(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } // draw the side column headers - skip = (int)(chheight / cellheight); - for (y = 0; y < state.palette.columns; y += 1 + skip) - + int const colskip = int(chheight / cellheight); + for (int y = 0; y < m_palette.columns(); y += 1 + colskip) + { // only display if there is data to show - if (state.palette.offset + y * state.palette.columns < total) + unsigned const index = m_palette.index(0, y); + if (index < total) { - char buffer[10]; - // if we're skipping, draw a point between the character and the box to indicate which // one it's referring to x0 = boxbounds.x0 + 5.5f * chwidth; - y0 = boxbounds.y0 + 3.5f * chheight + (float)y * cellheight; - if (skip != 0) + y0 = boxbounds.y0 + 3.5f * chheight + float(y) * cellheight; + if (colskip != 0) container.add_point(0.5f * (x0 + cellboxbounds.x0), y0 + 0.5f * cellheight, UI_LINE_WIDTH, rgb_t::white(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); // draw the row header - sprintf(buffer, "%5X", state.palette.offset + y * state.palette.columns); - for (x = 4; x >= 0; x--) + auto buffer = util::string_format("%5X", index); + for (int x = 4; x >= 0; x--) { - x0 -= ui_font->char_width(chheight, mui.machine().render().ui_aspect(), buffer[x]); - container.add_char(x0, y0 + 0.5f * (cellheight - chheight), chheight, mui.machine().render().ui_aspect(), rgb_t::white(), *ui_font, buffer[x]); + x0 -= ui_font->char_width(chheight, aspect, buffer[x]); + container.add_char(x0, y0 + 0.5f * (cellheight - chheight), chheight, aspect, rgb_t::white(), *ui_font, buffer[x]); } } + } // now add the rectangles for the colors - for (y = 0; y < state.palette.columns; y++) - for (x = 0; x < state.palette.columns; x++) + for (int y = 0; y < m_palette.columns(); y++) + { + for (int x = 0; x < m_palette.columns(); x++) { - int index = state.palette.offset + y * state.palette.columns + x; + int const index = m_palette.index(x, y); if (index < total) { - pen_t pen = state.palette.which ? palette->indirect_color(index) : raw_color[index]; - container.add_rect(cellboxbounds.x0 + x * cellwidth, cellboxbounds.y0 + y * cellheight, - cellboxbounds.x0 + (x + 1) * cellwidth, cellboxbounds.y0 + (y + 1) * cellheight, - 0xff000000 | pen, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + pen_t const pen = indirect ? palette.indirect_color(index) : raw_color[index]; + container.add_rect( + cellboxbounds.x0 + x * cellwidth, cellboxbounds.y0 + y * cellheight, + cellboxbounds.x0 + (x + 1) * cellwidth, cellboxbounds.y0 + (y + 1) * cellheight, + 0xff000000 | pen, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } } - - // handle keys - palette_handle_keys(mui.machine(), state); -} - - -//------------------------------------------------- -// palette_handle_keys - handle key inputs for -// the palette viewer -//------------------------------------------------- - -static void palette_handle_keys(running_machine &machine, ui_gfx_state &state) -{ - device_palette_interface *palette = state.palette.interface; - int rowcount, screencount; - int total; - - // handle zoom (minus,plus) - if (machine.ui_input().pressed(IPT_UI_ZOOM_OUT)) - state.palette.columns /= 2; - if (machine.ui_input().pressed(IPT_UI_ZOOM_IN)) - state.palette.columns *= 2; - - // clamp within range - if (state.palette.columns <= 4) - state.palette.columns = 4; - if (state.palette.columns > 64) - state.palette.columns = 64; - - // handle colormap selection (open bracket,close bracket) - if (machine.ui_input().pressed(IPT_UI_PREV_GROUP)) - { - if (state.palette.which) - state.palette.which = 0; - else if (state.palette.devindex > 0) - { - state.palette.devindex--; - palette_set_device(machine, state); - palette = state.palette.interface; - state.palette.which = (palette->indirect_entries() > 0); - } } - if (machine.ui_input().pressed(IPT_UI_NEXT_GROUP)) - { - if (!state.palette.which && palette->indirect_entries() > 0) - state.palette.which = 1; - else if (state.palette.devindex < state.palette.devcount - 1) - { - state.palette.devindex++; - palette_set_device(machine, state); - palette = state.palette.interface; - state.palette.which = 0; - } - } - - // cache some info in locals - total = state.palette.which ? palette->indirect_entries() : palette->entries(); - - // determine number of entries per row and total - rowcount = state.palette.columns; - screencount = rowcount * rowcount; - // handle keyboard navigation - if (machine.ui_input().pressed_repeat(IPT_UI_UP, 4)) - state.palette.offset -= rowcount; - if (machine.ui_input().pressed_repeat(IPT_UI_DOWN, 4)) - state.palette.offset += rowcount; - if (machine.ui_input().pressed_repeat(IPT_UI_PAGE_UP, 6)) - state.palette.offset -= screencount; - if (machine.ui_input().pressed_repeat(IPT_UI_PAGE_DOWN, 6)) - state.palette.offset += screencount; - if (machine.ui_input().pressed_repeat(IPT_UI_HOME, 4)) - state.palette.offset = 0; - if (machine.ui_input().pressed_repeat(IPT_UI_END, 4)) - state.palette.offset = total; - - // clamp within range - if (state.palette.offset + screencount > ((total + rowcount - 1) / rowcount) * rowcount) - state.palette.offset = ((total + rowcount - 1) / rowcount) * rowcount - screencount; - if (state.palette.offset < 0) - state.palette.offset = 0; + // handle keys + m_palette.handle_keys(m_machine); + return handle_general_keys(uistate); } - -/*************************************************************************** - GRAPHICS VIEWER -***************************************************************************/ - -//------------------------------------------------- -// gfxset_handler - handler for the graphics -// viewer -//------------------------------------------------- - -static void gfxset_handler(mame_ui_manager &mui, render_container &container, ui_gfx_state &state) +uint32_t gfx_viewer::handle_gfxset(mame_ui_manager &mui, render_container &container, bool uistate) { - render_font *ui_font = mui.get_font(); - int dev = state.gfxset.devindex; - int set = state.gfxset.set; - ui_gfx_info &info = state.gfxdev[dev]; - device_gfx_interface &interface = *info.interface; - gfx_element &gfx = *interface.gfx(set); - float fullwidth, fullheight; - float cellwidth, cellheight; - float chwidth, chheight; - float titlewidth; - float x0, y0; - render_bounds cellboxbounds; - render_bounds boxbounds; - int cellboxwidth, cellboxheight; - int targwidth = mui.machine().render().ui_target().width(); - int targheight = mui.machine().render().ui_target().height(); - int cellxpix, cellypix; - int xcells, ycells; - int pixelscale = 0; - int x, y, skip; + // get graphics info + auto &info = m_gfxset.m_devices[m_gfxset.m_device]; + auto &set = info.set(m_gfxset.m_set); + device_gfx_interface &interface = info.interface(); + gfx_element &gfx = *interface.gfx(m_gfxset.m_set); + + // get some UI metrics + render_font *const ui_font = mui.get_font(); + int const targwidth = m_machine.render().ui_target().width(); + int const targheight = m_machine.render().ui_target().height(); + float const aspect = m_machine.render().ui_aspect(&container); + float const chheight = mui.get_line_height(); + float const chwidth = ui_font->char_width(chheight, aspect, '0'); // add a half character padding for the box - chheight = mui.get_line_height(); - chwidth = ui_font->char_width(chheight, mui.machine().render().ui_aspect(), '0'); - boxbounds.x0 = 0.0f + 0.5f * chwidth; - boxbounds.x1 = 1.0f - 0.5f * chwidth; - boxbounds.y0 = 0.0f + 0.5f * chheight; - boxbounds.y1 = 1.0f - 0.5f * chheight; + render_bounds boxbounds{ + 0.0f + (0.5f * chwidth), + 0.0f + (0.5f * chheight), + 1.0f - (0.5f * chwidth), + 1.0f - (0.5f * chheight) }; // the character cell box bounds starts a half character in from the box - cellboxbounds = boxbounds; + render_bounds cellboxbounds = boxbounds; cellboxbounds.x0 += 0.5f * chwidth; - cellboxbounds.x1 -= 0.5f * chwidth; cellboxbounds.y0 += 0.5f * chheight; + cellboxbounds.x1 -= 0.5f * chwidth; cellboxbounds.y1 -= 0.5f * chheight; // add space on the left for 5 characters of text, plus a half character of padding @@ -640,93 +1174,110 @@ static void gfxset_handler(mame_ui_manager &mui, render_container &container, ui cellboxbounds.y0 += 3.0f * chheight; // convert back to pixels - cellboxwidth = (cellboxbounds.x1 - cellboxbounds.x0) * (float)targwidth; - cellboxheight = (cellboxbounds.y1 - cellboxbounds.y0) * (float)targheight; + float cellboxwidth = cellboxbounds.width() * float(targwidth); + float cellboxheight = cellboxbounds.height() * float(targheight); // compute the number of source pixels in a cell - cellxpix = 1 + ((info.rotate[set] & ORIENTATION_SWAP_XY) ? gfx.height() : gfx.width()); - cellypix = 1 + ((info.rotate[set] & ORIENTATION_SWAP_XY) ? gfx.width() : gfx.height()); + int const cellxpix = 1 + ((set.m_rotate & ORIENTATION_SWAP_XY) ? gfx.height() : gfx.width()); + int const cellypix = 1 + ((set.m_rotate & ORIENTATION_SWAP_XY) ? gfx.width() : gfx.height()); // compute the largest pixel scale factor that still fits - xcells = info.columns[set]; + int xcells = set.m_columns; + float pixelscale = 0.0f; while (xcells > 1) { - pixelscale = (cellboxwidth / xcells) / cellxpix; - if (pixelscale != 0) + pixelscale = cellboxwidth / (xcells * cellxpix); + if (set.m_integer_scale) + pixelscale = std::floor(pixelscale); + if (0.25f <= pixelscale) break; xcells--; } - info.columns[set] = xcells; - - // worst case, we need a pixel scale of 1 - pixelscale = std::max(1, pixelscale); + if (0.0f == pixelscale) + pixelscale = cellboxwidth / (xcells * cellxpix); // in the Y direction, we just display as many as we can - ycells = cellboxheight / (pixelscale * cellypix); + int ycells = int(cellboxheight / (pixelscale * cellypix)); + if (!ycells) + { + ycells = 1; + pixelscale = cellboxheight / cellypix; + xcells = int(cellboxwidth / (pixelscale * cellxpix)); + } // now determine the actual cellbox size + set.m_columns = xcells; cellboxwidth = std::min(cellboxwidth, xcells * pixelscale * cellxpix); cellboxheight = std::min(cellboxheight, ycells * pixelscale * cellypix); - // compute the size of a single cell at this pixel scale factor, as well as the aspect ratio - cellwidth = (cellboxwidth / (float)xcells) / (float)targwidth; - cellheight = (cellboxheight / (float)ycells) / (float)targheight; - //cellaspect = cellwidth / cellheight; + // compute the size of a single cell at this pixel scale factor + float const cellwidth = (cellboxwidth / xcells) / targwidth; + float const cellheight = (cellboxheight / ycells) / targheight; // working from the new width/height, recompute the boxbounds - fullwidth = (float)cellboxwidth / (float)targwidth + 6.5f * chwidth; - fullheight = (float)cellboxheight / (float)targheight + 4.0f * chheight; + float const fullwidth = cellboxwidth / targwidth + 6.5f * chwidth; + float const fullheight = cellboxheight / targheight + 4.0f * chheight; // recompute boxbounds from this boxbounds.x0 = (1.0f - fullwidth) * 0.5f; - boxbounds.x1 = boxbounds.x0 + fullwidth; boxbounds.y0 = (1.0f - fullheight) * 0.5f; + boxbounds.x1 = boxbounds.x0 + fullwidth; boxbounds.y1 = boxbounds.y0 + fullheight; // recompute cellboxbounds cellboxbounds.x0 = boxbounds.x0 + 6.0f * chwidth; - cellboxbounds.x1 = cellboxbounds.x0 + (float)cellboxwidth / (float)targwidth; cellboxbounds.y0 = boxbounds.y0 + 3.5f * chheight; - cellboxbounds.y1 = cellboxbounds.y0 + (float)cellboxheight / (float)targheight; + cellboxbounds.x1 = cellboxbounds.x0 + cellboxwidth / float(targwidth); + cellboxbounds.y1 = cellboxbounds.y0 + cellboxheight / float(targheight); // figure out the title std::ostringstream title_buf; - util::stream_format(title_buf, "'%s' %d/%d", interface.device().tag(), set, info.setcount - 1); + util::stream_format(title_buf, + _("gfxview", "[root%1$s] %2$d/%3$d"), + interface.device().tag(), + m_gfxset.m_set, info.setcount() - 1); // if the mouse pointer is over a pixel in a tile, add some info about the tile and pixel bool found_pixel = false; - int32_t mouse_target_x, mouse_target_y; float mouse_x, mouse_y; - bool mouse_button; - render_target *mouse_target = mui.machine().ui_input().find_mouse(&mouse_target_x, &mouse_target_y, &mouse_button); - if (mouse_target != nullptr && mouse_target->map_point_container(mouse_target_x, mouse_target_y, container, mouse_x, mouse_y) - && cellboxbounds.x0 <= mouse_x && cellboxbounds.x1 > mouse_x - && cellboxbounds.y0 <= mouse_y && cellboxbounds.y1 > mouse_y) + if (map_mouse(container, cellboxbounds, mouse_x, mouse_y)) { - int code = info.offset[set] + int((mouse_x - cellboxbounds.x0) / cellwidth) + int((mouse_y - cellboxbounds.y0) / cellheight) * xcells; + int const code = set.m_offset + int((mouse_x - cellboxbounds.x0) / cellwidth) + int((mouse_y - cellboxbounds.y0) / cellheight) * xcells; int xpixel = int((mouse_x - cellboxbounds.x0) / (cellwidth / cellxpix)) % cellxpix; int ypixel = int((mouse_y - cellboxbounds.y0) / (cellheight / cellypix)) % cellypix; - if (code < gfx.elements() && xpixel < (cellxpix - 1) && ypixel < (cellypix - 1)) + if ((code < gfx.elements()) && (xpixel < (cellxpix - 1)) && (ypixel < (cellypix - 1))) { found_pixel = true; - if (info.rotate[set] & ORIENTATION_FLIP_X) + if (set.m_rotate & ORIENTATION_FLIP_X) xpixel = (cellxpix - 2) - xpixel; - if (info.rotate[set] & ORIENTATION_FLIP_Y) + if (set.m_rotate & ORIENTATION_FLIP_Y) ypixel = (cellypix - 2) - ypixel; - if (info.rotate[set] & ORIENTATION_SWAP_XY) + if (set.m_rotate & ORIENTATION_SWAP_XY) std::swap(xpixel, ypixel); - uint8_t pixdata = gfx.get_data(code)[xpixel + ypixel * gfx.rowbytes()]; - util::stream_format(title_buf, " #%X:%X @ %d,%d = %X", - code, info.color[set], xpixel, ypixel, - gfx.colorbase() + info.color[set] * gfx.granularity() + pixdata); + uint8_t const pixdata = gfx.get_data(code)[xpixel + ypixel * gfx.rowbytes()]; + util::stream_format(title_buf, + _("gfxview", " #%1$X:%2$X (%3$d %4$d) = %5$X"), + code, set.m_color, + xpixel, ypixel, + gfx.colorbase() + (set.m_color * gfx.granularity()) + pixdata); + + // keep touch pointer displayed after release so they know what it's pointing at + mame_ui_manager::display_pointer pointers[1]{ { m_machine.render().ui_target(), m_pointer_type, m_pointer_x, m_pointer_y } }; + if (ui_event::pointer::TOUCH == m_pointer_type) + mui.set_pointers(std::begin(pointers), std::end(pointers)); } } if (!found_pixel) - util::stream_format(title_buf, " %dx%d COLOR %X/%X", gfx.width(), gfx.height(), info.color[set], info.color_count[set]); + util::stream_format(title_buf, + _("gfxview", u8" %1$d\u00d7%2$d color %3$X/%4$X"), + gfx.width(), gfx.height(), + set.m_color, set.m_color_count); + + float x0, y0; // expand the outer box to fit the title - const std::string title = title_buf.str(); - titlewidth = ui_font->string_width(chheight, mui.machine().render().ui_aspect(), title.c_str()); + std::string const title = std::move(title_buf).str(); + float const titlewidth = ui_font->string_width(chheight, aspect, title); x0 = 0.0f; if (boxbounds.x1 - boxbounds.x0 < titlewidth + chwidth) x0 = boxbounds.x0 - (0.5f - 0.5f * (titlewidth + chwidth)); @@ -735,328 +1286,85 @@ static void gfxset_handler(mame_ui_manager &mui, render_container &container, ui mui.draw_outlined_box(container, boxbounds.x0 - x0, boxbounds.y0, boxbounds.x1 + x0, boxbounds.y1, mui.colors().gfxviewer_bg_color()); // draw the title - x0 = 0.5f - 0.5f * titlewidth; - y0 = boxbounds.y0 + 0.5f * chheight; - for (auto ch : title) - { - container.add_char(x0, y0, chheight, mui.machine().render().ui_aspect(), rgb_t::white(), *ui_font, ch); - x0 += ui_font->char_width(chheight, mui.machine().render().ui_aspect(), ch); - } + draw_text(mui, container, title, 0.5f - 0.5f * titlewidth, boxbounds.y0 + 0.5f * chheight); // draw the top column headers - skip = (int)(chwidth / cellwidth); - for (x = 0; x < xcells; x += 1 + skip) + int const colskip = int(chwidth / cellwidth); + for (int x = 0; x < xcells; x += 1 + colskip) { - x0 = boxbounds.x0 + 6.0f * chwidth + (float)x * cellwidth; + x0 = boxbounds.x0 + 6.0f * chwidth + float(x) * cellwidth; y0 = boxbounds.y0 + 2.0f * chheight; - container.add_char(x0 + 0.5f * (cellwidth - chwidth), y0, chheight, mui.machine().render().ui_aspect(), rgb_t::white(), *ui_font, "0123456789ABCDEF"[x & 0xf]); + container.add_char(x0 + 0.5f * (cellwidth - chwidth), y0, chheight, aspect, rgb_t::white(), *ui_font, "0123456789ABCDEF"[x & 0xf]); - // if we're skipping, draw a point between the character and the box to indicate which - // one it's referring to - if (skip != 0) + // if we're skipping, draw a point between the character and the box to indicate which one it's referring to + if (colskip) container.add_point(x0 + 0.5f * cellwidth, 0.5f * (y0 + chheight + boxbounds.y0 + 3.5f * chheight), UI_LINE_WIDTH, rgb_t::white(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } // draw the side column headers - skip = (int)(chheight / cellheight); - for (y = 0; y < ycells; y += 1 + skip) - + int const rowskip = int(chheight / cellheight); + for (int y = 0; y < ycells; y += 1 + rowskip) + { // only display if there is data to show - if (info.offset[set] + y * xcells < gfx.elements()) + if (set.m_offset + (y * xcells) < gfx.elements()) { - char buffer[10]; - - // if we're skipping, draw a point between the character and the box to indicate which - // one it's referring to + // if we're skipping, draw a point between the character and the box to indicate which one it's referring to x0 = boxbounds.x0 + 5.5f * chwidth; - y0 = boxbounds.y0 + 3.5f * chheight + (float)y * cellheight; - if (skip != 0) + y0 = boxbounds.y0 + 3.5f * chheight + float(y) * cellheight; + if (rowskip) container.add_point(0.5f * (x0 + boxbounds.x0 + 6.0f * chwidth), y0 + 0.5f * cellheight, UI_LINE_WIDTH, rgb_t::white(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); // draw the row header - sprintf(buffer, "%5X", info.offset[set] + y * xcells); - for (x = 4; x >= 0; x--) + auto buffer = util::string_format("%5X", set.m_offset + (y * xcells)); + for (int x = 4; x >= 0; x--) { - x0 -= ui_font->char_width(chheight, mui.machine().render().ui_aspect(), buffer[x]); - container.add_char(x0, y0 + 0.5f * (cellheight - chheight), chheight, mui.machine().render().ui_aspect(), rgb_t::white(), *ui_font, buffer[x]); + x0 -= ui_font->char_width(chheight, aspect, buffer[x]); + container.add_char(x0, y0 + 0.5f * (cellheight - chheight), chheight, aspect, rgb_t::white(), *ui_font, buffer[x]); } } + } // update the bitmap - gfxset_update_bitmap(mui.machine(), state, xcells, ycells, gfx); + update_gfxset_bitmap(xcells, ycells, gfx); // add the final quad - container.add_quad(cellboxbounds.x0, cellboxbounds.y0, cellboxbounds.x1, cellboxbounds.y1, - rgb_t::white(), state.texture, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + container.add_quad( + cellboxbounds.x0, cellboxbounds.y0, cellboxbounds.x1, cellboxbounds.y1, + rgb_t::white(), m_texture, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); // handle keyboard navigation before drawing - gfxset_handle_keys(mui.machine(), state, xcells, ycells); + if (m_gfxset.handle_keys(m_machine, xcells, ycells)) + m_bitmap_dirty = true; + return handle_general_keys(uistate); } -//------------------------------------------------- -// gfxset_handle_keys - handle keys for the -// graphics viewer -//------------------------------------------------- - -static void gfxset_handle_keys(running_machine &machine, ui_gfx_state &state, int xcells, int ycells) +uint32_t gfx_viewer::handle_tilemap(mame_ui_manager &mui, render_container &container, bool uistate) { - // handle gfxset selection (open bracket,close bracket) - if (machine.ui_input().pressed(IPT_UI_PREV_GROUP)) - { - if (state.gfxset.set > 0) - state.gfxset.set--; - else if (state.gfxset.devindex > 0) - { - state.gfxset.devindex--; - state.gfxset.set = state.gfxdev[state.gfxset.devindex].setcount - 1; - } - state.bitmap_dirty = true; - } - if (machine.ui_input().pressed(IPT_UI_NEXT_GROUP)) - { - if (state.gfxset.set < state.gfxdev[state.gfxset.devindex].setcount - 1) - state.gfxset.set++; - else if (state.gfxset.devindex < state.gfxset.devcount - 1) - { - state.gfxset.devindex++; - state.gfxset.set = 0; - } - state.bitmap_dirty = true; - } - - // cache some info in locals - int dev = state.gfxset.devindex; - int set = state.gfxset.set; - ui_gfx_info &info = state.gfxdev[dev]; - gfx_element &gfx = *info.interface->gfx(set); - - // handle cells per line (minus,plus) - if (machine.ui_input().pressed(IPT_UI_ZOOM_OUT)) - { info.columns[set] = xcells - 1; state.bitmap_dirty = true; } - - if (machine.ui_input().pressed(IPT_UI_ZOOM_IN)) - { info.columns[set] = xcells + 1; state.bitmap_dirty = true; } - - // clamp within range - if (info.columns[set] < 2) - { info.columns[set] = 2; state.bitmap_dirty = true; } - if (info.columns[set] > 128) - { info.columns[set] = 128; state.bitmap_dirty = true; } - - // handle rotation (R) - if (machine.ui_input().pressed(IPT_UI_ROTATE)) - { - info.rotate[set] = orientation_add(ROT90, info.rotate[set]); - state.bitmap_dirty = true; - } - - // handle navigation within the cells (up,down,pgup,pgdown) - if (machine.ui_input().pressed_repeat(IPT_UI_UP, 4)) - { info.offset[set] -= xcells; state.bitmap_dirty = true; } - if (machine.ui_input().pressed_repeat(IPT_UI_DOWN, 4)) - { info.offset[set] += xcells; state.bitmap_dirty = true; } - if (machine.ui_input().pressed_repeat(IPT_UI_PAGE_UP, 6)) - { info.offset[set] -= xcells * ycells; state.bitmap_dirty = true; } - if (machine.ui_input().pressed_repeat(IPT_UI_PAGE_DOWN, 6)) - { info.offset[set] += xcells * ycells; state.bitmap_dirty = true; } - if (machine.ui_input().pressed_repeat(IPT_UI_HOME, 4)) - { info.offset[set] = 0; state.bitmap_dirty = true; } - if (machine.ui_input().pressed_repeat(IPT_UI_END, 4)) - { info.offset[set] = gfx.elements(); state.bitmap_dirty = true; } - - // clamp within range - if (info.offset[set] + xcells * ycells > ((gfx.elements() + xcells - 1) / xcells) * xcells) - { - info.offset[set] = ((gfx.elements() + xcells - 1) / xcells) * xcells - xcells * ycells; - state.bitmap_dirty = true; - } - if (info.offset[set] < 0) - { info.offset[set] = 0; state.bitmap_dirty = true; } - - // handle color selection (left,right) - if (machine.ui_input().pressed_repeat(IPT_UI_LEFT, 4)) - { info.color[set] -= 1; state.bitmap_dirty = true; } - if (machine.ui_input().pressed_repeat(IPT_UI_RIGHT, 4)) - { info.color[set] += 1; state.bitmap_dirty = true; } - - // clamp within range - if (info.color[set] >= info.color_count[set]) - { info.color[set] = info.color_count[set] - 1; state.bitmap_dirty = true; } - if (info.color[set] < 0) - { info.color[set] = 0; state.bitmap_dirty = true; } -} - - -//------------------------------------------------- -// gfxset_update_bitmap - redraw the current -// graphics view bitmap -//------------------------------------------------- - -static void gfxset_update_bitmap(running_machine &machine, ui_gfx_state &state, int xcells, int ycells, gfx_element &gfx) -{ - int dev = state.gfxset.devindex; - int set = state.gfxset.set; - ui_gfx_info &info = state.gfxdev[dev]; - int cellxpix, cellypix; - int x, y; - - // compute the number of source pixels in a cell - cellxpix = 1 + ((info.rotate[set] & ORIENTATION_SWAP_XY) ? gfx.height() : gfx.width()); - cellypix = 1 + ((info.rotate[set] & ORIENTATION_SWAP_XY) ? gfx.width() : gfx.height()); - - // realloc the bitmap if it is too small - if (state.bitmap == nullptr || state.texture == nullptr || state.bitmap->bpp() != 32 || state.bitmap->width() != cellxpix * xcells || state.bitmap->height() != cellypix * ycells) - { - // free the old stuff - machine.render().texture_free(state.texture); - global_free(state.bitmap); - - // allocate new stuff - state.bitmap = global_alloc(bitmap_rgb32(cellxpix * xcells, cellypix * ycells)); - state.texture = machine.render().texture_alloc(); - state.texture->set_bitmap(*state.bitmap, state.bitmap->cliprect(), TEXFORMAT_ARGB32); - - // force a redraw - state.bitmap_dirty = true; - } - - // handle the redraw - if (state.bitmap_dirty) - { - // loop over rows - for (y = 0; y < ycells; y++) - { - rectangle cellbounds; - - // make a rect that covers this row - cellbounds.set(0, state.bitmap->width() - 1, y * cellypix, (y + 1) * cellypix - 1); - - // only display if there is data to show - if (info.offset[set] + y * xcells < gfx.elements()) - { - // draw the individual cells - for (x = 0; x < xcells; x++) - { - int index = info.offset[set] + y * xcells + x; - - // update the bounds for this cell - cellbounds.min_x = x * cellxpix; - cellbounds.max_x = (x + 1) * cellxpix - 1; - - // only render if there is data - if (index < gfx.elements()) - gfxset_draw_item(machine, gfx, index, *state.bitmap, cellbounds.min_x, cellbounds.min_y, info.color[set], info.rotate[set], info.palette[set]); - - // otherwise, fill with transparency - else - state.bitmap->fill(0, cellbounds); - } - } - - // otherwise, fill with transparency - else - state.bitmap->fill(0, cellbounds); - } - - // reset the texture to force an update - state.texture->set_bitmap(*state.bitmap, state.bitmap->cliprect(), TEXFORMAT_ARGB32); - state.bitmap_dirty = false; - } -} - - -//------------------------------------------------- -// gfxset_draw_item - draw a single item into -// the view -//------------------------------------------------- - -static void gfxset_draw_item(running_machine &machine, gfx_element &gfx, int index, bitmap_rgb32 &bitmap, int dstx, int dsty, int color, int rotate, device_palette_interface *dpalette) -{ - int width = (rotate & ORIENTATION_SWAP_XY) ? gfx.height() : gfx.width(); - int height = (rotate & ORIENTATION_SWAP_XY) ? gfx.width() : gfx.height(); - const rgb_t *palette = dpalette->palette()->entry_list_raw() + gfx.colorbase() + color * gfx.granularity(); - - int x, y; - - // loop over rows in the cell - for (y = 0; y < height; y++) - { - uint32_t *dest = &bitmap.pix32(dsty + y, dstx); - const uint8_t *src = gfx.get_data(index); - - // loop over columns in the cell - for (x = 0; x < width; x++) - { - int effx = x, effy = y; - const uint8_t *s; - - // compute effective x,y values after rotation - if (!(rotate & ORIENTATION_SWAP_XY)) - { - if (rotate & ORIENTATION_FLIP_X) - effx = gfx.width() - 1 - effx; - if (rotate & ORIENTATION_FLIP_Y) - effy = gfx.height() - 1 - effy; - } - else - { - if (rotate & ORIENTATION_FLIP_X) - effx = gfx.height() - 1 - effx; - if (rotate & ORIENTATION_FLIP_Y) - effy = gfx.width() - 1 - effy; - std::swap(effx, effy); - } - - // get a pointer to the start of this source row - s = src + effy * gfx.rowbytes(); - - // extract the pixel - *dest++ = 0xff000000 | palette[s[effx]]; - } - } -} - - - -/*************************************************************************** - TILEMAP VIEWER -***************************************************************************/ - -//------------------------------------------------- -// tilemap_handler - handler for the tilemap -// viewer -//------------------------------------------------- - -static void tilemap_handler(mame_ui_manager &mui, render_container &container, ui_gfx_state &state) -{ - render_font *ui_font = mui.get_font(); - float chwidth, chheight; - render_bounds mapboxbounds; - render_bounds boxbounds; - int targwidth = mui.machine().render().ui_target().width(); - int targheight = mui.machine().render().ui_target().height(); - float titlewidth; - float x0, y0; - int mapboxwidth, mapboxheight; + // get some UI metrics + render_font *const ui_font = mui.get_font(); + int const targwidth = m_machine.render().ui_target().width(); + int const targheight = m_machine.render().ui_target().height(); + float const aspect = m_machine.render().ui_aspect(&container); + float const chheight = mui.get_line_height(); + float const chwidth = ui_font->char_width(chheight, aspect, '0'); // get the size of the tilemap itself - tilemap_t *tilemap = mui.machine().tilemap().find(state.tilemap.which); - uint32_t mapwidth = tilemap->width(); - uint32_t mapheight = tilemap->height(); - if (state.tilemap.rotate & ORIENTATION_SWAP_XY) + tilemap_t &tilemap = *m_machine.tilemap().find(m_tilemap.index()); + uint32_t mapwidth = tilemap.width(); + uint32_t mapheight = tilemap.height(); + if (m_tilemap.rotate() & ORIENTATION_SWAP_XY) std::swap(mapwidth, mapheight); // add a half character padding for the box - chheight = mui.get_line_height(); - chwidth = ui_font->char_width(chheight, mui.machine().render().ui_aspect(), '0'); - boxbounds.x0 = 0.0f + 0.5f * chwidth; - boxbounds.x1 = 1.0f - 0.5f * chwidth; - boxbounds.y0 = 0.0f + 0.5f * chheight; - boxbounds.y1 = 1.0f - 0.5f * chheight; + render_bounds boxbounds{ + 0.0f + (0.5f * chwidth), + 0.0f + (0.5f * chheight), + 1.0f - (0.5f * chwidth), + 1.0f - (0.5f * chheight) }; // the tilemap box bounds starts a half character in from the box - mapboxbounds = boxbounds; + render_bounds mapboxbounds = boxbounds; mapboxbounds.x0 += 0.5f * chwidth; mapboxbounds.x1 -= 0.5f * chwidth; mapboxbounds.y0 += 0.5f * chheight; @@ -1066,28 +1374,30 @@ static void tilemap_handler(mame_ui_manager &mui, render_container &container, u mapboxbounds.y0 += 1.5f * chheight; // convert back to pixels - mapboxwidth = (mapboxbounds.x1 - mapboxbounds.x0) * (float)targwidth; - mapboxheight = (mapboxbounds.y1 - mapboxbounds.y0) * (float)targheight; + int mapboxwidth = mapboxbounds.width() * float(targwidth); + int mapboxheight = mapboxbounds.height() * float(targheight); - // determine the maximum integral scaling factor - int pixelscale = state.tilemap.zoom; - if (pixelscale == 0) + float pixelscale; + if (m_tilemap.auto_zoom()) { - int maxxscale, maxyscale; - for (maxxscale = 1; mapwidth * (maxxscale + 1) < mapboxwidth; maxxscale++) { } - for (maxyscale = 1; mapheight * (maxyscale + 1) < mapboxheight; maxyscale++) { } - pixelscale = std::min(maxxscale, maxyscale); + // determine the maximum integral scaling factor + pixelscale = std::min(std::floor(mapboxwidth / mapwidth), std::floor(mapboxheight / mapheight)); + pixelscale = std::max(pixelscale, 1.0f); + } + else + { + pixelscale = m_tilemap.zoom_scale(); } // recompute the final box size - mapboxwidth = std::min(mapboxwidth, int(mapwidth * pixelscale)); - mapboxheight = std::min(mapboxheight, int(mapheight * pixelscale)); + mapboxwidth = std::min<int>(mapboxwidth, std::lround(mapwidth * pixelscale)); + mapboxheight = std::min<int>(mapboxheight, std::lround(mapheight * pixelscale)); // recompute the bounds, centered within the existing bounds - mapboxbounds.x0 += 0.5f * ((mapboxbounds.x1 - mapboxbounds.x0) - (float)mapboxwidth / (float)targwidth); - mapboxbounds.x1 = mapboxbounds.x0 + (float)mapboxwidth / (float)targwidth; - mapboxbounds.y0 += 0.5f * ((mapboxbounds.y1 - mapboxbounds.y0) - (float)mapboxheight / (float)targheight); - mapboxbounds.y1 = mapboxbounds.y0 + (float)mapboxheight / (float)targheight; + mapboxbounds.x0 += 0.5f * ((mapboxbounds.x1 - mapboxbounds.x0) - float(mapboxwidth) / targwidth); + mapboxbounds.x1 = mapboxbounds.x0 + float(mapboxwidth) / targwidth; + mapboxbounds.y0 += 0.5f * ((mapboxbounds.y1 - mapboxbounds.y0) - float(mapboxheight) / targheight); + mapboxbounds.y1 = mapboxbounds.y0 + float(mapboxheight) / targheight; // now recompute the outer box against this new info boxbounds.x0 = mapboxbounds.x0 - 0.5f * chwidth; @@ -1097,43 +1407,49 @@ static void tilemap_handler(mame_ui_manager &mui, render_container &container, u // figure out the title std::ostringstream title_buf; - util::stream_format(title_buf, "TILEMAP %d/%d", state.tilemap.which + 1, mui.machine().tilemap().count()); + util::stream_format(title_buf, + (m_tilemap.flags() != TILEMAP_DRAW_ALL_CATEGORIES) ? _("gfxview", "Tilemap %1$d/%2$d category %3$u") : _("gfxview", "Tilemap %1$d/%2$d "), + m_tilemap.index() + 1, m_machine.tilemap().count(), + m_tilemap.flags()); // if the mouse pointer is over a tile, add some info about its coordinates and color - int32_t mouse_target_x, mouse_target_y; float mouse_x, mouse_y; - bool mouse_button; - render_target *mouse_target = mui.machine().ui_input().find_mouse(&mouse_target_x, &mouse_target_y, &mouse_button); - if (mouse_target != nullptr && mouse_target->map_point_container(mouse_target_x, mouse_target_y, container, mouse_x, mouse_y) - && mapboxbounds.x0 <= mouse_x && mapboxbounds.x1 > mouse_x - && mapboxbounds.y0 <= mouse_y && mapboxbounds.y1 > mouse_y) + if (map_mouse(container, mapboxbounds, mouse_x, mouse_y)) { int xpixel = (mouse_x - mapboxbounds.x0) * targwidth; int ypixel = (mouse_y - mapboxbounds.y0) * targheight; - if (state.tilemap.rotate & ORIENTATION_FLIP_X) + if (m_tilemap.rotate() & ORIENTATION_FLIP_X) xpixel = (mapboxwidth - 1) - xpixel; - if (state.tilemap.rotate & ORIENTATION_FLIP_Y) + if (m_tilemap.rotate() & ORIENTATION_FLIP_Y) ypixel = (mapboxheight - 1) - ypixel; - if (state.tilemap.rotate & ORIENTATION_SWAP_XY) + if (m_tilemap.rotate() & ORIENTATION_SWAP_XY) std::swap(xpixel, ypixel); - uint32_t col = ((xpixel / pixelscale + state.tilemap.xoffs) / tilemap->tilewidth()) % tilemap->cols(); - uint32_t row = ((ypixel / pixelscale + state.tilemap.yoffs) / tilemap->tileheight()) % tilemap->rows(); + uint32_t const col = ((std::lround(xpixel / pixelscale) + m_tilemap.xoffs()) / tilemap.tilewidth()) % tilemap.cols(); + uint32_t const row = ((std::lround(ypixel / pixelscale) + m_tilemap.yoffs()) / tilemap.tileheight()) % tilemap.rows(); uint8_t gfxnum; uint32_t code, color; - tilemap->get_info_debug(col, row, gfxnum, code, color); - util::stream_format(title_buf, " @ %d,%d = GFX%d #%X:%X", - col * tilemap->tilewidth(), row * tilemap->tileheight(), - int(gfxnum), code, color); + tilemap.get_info_debug(col, row, gfxnum, code, color); + util::stream_format(title_buf, + _("gfxview", " (%1$u %2$u) = GFX%3$u #%4$X:%5$X"), + col * tilemap.tilewidth(), row * tilemap.tileheight(), + gfxnum, code, color); + + // keep touch pointer displayed after release so they know what it's pointing at + mame_ui_manager::display_pointer pointers[1]{ { m_machine.render().ui_target(), m_pointer_type, m_pointer_x, m_pointer_y } }; + if (ui_event::pointer::TOUCH == m_pointer_type) + mui.set_pointers(std::begin(pointers), std::end(pointers)); } else - util::stream_format(title_buf, " %dx%d OFFS %d,%d", tilemap->width(), tilemap->height(), state.tilemap.xoffs, state.tilemap.yoffs); - - if (state.tilemap.flags != TILEMAP_DRAW_ALL_CATEGORIES) - util::stream_format(title_buf, " CAT %d", state.tilemap.flags); + { + util::stream_format(title_buf, + _("gfxview", u8" %1$d\u00d7%2$d origin (%3$d %4$d)"), + tilemap.width(), tilemap.height(), + m_tilemap.xoffs(), m_tilemap.yoffs()); + } // expand the outer box to fit the title - const std::string title = title_buf.str(); - titlewidth = ui_font->string_width(chheight, mui.machine().render().ui_aspect(), title.c_str()); + std::string const title = std::move(title_buf).str(); + float const titlewidth = ui_font->string_width(chheight, aspect, title); if (boxbounds.x1 - boxbounds.x0 < titlewidth + chwidth) { boxbounds.x0 = 0.5f - 0.5f * (titlewidth + chwidth); @@ -1144,192 +1460,158 @@ static void tilemap_handler(mame_ui_manager &mui, render_container &container, u mui.draw_outlined_box(container, boxbounds.x0, boxbounds.y0, boxbounds.x1, boxbounds.y1, mui.colors().gfxviewer_bg_color()); // draw the title - x0 = 0.5f - 0.5f * titlewidth; - y0 = boxbounds.y0 + 0.5f * chheight; - for (auto ch : title) - { - container.add_char(x0, y0, chheight, mui.machine().render().ui_aspect(), rgb_t::white(), *ui_font, ch); - x0 += ui_font->char_width(chheight, mui.machine().render().ui_aspect(), ch); - } + draw_text(mui, container, title, 0.5f - 0.5f * titlewidth, boxbounds.y0 + 0.5f * chheight); // update the bitmap - tilemap_update_bitmap(mui.machine(), state, mapboxwidth / pixelscale, mapboxheight / pixelscale); + update_tilemap_bitmap(std::lround(mapboxwidth / pixelscale), std::lround(mapboxheight / pixelscale)); // add the final quad - container.add_quad(mapboxbounds.x0, mapboxbounds.y0, - mapboxbounds.x1, mapboxbounds.y1, - rgb_t::white(), state.texture, - PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXORIENT(state.tilemap.rotate)); + container.add_quad( + mapboxbounds.x0, mapboxbounds.y0, + mapboxbounds.x1, mapboxbounds.y1, + rgb_t::white(), m_texture, + PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXORIENT(m_tilemap.rotate())); // handle keyboard input - tilemap_handle_keys(mui.machine(), state, mapboxwidth, mapboxheight); + if (m_tilemap.handle_keys(m_machine, pixelscale)) + m_bitmap_dirty = true; + return handle_general_keys(uistate); } -//------------------------------------------------- -// tilemap_handle_keys - handle keys for the -// tilemap view -//------------------------------------------------- - -static void tilemap_handle_keys(running_machine &machine, ui_gfx_state &state, int viswidth, int visheight) +void gfx_viewer::update_gfxset_bitmap(int xcells, int ycells, gfx_element &gfx) { - // handle tilemap selection (open bracket,close bracket) - if (machine.ui_input().pressed(IPT_UI_PREV_GROUP) && state.tilemap.which > 0) - { state.tilemap.which--; state.bitmap_dirty = true; } - if (machine.ui_input().pressed(IPT_UI_NEXT_GROUP) && state.tilemap.which < machine.tilemap().count() - 1) - { state.tilemap.which++; state.bitmap_dirty = true; } + auto const &info = m_gfxset.m_devices[m_gfxset.m_device]; + auto const &set = info.set(m_gfxset.m_set); - // cache some info in locals - tilemap_t *tilemap = machine.tilemap().find(state.tilemap.which); - uint32_t mapwidth = tilemap->width(); - uint32_t mapheight = tilemap->height(); - - // handle zoom (minus,plus) - if (machine.ui_input().pressed(IPT_UI_ZOOM_OUT) && state.tilemap.zoom > 0) - { - state.tilemap.zoom--; - state.bitmap_dirty = true; - if (state.tilemap.zoom != 0) - machine.popmessage("Zoom = %d", state.tilemap.zoom); - else - machine.popmessage("Zoom Auto"); - } - if (machine.ui_input().pressed(IPT_UI_ZOOM_IN) && state.tilemap.zoom < 8) - { - state.tilemap.zoom++; - state.bitmap_dirty = true; - machine.popmessage("Zoom = %d", state.tilemap.zoom); - } + // compute the number of source pixels in a cell + int const cellxpix = 1 + ((set.m_rotate & ORIENTATION_SWAP_XY) ? gfx.height() : gfx.width()); + int const cellypix = 1 + ((set.m_rotate & ORIENTATION_SWAP_XY) ? gfx.width() : gfx.height()); - // handle rotation (R) - if (machine.ui_input().pressed(IPT_UI_ROTATE)) - { - state.tilemap.rotate = orientation_add(ROT90, state.tilemap.rotate); - state.bitmap_dirty = true; - } + // reallocate the bitmap if it is too small + resize_bitmap(cellxpix * xcells, cellypix * ycells); - // return to (0,0) (HOME) - if( machine.ui_input().pressed(IPT_UI_HOME)) + // handle the redraw + if (m_bitmap_dirty) { - state.tilemap.xoffs = 0; - state.tilemap.yoffs = 0; - state.bitmap_dirty = true; - } + // pre-fill with transparency + m_bitmap.fill(0); - // handle flags (category) - if (machine.ui_input().pressed(IPT_UI_PAGE_UP) && state.tilemap.flags != TILEMAP_DRAW_ALL_CATEGORIES) - { - if (state.tilemap.flags > 0) - { - state.tilemap.flags--; - machine.popmessage("Category = %d", state.tilemap.flags); - } - else + // loop over rows + for (int y = 0, index = set.m_offset; y < ycells; y++) { - state.tilemap.flags = TILEMAP_DRAW_ALL_CATEGORIES; - machine.popmessage("Category All"); + // make a rectangle that covers this row + rectangle cellbounds(0, m_bitmap.width() - 1, y * cellypix, (y + 1) * cellypix - 1); + + // only display if there is data to show + if (index < gfx.elements()) + { + // draw the individual cells + for (int x = 0; x < xcells; x++, index++) + { + // update the bounds for this cell + cellbounds.min_x = x * cellxpix; + cellbounds.max_x = (x + 1) * cellxpix - 1; + + if (index < gfx.elements()) // only render if there is data + gfxset_draw_item(gfx, index, cellbounds.min_x, cellbounds.min_y, set); + else // otherwise, fill with transparency + m_bitmap.fill(0, cellbounds); + } + } } - state.bitmap_dirty = true; - } - if (machine.ui_input().pressed(IPT_UI_PAGE_DOWN) && (state.tilemap.flags < TILEMAP_DRAW_CATEGORY_MASK || (state.tilemap.flags == TILEMAP_DRAW_ALL_CATEGORIES))) - { - if (state.tilemap.flags == TILEMAP_DRAW_ALL_CATEGORIES) - state.tilemap.flags = 0; - else - state.tilemap.flags++; - state.bitmap_dirty = true; - machine.popmessage("Category = %d", state.tilemap.flags); - } - // handle navigation (up,down,left,right), taking orientation into account - int step = 8; // this may be applied more than once if multiple directions are pressed - if (machine.input().code_pressed(KEYCODE_LSHIFT)) step = 1; - if (machine.input().code_pressed(KEYCODE_LCONTROL)) step = 64; - if (machine.ui_input().pressed_repeat(IPT_UI_UP, 4)) - { - if (state.tilemap.rotate & ORIENTATION_SWAP_XY) - state.tilemap.xoffs -= (state.tilemap.rotate & ORIENTATION_FLIP_Y) ? -step : step; - else - state.tilemap.yoffs -= (state.tilemap.rotate & ORIENTATION_FLIP_Y) ? -step : step; - state.bitmap_dirty = true; - } - if (machine.ui_input().pressed_repeat(IPT_UI_DOWN, 4)) - { - if (state.tilemap.rotate & ORIENTATION_SWAP_XY) - state.tilemap.xoffs += (state.tilemap.rotate & ORIENTATION_FLIP_Y) ? -step : step; - else - state.tilemap.yoffs += (state.tilemap.rotate & ORIENTATION_FLIP_Y) ? -step : step; - state.bitmap_dirty = true; - } - if (machine.ui_input().pressed_repeat(IPT_UI_LEFT, 6)) - { - if (state.tilemap.rotate & ORIENTATION_SWAP_XY) - state.tilemap.yoffs -= (state.tilemap.rotate & ORIENTATION_FLIP_X) ? -step : step; - else - state.tilemap.xoffs -= (state.tilemap.rotate & ORIENTATION_FLIP_X) ? -step : step; - state.bitmap_dirty = true; - } - if (machine.ui_input().pressed_repeat(IPT_UI_RIGHT, 6)) - { - if (state.tilemap.rotate & ORIENTATION_SWAP_XY) - state.tilemap.yoffs += (state.tilemap.rotate & ORIENTATION_FLIP_X) ? -step : step; - else - state.tilemap.xoffs += (state.tilemap.rotate & ORIENTATION_FLIP_X) ? -step : step; - state.bitmap_dirty = true; + // reset the texture to force an update + m_texture->set_bitmap(m_bitmap, m_bitmap.cliprect(), TEXFORMAT_ARGB32); + m_bitmap_dirty = false; } - - // clamp within range - while (state.tilemap.xoffs < 0) - state.tilemap.xoffs += mapwidth; - while (state.tilemap.xoffs >= mapwidth) - state.tilemap.xoffs -= mapwidth; - while (state.tilemap.yoffs < 0) - state.tilemap.yoffs += mapheight; - while (state.tilemap.yoffs >= mapheight) - state.tilemap.yoffs -= mapheight; } -//------------------------------------------------- -// tilemap_update_bitmap - update the bitmap -// for the tilemap view -//------------------------------------------------- - -static void tilemap_update_bitmap(running_machine &machine, ui_gfx_state &state, int width, int height) +void gfx_viewer::update_tilemap_bitmap(int width, int height) { // swap the coordinates back if they were talking about a rotated surface - if (state.tilemap.rotate & ORIENTATION_SWAP_XY) + if (m_tilemap.rotate() & ORIENTATION_SWAP_XY) std::swap(width, height); - // realloc the bitmap if it is too small - if (state.bitmap == nullptr || state.texture == nullptr || state.bitmap->width() != width || state.bitmap->height() != height) - { - // free the old stuff - machine.render().texture_free(state.texture); - global_free(state.bitmap); + // reallocate the bitmap if it is too small + resize_bitmap(width, height); - // allocate new stuff - state.bitmap = global_alloc(bitmap_rgb32(width, height)); - state.texture = machine.render().texture_alloc(); - state.texture->set_bitmap(*state.bitmap, state.bitmap->cliprect(), TEXFORMAT_RGB32); + // handle the redraw + if (m_bitmap_dirty) + { + m_bitmap.fill(0); + tilemap_t &tilemap = *m_machine.tilemap().find(m_tilemap.index()); + screen_device *const first_screen = screen_device_enumerator(m_machine.root_device()).first(); + if (first_screen) + tilemap.draw_debug(*first_screen, m_bitmap, m_tilemap.xoffs(), m_tilemap.yoffs(), m_tilemap.flags()); - // force a redraw - state.bitmap_dirty = true; + // reset the texture to force an update + m_texture->set_bitmap(m_bitmap, m_bitmap.cliprect(), TEXFORMAT_RGB32); + m_bitmap_dirty = false; } +} - // handle the redraw - if (state.bitmap_dirty) + +void gfx_viewer::gfxset_draw_item(gfx_element &gfx, int index, int dstx, int dsty, gfxset::setinfo const &info) +{ + int const width = (info.m_rotate & ORIENTATION_SWAP_XY) ? gfx.height() : gfx.width(); + int const height = (info.m_rotate & ORIENTATION_SWAP_XY) ? gfx.width() : gfx.height(); + rgb_t const *const palette = info.m_palette->palette()->entry_list_raw() + gfx.colorbase() + info.m_color * gfx.granularity(); + uint8_t const *const src = gfx.get_data(index); + + // loop over rows in the cell + for (int y = 0; y < height; y++) { - state.bitmap->fill(0); - tilemap_t *tilemap = machine.tilemap().find(state.tilemap.which); - screen_device *first_screen = screen_device_iterator(machine.root_device()).first(); - if (first_screen) + uint32_t *dest = &m_bitmap.pix(dsty + y, dstx); + + // loop over columns in the cell + for (int x = 0; x < width; x++) { - tilemap->draw_debug(*first_screen, *state.bitmap, state.tilemap.xoffs, state.tilemap.yoffs, state.tilemap.flags); - } + // compute effective x,y values after rotation + int effx = x, effy = y; + if (!(info.m_rotate & ORIENTATION_SWAP_XY)) + { + if (info.m_rotate & ORIENTATION_FLIP_X) + effx = gfx.width() - 1 - effx; + if (info.m_rotate & ORIENTATION_FLIP_Y) + effy = gfx.height() - 1 - effy; + } + else + { + if (info.m_rotate & ORIENTATION_FLIP_X) + effx = gfx.height() - 1 - effx; + if (info.m_rotate & ORIENTATION_FLIP_Y) + effy = gfx.width() - 1 - effy; + std::swap(effx, effy); + } - // reset the texture to force an update - state.texture->set_bitmap(*state.bitmap, state.bitmap->cliprect(), TEXFORMAT_RGB32); - state.bitmap_dirty = false; + // get a pointer to the start of this source row + uint8_t const *const s = src + (effy * gfx.rowbytes()); + + // extract the pixel + *dest++ = 0xff000000 | palette[s[effx]]; + } } } + +} // anonymous namespace + + + +/*************************************************************************** + MAIN ENTRY POINT +***************************************************************************/ + +//------------------------------------------------- +// ui_gfx_ui_handler - primary UI handler +// +// NOTE: this must not be called before machine +// initialization is complete, as some drivers +// create or modify gfx sets in VIDEO_START +//------------------------------------------------- + +uint32_t ui_gfx_ui_handler(render_container &container, mame_ui_manager &mui, bool uistate) +{ + return mui.get_session_data<gfx_viewer, gfx_viewer>(mui.machine()).handle(mui, container, uistate); +} diff --git a/src/frontend/mame/ui/viewgfx.h b/src/frontend/mame/ui/viewgfx.h index a4d2205d7a8..cb368efc91c 100644 --- a/src/frontend/mame/ui/viewgfx.h +++ b/src/frontend/mame/ui/viewgfx.h @@ -13,18 +13,13 @@ #pragma once +#include "ui/ui.h" /*************************************************************************** FUNCTION PROTOTYPES ***************************************************************************/ -// initialization -void ui_gfx_init(running_machine &machine); - -// returns 'true' if the internal graphics viewer has relevance -bool ui_gfx_is_relevant(running_machine &machine); - // master handler uint32_t ui_gfx_ui_handler(render_container &container, mame_ui_manager &mui, bool uistate); diff --git a/src/frontend/mame/ui/widgets.cpp b/src/frontend/mame/ui/widgets.cpp index d8907303b36..372a4df7331 100644 --- a/src/frontend/mame/ui/widgets.cpp +++ b/src/frontend/mame/ui/widgets.cpp @@ -24,7 +24,7 @@ namespace ui { //------------------------------------------------- widgets_manager::widgets_manager(running_machine &machine) - : m_hilight_bitmap(std::make_unique<bitmap_argb32>(256, 1)) + : m_hilight_bitmap(std::make_unique<bitmap_argb32>(512, 1)) , m_hilight_texture(nullptr, machine.render()) , m_hilight_main_bitmap(std::make_unique<bitmap_argb32>(1, 128)) , m_hilight_main_texture(nullptr, machine.render()) @@ -33,10 +33,10 @@ widgets_manager::widgets_manager(running_machine &machine) render_manager &render(machine.render()); // create a texture for hilighting items - for (unsigned x = 0; x < 256; ++x) + for (unsigned x = 0; x < 512; ++x) { - unsigned const alpha((x < 25) ? (0xff * x / 25) : (x >(256 - 25)) ? (0xff * (255 - x) / 25) : 0xff); - m_hilight_bitmap->pix32(0, x) = rgb_t(alpha, 0xff, 0xff, 0xff); + unsigned const alpha((x < 50) ? ((x + 1) * 5) : (x > (511 - 50)) ? ((512 - x) * 5) : 0xff); + m_hilight_bitmap->pix(0, x) = rgb_t(alpha, 0xff, 0xff, 0xff); } m_hilight_texture.reset(render.texture_alloc()); m_hilight_texture->set_bitmap(*m_hilight_bitmap, m_hilight_bitmap->cliprect(), TEXFORMAT_ARGB32); @@ -49,7 +49,7 @@ widgets_manager::widgets_manager(running_machine &machine) unsigned const r = r1 + (y * (r2 - r1) / 128); unsigned const g = g1 + (y * (g2 - g1) / 128); unsigned const b = b1 + (y * (b2 - b1) / 128); - m_hilight_main_bitmap->pix32(y, 0) = rgb_t(r, g, b); + m_hilight_main_bitmap->pix(y, 0) = rgb_t(r, g, b); } m_hilight_main_texture.reset(render.texture_alloc()); m_hilight_main_texture->set_bitmap(*m_hilight_main_bitmap, m_hilight_main_bitmap->cliprect(), TEXFORMAT_ARGB32); @@ -67,18 +67,17 @@ widgets_manager::widgets_manager(running_machine &machine) void widgets_manager::render_triangle(bitmap_argb32 &dest, bitmap_argb32 &source, const rectangle &sbounds, void *param) { - int halfwidth = dest.width() / 2; - int height = dest.height(); - int x, y; + int const halfwidth = dest.width() / 2; + int const height = dest.height(); // start with all-transparent dest.fill(rgb_t(0x00, 0x00, 0x00, 0x00)); // render from the tip to the bottom - for (y = 0; y < height; y++) + for (int y = 0; y < height; y++) { int linewidth = (y * (halfwidth - 1) + (height / 2)) * 255 * 2 / height; - uint32_t *target = &dest.pix32(y, halfwidth); + uint32_t *const target = &dest.pix(y, halfwidth); // don't antialias if height < 12 if (dest.height() < 12) @@ -89,25 +88,23 @@ void widgets_manager::render_triangle(bitmap_argb32 &dest, bitmap_argb32 &source } // loop while we still have data to generate - for (x = 0; linewidth > 0; x++) + for (int x = 0; linewidth > 0; x++) { int dalpha; - - // first column we only consume one pixel if (x == 0) { + // first column we only consume one pixel dalpha = std::min(0xff, linewidth); target[x] = rgb_t(dalpha, 0xff, 0xff, 0xff); } - - // remaining columns consume two pixels, one on each side else { + // remaining columns consume two pixels, one on each side dalpha = std::min(0x1fe, linewidth); target[x] = target[-x] = rgb_t(dalpha / 2, 0xff, 0xff, 0xff); } - // account for the weight we consumed */ + // account for the weight we consumed linewidth -= dalpha; } } |