summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--scripts/src/mame/frontend.lua1
-rw-r--r--src/emu/fileio.cpp2
-rw-r--r--src/frontend/mame/ui/icorender.cpp781
-rw-r--r--src/frontend/mame/ui/icorender.h227
-rw-r--r--src/frontend/mame/ui/menu.cpp4
-rw-r--r--src/frontend/mame/ui/menu.h22
-rw-r--r--src/frontend/mame/ui/selgame.cpp183
-rw-r--r--src/frontend/mame/ui/selgame.h13
-rw-r--r--src/frontend/mame/ui/selmenu.cpp239
-rw-r--r--src/frontend/mame/ui/selmenu.h36
-rw-r--r--src/frontend/mame/ui/selsoft.cpp256
-rw-r--r--src/frontend/mame/ui/selsoft.h38
-rw-r--r--src/frontend/mame/ui/utils.cpp19
-rw-r--r--src/frontend/mame/ui/utils.h1
-rw-r--r--src/frontend/mame/ui/widgets.cpp13
-rw-r--r--src/frontend/mame/ui/widgets.h12
-rw-r--r--src/lib/util/corefile.cpp22
-rw-r--r--src/lib/util/corefile.h21
18 files changed, 1347 insertions, 543 deletions
diff --git a/scripts/src/mame/frontend.lua b/scripts/src/mame/frontend.lua
index 60e955f8773..c8f21daa4a6 100644
--- a/scripts/src/mame/frontend.lua
+++ b/scripts/src/mame/frontend.lua
@@ -137,6 +137,7 @@ files {
MAME_DIR .. "src/frontend/mame/ui/defimg.ipp",
MAME_DIR .. "src/frontend/mame/ui/dirmenu.cpp",
MAME_DIR .. "src/frontend/mame/ui/dirmenu.h",
+ MAME_DIR .. "src/frontend/mame/ui/icorender.cpp",
MAME_DIR .. "src/frontend/mame/ui/icorender.h",
MAME_DIR .. "src/frontend/mame/ui/inifile.cpp",
MAME_DIR .. "src/frontend/mame/ui/inifile.h",
diff --git a/src/emu/fileio.cpp b/src/emu/fileio.cpp
index 3ec686266c8..bbd1d931f30 100644
--- a/src/emu/fileio.cpp
+++ b/src/emu/fileio.cpp
@@ -96,7 +96,7 @@ bool path_iterator::next(std::string &buffer, const char *name)
if (name)
{
// compute the full pathname
- if (!buffer.empty() && (*buffer.rbegin() != *PATH_SEPARATOR))
+ if (!buffer.empty() && !util::is_directory_separator(buffer.back()))
buffer.append(PATH_SEPARATOR);
buffer.append(name);
}
diff --git a/src/frontend/mame/ui/icorender.cpp b/src/frontend/mame/ui/icorender.cpp
new file mode 100644
index 00000000000..6f50d63f076
--- /dev/null
+++ b/src/frontend/mame/ui/icorender.cpp
@@ -0,0 +1,781 @@
+// license:BSD-3-Clause
+// copyright-holders:Maurizio Petrarota, Victor Laskin
+/***************************************************************************
+
+ ui/icorender.h
+
+ Windows icon file parser.
+
+ Previously based on code by Victor Laskin (victor.laskin@gmail.com)
+ http://vitiy.info/Code/ico.cpp
+
+ TODO:
+ * Add variant that loads all images from the file
+ * Allow size hint for choosing best candidate
+ * Allow selecting amongst candidates based on colour depth
+
+***************************************************************************/
+
+#include "emu.h"
+#include "icorender.h"
+
+#include "util/png.h"
+
+#include <algorithm>
+#include <cassert>
+#include <cstdint>
+#include <cstring>
+
+// need to set LOG_OUTPUT_STREAM because there's no logerror outside devices
+#define LOG_OUTPUT_STREAM std::cerr
+
+#define LOG_GENERAL (1U << 0)
+#define LOG_DIB (1U << 1)
+
+//#define VERBOSE (LOG_GENERAL | LOG_DIB)
+
+#include "logmacro.h"
+
+
+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
+{
+ uint16_t reserved; // must be 0
+ uint16_t type; // 1 for icon or 2 for cursor
+ uint16_t count; // number of images in the file
+};
+
+// ICO file directory entry
+struct icon_dir_entry_t
+{
+ constexpr unsigned get_width() const { return width ? width : 256U; }
+ constexpr unsigned get_height() const { return height ? height : 256U; }
+
+ void byteswap()
+ {
+ planes = little_endianize_int16(planes);
+ bpp = little_endianize_int16(bpp);
+ size = little_endianize_int32(size);
+ offset = little_endianize_int32(offset);
+ }
+
+ uint8_t width; // 0 means 256
+ uint8_t height; // 0 means 256
+ uint8_t colors; // for indexed colour, or 0 for direct colour
+ uint8_t reserved; // documentation says this should be 0 but .NET writes 255
+ uint16_t planes; // or hotspot X for cursor
+ uint16_t bpp; // 0 to infer from image data, or hotspot Y for cursor
+ uint32_t size; // image data size in bytes
+ 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)
+{
+ // 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)
+ {
+ case PNGERR_NONE:
+ // found valid PNG image
+ assert(bitmap.valid());
+ if ((dir.get_width() == bitmap.width()) && ((dir.get_height() == bitmap.height())))
+ {
+ LOG("Loaded %d*%d pixel PNG image from ICO file\n", bitmap.width(), bitmap.height());
+ }
+ else
+ {
+ LOG(
+ "Loaded %d*%d pixel PNG image from ICO file (directory indicated %u*%u)\n",
+ bitmap.width(),
+ bitmap.height(),
+ dir.get_width(),
+ dir.get_height());
+ }
+ return true;
+
+ case PNGERR_BAD_SIGNATURE:
+ // doesn't look like PNG data - just fall back to DIB without the file header
+ return false;
+
+ default:
+ // 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),
+ dir.offset,
+ dir.size);
+ return false;
+ }
+}
+
+
+bool load_ico_dib(util::core_file &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)
+ {
+ 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
+ {
+ 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;
+ }
+ break;
+
+ default:
+ // the next version will be longer
+ if (124U >= header.core.size)
+ {
+ 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;
+ }
+ break;
+
+ 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)
+ {
+ LOG(
+ "ICO file image data at %u (%u bytes) smaller than calculated DIB data size (%u bytes)\n",
+ dir.offset,
+ dir.size,
+ required_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)
+{
+ // try loading PNG image data (contains PNG file magic if used), and then fall back
+ if (load_ico_png(fp, dir, bitmap))
+ {
+ LOG("Successfully loaded PNG image from ICO file entry %u\n", index);
+ return true;
+ }
+ else if (load_ico_dib(fp, dir, bitmap))
+ {
+ LOG("Successfully loaded DIB image from ICO file entry %u\n", index);
+ return true;
+ }
+
+ // no luck
+ return false;
+}
+
+
+bool load_ico_image(util::core_file &fp, unsigned count, unsigned index, bitmap_argb32 &bitmap)
+{
+ // read the directory entry
+ 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))
+ {
+ LOG("Failed to read ICO file directory entry %u\n", index);
+ return false;
+ }
+ dir.byteswap();
+ if ((sizeof(icon_dir_t) + (sizeof(icon_dir_entry_t) * count)) > dir.offset)
+ {
+ LOG(
+ "ICO file image %u data starting at %u overlaps %u bytes of file header and directory\n",
+ index,
+ dir.offset,
+ sizeof(icon_dir_t) + (sizeof(icon_dir_entry_t) * count));
+ return false;
+ }
+ else
+ {
+ return load_ico_image(fp, index, dir, bitmap);
+ }
+}
+
+} // anonymous namespace
+
+
+int images_in_ico(util::core_file &fp)
+{
+ // read and check the icon file header
+ icon_dir_t header;
+ fp.seek(0, SEEK_SET);
+ if (fp.read(&header, sizeof(header)) != sizeof(header))
+ {
+ LOG("Failed to read ICO file header\n");
+ return -1;
+ }
+ header.reserved = little_endianize_int16(header.reserved);
+ header.type = little_endianize_int16(header.type);
+ header.count = little_endianize_int16(header.count);
+ if (0U != header.reserved)
+ {
+ LOG("Invalid ICO file header reserved field %u (expected 0)\n", header.reserved);
+ return -1;
+ }
+ if ((1U != header.type) && (2U != header.type))
+ {
+ LOG("Invalid ICO file header type field %u (expected 1 or 2)\n", header.type);
+ return -1;
+ }
+ return int(unsigned(little_endianize_int16(header.count)));
+}
+
+
+void render_load_ico(util::core_file &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");
+ static_assert(sizeof(icon_dir_entry_t) == 16U, "compiler has applied padding to icon_dir_entry_t");
+
+ // read and check the icon file header, then try to load the specified image
+ int const count(images_in_ico(fp));
+ if (0 > count)
+ {
+ // images_in_ico already logged an error
+ }
+ else if (index >= count)
+ {
+ osd_printf_verbose("Requested image %u from ICO file containing %d images\n", index, count);
+ }
+ else if (load_ico_image(fp, count, index, bitmap))
+ {
+ return;
+ }
+ bitmap.reset();
+}
+
+
+void render_load_ico_first(util::core_file &fp, bitmap_argb32 &bitmap)
+{
+ int const count(images_in_ico(fp));
+ for (int i = 0; count > i; ++i)
+ {
+ if (load_ico_image(fp, count, i, bitmap))
+ return;
+ }
+ bitmap.reset();
+}
+
+
+void render_load_ico_highest_detail(util::core_file &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)
+ {
+ // 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))
+ {
+ LOG("Failed to read ICO file directory entries\n");
+ }
+ else
+ {
+ // 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();
+}
+
+} // namespace ui
diff --git a/src/frontend/mame/ui/icorender.h b/src/frontend/mame/ui/icorender.h
index 17bc5a1d8ea..bb3f1a083be 100644
--- a/src/frontend/mame/ui/icorender.h
+++ b/src/frontend/mame/ui/icorender.h
@@ -1,13 +1,13 @@
// license:BSD-3-Clause
-// copyright-holders:Maurizio Petrarota, Victor Laskin
+// copyright-holders:Vas Crabb
/***************************************************************************
ui/icorender.h
- ICOns file loader.
+ Windows icon file parser.
- Original code by Victor Laskin (victor.laskin@gmail.com)
- http://vitiy.info/Code/ico.cpp
+ File handles passed to these functions must support read and seek
+ operations.
***************************************************************************/
#ifndef MAME_FRONTEND_MAME_UI_ICORENDER_H
@@ -15,219 +15,20 @@
#pragma once
-// These next two structs represent how the icon information is stored
-// in an ICO file.
-typedef struct
-{
- uint8_t bWidth; // Width of the image
- uint8_t bHeight; // Height of the image (times 2)
- uint8_t bColorCount; // Number of colors in image (0 if >=8bpp)
- uint8_t bReserved; // Reserved
- uint16_t wPlanes; // Color Planes
- uint16_t wBitCount; // Bits per pixel
- uint32_t dwBytesInRes; // how many bytes in this resource?
- uint32_t dwImageOffset; // where in the file is this image
-} ICONDIRENTRY, *LPICONDIRENTRY;
+namespace ui {
-typedef struct
-{
- uint16_t idReserved; // Reserved
- uint16_t idType; // resource type (1 for icons)
- uint16_t idCount; // how many images?
- //ICONDIRENTRY idEntries[1]; // the entries for each image
-} ICONDIR, *LPICONDIR;
+// get number of images in icon file (-1 on error)
+int images_in_ico(util::core_file &fp);
-// size - 40 bytes
-typedef struct {
- uint32_t biSize;
- uint32_t biWidth;
- uint32_t biHeight; // Icon Height (added height of XOR-Bitmap and AND-Bitmap)
- uint16_t biPlanes;
- uint16_t biBitCount;
- uint32_t biCompression;
- int32_t biSizeImage;
- uint32_t biXPelsPerMeter;
- uint32_t biYPelsPerMeter;
- uint32_t biClrUsed;
- uint32_t biClrImportant;
-} s_BITMAPINFOHEADER, *s_PBITMAPINFOHEADER;
+// load specified icon from file (zero-based)
+void render_load_ico(util::core_file &fp, unsigned index, bitmap_argb32 &bitmap);
-// 46 bytes
-typedef struct{
- s_BITMAPINFOHEADER icHeader; // DIB header
- uint32_t icColors[1]; // Color table (short 4 bytes) //RGBQUAD
- uint8_t icXOR[1]; // DIB bits for XOR mask
- uint8_t icAND[1]; // DIB bits for AND mask
-} ICONIMAGE, *LPICONIMAGE;
+// load first supported icon from file
+void render_load_ico_first(util::core_file &fp, bitmap_argb32 &bitmap);
-//-------------------------------------------------
-// load an ICO file into a bitmap
-//-------------------------------------------------
+// load highest detail supported icon from file
+void render_load_ico_highest_detail(util::core_file &fp, bitmap_argb32 &bitmap);
-inline void render_load_ico(bitmap_argb32 &bitmap, emu_file &file, const char *dirname, const char *filename)
-{
- int32_t width = 0;
- int32_t height = 0;
-
- // deallocate previous bitmap
- bitmap.reset();
-
- // define file's full name
- std::string fname;
-
- if (!dirname)
- fname = filename;
- else
- fname.assign(dirname).append(PATH_SEPARATOR).append(filename);
-
- osd_file::error filerr = file.open(fname.c_str());
-
- if (filerr != osd_file::error::NONE)
- return;
-
- // allocates a buffer for the image
- uint64_t size = file.size();
- uint8_t *buffer = global_alloc_array(uint8_t, size + 1);
-
- // read data from the file and set them in the buffer
- file.read(buffer, size);
-
- LPICONDIR icoDir = (LPICONDIR)buffer;
- int iconsCount = icoDir->idCount;
-
- if (icoDir->idReserved != 0 || icoDir->idType != 1 || iconsCount == 0 || iconsCount > 20)
- {
- file.close();
- global_free_array(buffer);
- return;
- }
-
- uint8_t* cursor = buffer;
- cursor += 6;
- ICONDIRENTRY* dirEntry = (ICONDIRENTRY*)(cursor);
- int maxSize = 0;
- int offset = 0;
- int maxBitCount = 0;
- for (int i = 0; i < iconsCount; i++, ++dirEntry)
- {
- int w = dirEntry->bWidth;
- int h = dirEntry->bHeight;
- int bitCount = dirEntry->wBitCount;
- if (w * h > maxSize || bitCount > maxBitCount) // we choose icon with max resolution
- {
- width = w;
- height = h;
- offset = dirEntry->dwImageOffset;
- maxSize = w * h;
- }
- }
-
- if (offset == 0) return;
-
- cursor = buffer;
- cursor += offset;
- ICONIMAGE* icon = (ICONIMAGE*)(cursor);
- int realBitsCount = (int)icon->icHeader.biBitCount;
- bool hasAndMask = (realBitsCount < 32) && (height != icon->icHeader.biHeight);
-
- cursor += 40;
- bitmap.allocate(width, height);
-
- // rgba + vertical swap
- if (realBitsCount >= 32)
- {
- for (int x = 0; x < width; ++x)
- for (int y = 0; y < height; ++y)
- {
- int shift2 = 4 * (x + (height - y - 1) * width);
- bitmap.pix32(y, x) = rgb_t(cursor[shift2 + 3], cursor[shift2 + 2], cursor[shift2 + 1], cursor[shift2]);
- }
- }
- else if (realBitsCount == 24)
- for (int x = 0; x < width; ++x)
- for (int y = 0; y < height; ++y)
- {
- int shift2 = 3 * (x + (height - y - 1) * width);
- bitmap.pix32(y, x) = rgb_t(255, cursor[shift2 + 2], cursor[shift2 + 1], cursor[shift2]);
- }
- else if (realBitsCount == 8) // 256 colors
- {
- // 256 color table
- uint8_t *colors = cursor;
- cursor += 256 * 4;
- for (int x = 0; x < width; ++x)
- for (int y = 0; y < height; ++y)
- {
- int shift2 = (x + (height - y - 1) * width);
- int index = 4 * cursor[shift2];
- bitmap.pix32(y, x) = rgb_t(255, colors[index + 2], colors[index + 1], colors[index]);
- }
- }
- else if (realBitsCount == 4) // 16 colors
- {
- // 16 color table
- uint8_t *colors = cursor;
- cursor += 16 * 4;
- for (int x = 0; x < width; ++x)
- for (int y = 0; y < height; ++y)
- {
- int shift2 = (x + (height - y - 1) * width);
- uint8_t index = cursor[shift2 / 2];
- if (shift2 % 2 == 0)
- index = (index >> 4) & 0xF;
- else
- index = index & 0xF;
- index *= 4;
- bitmap.pix32(y, x) = rgb_t(255, colors[index + 2], colors[index + 1], colors[index]);
- }
- }
- else if (realBitsCount == 1) // 2 colors
- {
- // 2 color table
- uint8_t *colors = cursor;
- cursor += 2 * 4;
- int boundary = width; // !!! 32 bit boundary (http://www.daubnet.com/en/file-format-ico)
- while (boundary % 32 != 0) boundary++;
-
- for (int x = 0; x < width; ++x)
- for (int y = 0; y < height; ++y)
- {
- int shift2 = (x + (height - y - 1) * boundary);
- uint8_t index = cursor[shift2 / 8];
-
- // select 1 bit only
- uint8_t bit = 7 - (x % 8);
- index = (index >> bit) & 0x01;
- index *= 4;
- bitmap.pix32(y, x) = rgb_t(255, colors[index + 2], colors[index + 1], colors[index]);
- }
- }
-
- // Read AND mask after base color data - 1 BIT MASK
- if (hasAndMask)
- {
- int boundary = width * realBitsCount; // !!! 32 bit boundary (http://www.daubnet.com/en/file-format-ico)
- while (boundary % 32 != 0) boundary++;
- cursor += boundary * height / 8;
-
- boundary = width;
- while (boundary % 32 != 0) boundary++;
-
- for (int y = 0; y < height; ++y)
- for (int x = 0; x < width; ++x)
- {
- uint8_t bit = 7 - (x % 8);
- int shift2 = (x + (height - y - 1) * boundary) / 8;
- int mask = (0x01 & ((uint8_t)cursor[shift2] >> bit));
- rgb_t colors = bitmap.pix32(y, x);
- uint8_t alpha = colors.a();
- alpha *= 1 - mask;
- colors.set_a(alpha);
- bitmap.pix32(y, x) = colors;
- }
- }
- file.close();
- global_free_array(buffer);
-}
+} // namespace ui
#endif // MAME_FRONTEND_MAME_UI_ICORENDER_H
diff --git a/src/frontend/mame/ui/menu.cpp b/src/frontend/mame/ui/menu.cpp
index 9a8fb8255e3..4bc6a13d81e 100644
--- a/src/frontend/mame/ui/menu.cpp
+++ b/src/frontend/mame/ui/menu.cpp
@@ -86,14 +86,14 @@ menu::global_state::global_state(running_machine &machine, ui_options const &opt
, m_machine(machine)
, m_cleanup_callbacks()
, m_bgrnd_bitmap()
+ , m_bgrnd_texture(nullptr, machine.render())
, m_stack()
, m_free()
{
render_manager &render(machine.render());
- auto const texture_free([&render](render_texture *texture) { render.texture_free(texture); });
// create a texture for main menu background
- m_bgrnd_texture = texture_ptr(render.texture_alloc(render_texture::hq_scale), texture_free);
+ m_bgrnd_texture.reset(render.texture_alloc(render_texture::hq_scale));
if (options.use_background_image() && (&machine.system() == &GAME_NAME(___empty)))
{
m_bgrnd_bitmap = std::make_unique<bitmap_argb32>(0, 0);
diff --git a/src/frontend/mame/ui/menu.h b/src/frontend/mame/ui/menu.h
index f0c9600d509..ffcb0ed71e8 100644
--- a/src/frontend/mame/ui/menu.h
+++ b/src/frontend/mame/ui/menu.h
@@ -27,6 +27,7 @@
namespace ui {
+
/***************************************************************************
TYPE DEFINITIONS
***************************************************************************/
@@ -35,18 +36,17 @@ class menu
{
public:
// flags for menu items
- enum
+ enum : unsigned
{
- FLAG_LEFT_ARROW = (1 << 0),
- FLAG_RIGHT_ARROW = (1 << 1),
- FLAG_INVERT = (1 << 2),
- FLAG_MULTILINE = (1 << 3),
- FLAG_REDTEXT = (1 << 4),
- FLAG_DISABLE = (1 << 5),
- FLAG_UI_DATS = (1 << 6),
- FLAG_UI_FAVORITE = (1 << 7),
- FLAG_UI_HEADING = (1 << 8),
- FLAG_COLOR_BOX = (1 << 9)
+ 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
};
virtual ~menu();
diff --git a/src/frontend/mame/ui/selgame.cpp b/src/frontend/mame/ui/selgame.cpp
index c36392e8b10..aaf73703d62 100644
--- a/src/frontend/mame/ui/selgame.cpp
+++ b/src/frontend/mame/ui/selgame.cpp
@@ -1,5 +1,5 @@
// license:BSD-3-Clause
-// copyright-holders:Maurizio Petrarota
+// copyright-holders:Maurizio Petrarota, Vas Crabb
/*********************************************************************
ui/selgame.cpp
@@ -12,6 +12,7 @@
#include "ui/selgame.h"
#include "ui/auditmenu.h"
+#include "ui/icorender.h"
#include "ui/inifile.h"
#include "ui/miscmenu.h"
#include "ui/optsmenu.h"
@@ -39,8 +40,6 @@
#include <mutex>
#include <thread>
-#include "ui/icorender.h" // this is inline code
-
extern const char UI_VERSION_TAG[];
@@ -220,10 +219,11 @@ menu_select_game::menu_select_game(mame_ui_manager &mui, render_container &conta
: menu_select_launch(mui, container, false)
, m_persistent_data(persistent_data::instance())
, m_icons(MAX_ICONS_RENDER)
- , m_has_icons(false)
+ , m_icon_paths()
, m_displaylist()
, m_searchlist()
, m_searched_fields(persistent_data::AVAIL_NONE)
+ , m_populated_favorites(false)
{
std::string error_string, last_filter, sub_filter;
ui_options &moptions = mui.options();
@@ -231,18 +231,8 @@ menu_select_game::menu_select_game(mame_ui_manager &mui, render_container &conta
// load drivers cache
m_persistent_data.cache_data();
- // check if there are available icons
- file_enumerator path(moptions.icons_directory());
- const osd::directory::entry *dir;
- while ((dir = path.next()) != nullptr)
- {
- std::string src(dir->name);
- if (src.find(".ico") != std::string::npos || src.find("icons") != std::string::npos)
- {
- m_has_icons = true;
- break;
- }
- }
+ // check if there are available system icons
+ check_for_icons(nullptr);
// build drivers list
if (!load_available_machines())
@@ -334,13 +324,13 @@ void menu_select_game::handle()
return;
}
- // if i have to reselect a software, force software list submenu
+ // if I have to select software, force software list submenu
if (reselect_last::get())
{
const game_driver *driver;
const ui_software_info *software;
get_selection(software, driver);
- menu::stack_push<menu_select_software>(ui(), container(), driver);
+ menu::stack_push<menu_select_software>(ui(), container(), *driver);
return;
}
@@ -397,7 +387,7 @@ void menu_select_game::handle()
case IPT_UI_SELECT:
if (get_focus() == focused_menu::MAIN)
{
- if (isfavorite())
+ if (m_populated_favorites)
inkey_select_favorite(menu_event);
else
inkey_select(menu_event);
@@ -406,7 +396,7 @@ void menu_select_game::handle()
case IPT_CUSTOM:
// handle IPT_CUSTOM (mouse right click)
- if (!isfavorite())
+ if (!m_populated_favorites)
{
menu::stack_push<menu_machine_configure>(
ui(), container(),
@@ -459,7 +449,7 @@ void menu_select_game::handle()
if (uintptr_t(menu_event->itemref) > skip_main_items)
{
favorite_manager &mfav(mame_machine_manager::instance()->favorite());
- if (!isfavorite())
+ if (!m_populated_favorites)
{
game_driver const *const driver(reinterpret_cast<game_driver const *>(menu_event->itemref));
if (!mfav.is_favorite_system(*driver))
@@ -506,16 +496,19 @@ void menu_select_game::handle()
void menu_select_game::populate(float &customtop, float &custombottom)
{
- set_redraw_icon();
+ 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;
constexpr uint32_t flags_ui = FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW;
if (!isfavorite())
{
- // if search is not empty, find approximate matches
+ m_populated_favorites = false;
if (!m_search.empty())
{
+ // if search is not empty, find approximate matches
populate_search();
}
else
@@ -555,11 +548,11 @@ void menu_select_game::populate(float &customtop, float &custombottom)
else
{
// populate favorites list
+ 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
{
- auto flags = flags_ui | FLAG_UI_FAVORITE;
if (info.startempty == 1)
{
if (old_item_selected == -1 && info.shortname == reselect_last::driver())
@@ -573,14 +566,14 @@ void menu_select_game::populate(float &customtop, float &custombottom)
cloneof = false;
}
- item_append(info.longname, "", (cloneof) ? (flags | FLAG_INVERT) : flags, (void *)&info);
+ item_append(info.longname, "", cloneof ? (flags_ui | FLAG_INVERT) : flags_ui, (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 : (FLAG_INVERT | flags), (void *)&info);
+ info.parentname.empty() ? flags_ui : (FLAG_INVERT | flags_ui), (void *)&info);
}
curitem++;
});
@@ -792,7 +785,7 @@ void menu_select_game::inkey_select(const event *menu_event)
{
if (!swlistdev.get_info().empty())
{
- menu::stack_push<menu_select_software>(ui(), container(), driver);
+ menu::stack_push<menu_select_software>(ui(), container(), *driver);
return;
}
}
@@ -862,7 +855,7 @@ void menu_select_game::inkey_select_favorite(const event *menu_event)
{
if (!swlistdev.get_info().empty())
{
- menu::stack_push<menu_select_software>(ui(), container(), ui_swinfo->driver);
+ menu::stack_push<menu_select_software>(ui(), container(), *ui_swinfo->driver);
return;
}
}
@@ -935,7 +928,7 @@ void menu_select_game::change_info_pane(int delta)
game_driver const *drv;
ui_software_info const *soft;
get_selection(soft, drv);
- if (!isfavorite())
+ if (!m_populated_favorites)
{
if (uintptr_t(drv) > skip_main_items)
cap_delta(ui_globals::curdats_view, ui_globals::curdats_total);
@@ -1009,7 +1002,7 @@ void menu_select_game::populate_search()
m_searchlist.end(),
[] (auto const &lhs, auto const &rhs) { return lhs.first < rhs.first; });
uint32_t flags_ui = FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW;
- for (int curitem = 0; (std::min)(m_searchlist.size(), std::size_t(200)) > curitem; ++curitem)
+ for (int curitem = 0; (std::min)(m_searchlist.size(), MAX_VISIBLE_SEARCH) > curitem; ++curitem)
{
game_driver const &drv(*m_searchlist[curitem].second.get().driver);
bool cloneof = strcmp(drv.parent, "0") != 0;
@@ -1176,39 +1169,31 @@ void menu_select_game::general_info(const game_driver *driver, std::string &buff
//-------------------------------------------------
-// draw icons
+// get (possibly cached) icon texture
//-------------------------------------------------
-float menu_select_game::draw_icon(int linenum, void *selectedref, float x0, float y0)
+render_texture *menu_select_game::get_icon_texture(int linenum, void *selectedref)
{
- if (!m_has_icons)
- return 0.0f;
-
- float ud_arrow_width = ui().get_line_height() * container().manager().ui_aspect(&container());
- const game_driver *driver = nullptr;
-
- if (item[0].flags & FLAG_UI_FAVORITE)
- {
- ui_software_info *soft = (ui_software_info *)selectedref;
- if (soft->startempty == 1)
- driver = soft->driver;
- }
- else
- driver = (const game_driver *)selectedref;
-
- auto x1 = x0 + ud_arrow_width;
- auto y1 = y0 + ui().get_line_height();
+ game_driver const *const driver(m_populated_favorites
+ ? reinterpret_cast<ui_software_info const *>(selectedref)->driver
+ : reinterpret_cast<game_driver const *>(selectedref));
+ assert(driver);
icon_cache::iterator icon(m_icons.find(driver));
- if ((m_icons.end() == icon) || redraw_icon())
+ if ((m_icons.end() == icon) || !icon->second.texture)
{
+ if (m_icon_paths.empty())
+ m_icon_paths = make_icon_paths(nullptr);
+
+ // allocate an entry or allocate a texture on forced redraw
if (m_icons.end() == icon)
{
- texture_ptr texture(machine().render().texture_alloc(), [&render = machine().render()] (render_texture *texture) { render.texture_free(texture); });
- icon = m_icons.emplace(
- std::piecewise_construct,
- std::forward_as_tuple(driver),
- std::forward_as_tuple(std::piecewise_construct, std::forward_as_tuple(std::move(texture)), std::tuple<>())).first;
+ icon = m_icons.emplace(driver, texture_ptr(machine().render().texture_alloc(), machine().render())).first;
+ }
+ else
+ {
+ assert(!icon->second.texture);
+ icon->second.texture.reset(machine().render().texture_alloc());
}
// set clone status
@@ -1220,83 +1205,23 @@ float menu_select_game::draw_icon(int linenum, void *selectedref, float x0, floa
cloneof = false;
}
- // get search path
- path_iterator path(ui().options().icons_directory());
- std::string curpath;
- std::string searchstr(ui().options().icons_directory());
-
- // iterate over path and add path for zipped formats
- while (path.next(curpath))
- searchstr.append(";").append(curpath.c_str()).append(PATH_SEPARATOR).append("icons");
-
bitmap_argb32 tmp;
- emu_file snapfile(searchstr.c_str(), OPEN_FLAG_READ);
- std::string fullname = std::string(driver->name).append(".ico");
- render_load_ico(tmp, snapfile, nullptr, fullname.c_str());
-
- if (!tmp.valid() && cloneof)
+ emu_file snapfile(std::string(m_icon_paths), OPEN_FLAG_READ);
+ if (snapfile.open(std::string(driver->name).append(".ico")) == osd_file::error::NONE)
{
- fullname.assign(driver->parent).append(".ico");
- render_load_ico(tmp, snapfile, nullptr, fullname.c_str());
- }
-
- bitmap_argb32 &bitmap(icon->second.second);
- if (tmp.valid())
- {
- float panel_width = x1 - x0;
- float panel_height = y1 - y0;
- auto screen_width = machine().render().ui_target().width();
- auto 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;
-
- // Calculate resize ratios for resizing
- auto ratioW = (float)panel_width_pixel / tmp.width();
- auto ratioH = (float)panel_height_pixel / tmp.height();
- auto dest_xPixel = tmp.width();
- auto dest_yPixel = tmp.height();
-
- if (ratioW < 1 || ratioH < 1)
- {
- // smaller ratio will ensure that the image fits in the view
- float ratio = std::min(ratioW, ratioH);
- dest_xPixel = tmp.width() * ratio;
- dest_yPixel = tmp.height() * ratio;
- }
-
- bitmap_argb32 dest_bitmap;
-
- // resample if necessary
- if (dest_xPixel != tmp.width() || dest_yPixel != tmp.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, color, true);
- }
- else
- dest_bitmap = std::move(tmp);
-
- bitmap.allocate(panel_width_pixel, panel_height_pixel);
- for (int x = 0; x < dest_xPixel; x++)
- for (int y = 0; y < dest_yPixel; y++)
- bitmap.pix32(y, x) = dest_bitmap.pix32(y, x);
-
- icon->second.first->set_bitmap(bitmap, bitmap.cliprect(), TEXFORMAT_ARGB32);
+ render_load_ico_highest_detail(snapfile, tmp);
+ snapfile.close();
}
- else
+ if (!tmp.valid() && cloneof && (snapfile.open(std::string(driver->parent).append(".ico")) == osd_file::error::NONE))
{
- bitmap.reset();
+ render_load_ico_highest_detail(snapfile, tmp);
+ snapfile.close();
}
- }
- if (icon->second.second.valid())
- container().add_quad(x0, y0, x1, y1, rgb_t::white(), icon->second.first.get(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA));
+ scale_icon(std::move(tmp), icon->second);
+ }
- return ud_arrow_width * 1.5f;
+ return icon->second.bitmap.valid() ? icon->second.texture.get() : nullptr;
}
@@ -1305,12 +1230,12 @@ void menu_select_game::inkey_export()
std::vector<game_driver const *> list;
if (!m_search.empty())
{
- for (int curitem = 0; (std::min)(m_searchlist.size(), std::size_t(200)); ++curitem)
+ for (int curitem = 0; (std::min)(m_searchlist.size(), MAX_VISIBLE_SEARCH); ++curitem)
list.push_back(m_searchlist[curitem].second.get().driver);
}
else
{
- if (isfavorite())
+ if (m_populated_favorites)
{
// iterate over favorites
mame_machine_manager::instance()->favorite().apply(
@@ -1421,7 +1346,7 @@ float menu_select_game::draw_left_panel(float x1, float y1, float x2, float y2)
void menu_select_game::get_selection(ui_software_info const *&software, game_driver const *&driver) const
{
- if (item[0].flags & FLAG_UI_FAVORITE) // TODO: work out why this doesn't use isfavorite()
+ if (m_populated_favorites)
{
software = reinterpret_cast<ui_software_info const *>(get_selection_ptr());
driver = software ? software->driver : nullptr;
@@ -1442,7 +1367,7 @@ void menu_select_game::make_topbox_text(std::string &line0, std::string &line1,
(driver_list::total() - 1),
m_persistent_data.bios_count());
- if (isfavorite())
+ if (m_populated_favorites)
{
line1.clear();
}
diff --git a/src/frontend/mame/ui/selgame.h b/src/frontend/mame/ui/selgame.h
index 7d5d906a1c1..da65fee98ce 100644
--- a/src/frontend/mame/ui/selgame.h
+++ b/src/frontend/mame/ui/selgame.h
@@ -1,5 +1,5 @@
// license:BSD-3-Clause
-// copyright-holders:Maurizio Petrarota
+// copyright-holders:Maurizio Petrarota, Vas Crabb
/***************************************************************************
ui/selgame.h
@@ -39,25 +39,27 @@ private:
CONF_PLUGINS,
};
- using icon_cache = util::lru_cache_map<game_driver const *, std::pair<texture_ptr, bitmap_argb32> >;
+ using icon_cache = texture_lru<game_driver const *>;
class persistent_data;
persistent_data &m_persistent_data;
icon_cache m_icons;
- bool m_has_icons;
+ std::string m_icon_paths;
std::vector<std::reference_wrapper<ui_system_info const> > m_displaylist;
std::vector<std::pair<double, std::reference_wrapper<ui_system_info const> > > m_searchlist;
unsigned m_searched_fields;
+ bool m_populated_favorites;
static bool s_first_start;
virtual void populate(float &customtop, float &custombottom) override;
virtual void handle() override;
- // draw left panel
+ // drawing
virtual float draw_left_panel(float x1, float y1, float x2, float y2) 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;
@@ -89,9 +91,6 @@ private:
// General info
virtual void general_info(const game_driver *driver, std::string &buffer) override;
- // drawing
- virtual float draw_icon(int linenum, void *selectedref, float x1, float y1) override;
-
// handlers
void inkey_select(const event *menu_event);
void inkey_select_favorite(const event *menu_event);
diff --git a/src/frontend/mame/ui/selmenu.cpp b/src/frontend/mame/ui/selmenu.cpp
index 2702ae9260d..82c5cfee9ff 100644
--- a/src/frontend/mame/ui/selmenu.cpp
+++ b/src/frontend/mame/ui/selmenu.cpp
@@ -1,5 +1,5 @@
// license:BSD-3-Clause
-// copyright-holders:Nicola Salmoria, Aaron Giles, Nathan Woods
+// copyright-holders:Nicola Salmoria, Aaron Giles, Nathan Woods, Vas Crabb
/***************************************************************************
ui/selmenu.cpp
@@ -180,7 +180,7 @@ void menu_select_launch::reselect_last::set_software(game_driver const &driver,
s_driver = driver.name;
if (swinfo.startempty)
{
- // magic strings are bad...
+ // FIXME: magic strings are bad...
s_software = "[Start empty]";
s_swlist.clear();
}
@@ -373,27 +373,26 @@ void menu_select_launch::bios_selection::custom_render(void *selectedref, float
menu_select_launch::cache::cache(running_machine &machine)
: m_snapx_bitmap(std::make_unique<bitmap_argb32>(0, 0))
- , m_snapx_texture()
+ , m_snapx_texture(nullptr, machine.render())
, m_snapx_driver(nullptr)
, m_snapx_software(nullptr)
, m_no_avail_bitmap(256, 256)
, m_star_bitmap(32, 32)
- , m_star_texture()
+ , m_star_texture(nullptr, machine.render())
, m_toolbar_bitmap()
, m_sw_toolbar_bitmap()
, m_toolbar_texture()
, m_sw_toolbar_texture()
{
render_manager &render(machine.render());
- auto const texture_free([&render](render_texture *texture) { render.texture_free(texture); });
// create a texture for snapshot
- m_snapx_texture = texture_ptr(render.texture_alloc(render_texture::hq_scale), texture_free);
+ 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_star_bitmap.pix32(0), favorite_star_bmp, 32 * 32 * sizeof(uint32_t));
- m_star_texture = texture_ptr(render.texture_alloc(), texture_free);
+ m_star_texture.reset(render.texture_alloc());
m_star_texture->set_bitmap(m_star_bitmap, m_star_bitmap.cliprect(), TEXFORMAT_ARGB32);
m_toolbar_bitmap.reserve(UI_TOOLBAR_BUTTONS);
@@ -405,8 +404,8 @@ menu_select_launch::cache::cache(running_machine &machine)
{
m_toolbar_bitmap.emplace_back(32, 32);
m_sw_toolbar_bitmap.emplace_back(32, 32);
- m_toolbar_texture.emplace_back(texture_ptr(render.texture_alloc(), texture_free));
- m_sw_toolbar_texture.emplace_back(texture_ptr(render.texture_alloc(), texture_free));
+ m_toolbar_texture.emplace_back(render.texture_alloc(), render);
+ m_sw_toolbar_texture.emplace_back(render.texture_alloc(), render);
std::memcpy(&m_toolbar_bitmap.back().pix32(0), toolbar_bitmap_bmp[i], 32 * 32 * sizeof(uint32_t));
if (m_toolbar_bitmap.back().valid())
@@ -458,7 +457,7 @@ menu_select_launch::menu_select_launch(mame_ui_manager &mui, render_container &c
, m_pressed(false)
, m_repeat(0)
, m_right_visible_lines(0)
- , m_redraw_icon(false)
+ , m_has_icons(false)
, m_switch_image(false)
, m_default_image(true)
, m_image_view(FIRST_VIEW)
@@ -990,6 +989,150 @@ float menu_select_launch::draw_left_panel(
}
+//-------------------------------------------------
+// icon helpers
+//-------------------------------------------------
+
+void menu_select_launch::check_for_icons(char const *listname)
+{
+ // only ever set the flag, never clear it
+ if (m_has_icons)
+ return;
+
+ // iterate over configured icon paths
+ path_iterator paths(ui().options().icons_directory());
+ std::string current;
+ while (paths.next(current))
+ {
+ // 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());
+
+ // open and walk the directory
+ osd::directory::ptr const dir(osd::directory::open(current));
+ if (dir)
+ {
+ // this could be improved in many ways - it's just a rough go/no-go
+ osd::directory::entry const *entry;
+ while ((entry = dir->read()) != nullptr)
+ {
+ current = entry->name;
+ std::string::size_type const found(current.rfind(".ico"));
+ if ((std::string::npos != found) && ((current.length() - 4) == found))
+ {
+ osd_printf_verbose("Entry %s is a candidate icon file\n", entry->name);
+ m_has_icons = true;
+ return;
+ }
+ else if (("icons" == current) || (current.find("icons.") == 0U))
+ {
+ osd_printf_verbose("Entry %s is a candidate icon collection\n", entry->name);
+ m_has_icons = true;
+ return;
+ }
+ }
+ }
+ }
+
+ // nothing promising
+ osd_printf_verbose(
+ "No candidate icons found for %s%s\n",
+ listname ? "software list " : "",
+ listname ? listname : "machines");
+}
+
+std::string menu_select_launch::make_icon_paths(char const *listname) const
+{
+ // iterate over configured icon paths
+ path_iterator paths(ui().options().icons_directory());
+ std::string current, result;
+ while (paths.next(current))
+ {
+ // 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);
+ }
+
+ // append the configured path
+ if (!result.empty())
+ result.append(1, ';'); // FIXME: should be a macro
+ result.append(current);
+
+ // append with "icons" appended so it'll search icons.zip or icons.7z in the directory
+ if (!current.empty())
+ {
+ result.append(1, ';'); // FIXME: should be a macro
+ result.append(current);
+ if (!util::is_directory_separator(result.back()))
+ result.append(PATH_SEPARATOR);
+ }
+ result.append("icons");
+ }
+
+ // log the result for debugging
+ osd_printf_verbose(
+ "Icon path for %s%s set to %s\n",
+ listname ? "software list " : "",
+ listname ? listname : "machines",
+ result.c_str());
+ return result;
+}
+
+bool menu_select_launch::scale_icon(bitmap_argb32 &&src, texture_and_bitmap &dst) const
+{
+ 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
+ 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 pix_height(src.height() * ratio);
+ float const pix_width(src.width() * ratio);
+ tmp.allocate(int32_t(pix_width), int32_t(pix_height));
+ render_resample_argb_bitmap_hq(tmp, src, render_color{ 1.0F, 1.0F, 1.0F, 1.0F }, true);
+ }
+ else
+ {
+ tmp = std::move(src);
+ }
+
+ // copy into the destination
+ dst.bitmap.allocate(max_width, max_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.texture->set_bitmap(dst.bitmap, dst.bitmap.cliprect(), TEXFORMAT_ARGB32);
+ return true;
+ }
+ else
+ {
+ // couldn't load icon
+ dst.bitmap.reset();
+ return false;
+ }
+}
+
+
template <typename T> bool menu_select_launch::select_bios(T const &driver, bool inlist)
{
s_bios biosname;
@@ -1095,10 +1238,16 @@ void menu_select_launch::set_pressed()
// draw icons
//-------------------------------------------------
-float menu_select_launch::draw_icon(int linenum, void *selectedref, float x0, float y0)
+void menu_select_launch::draw_icon(int linenum, void *selectedref, float x0, float y0)
{
- // derived class must implement this
- return 0.0f;
+ 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();
+ container().add_quad(x0, y0, x1, y1, rgb_t::white(), icon, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA));
+ };
}
@@ -1612,8 +1761,9 @@ void menu_select_launch::draw(uint32_t flags)
{
bool noinput = (flags & PROCESS_NOINPUT);
float line_height = ui().get_line_height();
- float ud_arrow_width = line_height * machine().render().ui_aspect();
- float gutter_width = 0.52f * ud_arrow_width;
+ 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;
@@ -1698,18 +1848,21 @@ void menu_select_launch::draw(uint32_t flags)
if (mouse_in_rect(line_x0, line_y0, line_x1, line_y1) && is_selectable(pitem))
hover = itemnum;
- // if we're selected, draw with a different background
if (is_selected(itemnum) && m_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);
fgcolor3 = rgb_t(0xcc, 0xcc, 0x00);
- 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(),
+ line_x0 + 0.01f, line_y0, line_x1 - 0.01f, line_y1,
+ 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 (itemnum == hover)
{
+ // else if the mouse is over this item, draw with a different background
fgcolor = fgcolor3 = UI_MOUSEOVER_COLOR;
bgcolor = UI_MOUSEOVER_BG_COLOR;
highlight(line_x0, line_y0, line_x1, line_y1, bgcolor);
@@ -1749,29 +1902,52 @@ void menu_select_launch::draw(uint32_t flags)
else if (pitem.subtext.empty())
{
// draw the item centered
- int item_invert = pitem.flags & FLAG_INVERT;
- auto icon = draw_icon(linenum, item[itemnum].ref, effective_left, line_y);
- ui().draw_text_full(container(), itemtext, effective_left + icon, line_y, effective_width - icon, ui::text_layout::LEFT, ui::text_layout::TRUNCATE,
- mame_ui_manager::NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, nullptr, nullptr);
+ 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
{
- auto item_invert = pitem.flags & FLAG_INVERT;
+ 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, 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);
+ 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
- ui().draw_text_full(container(), itemtext, effective_left, line_y, effective_width - subitem_width,
- ui::text_layout::LEFT, ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, &item_width, nullptr);
+ 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 + item_width, line_y, effective_width - item_width,
- ui::text_layout::RIGHT, ui::text_layout::NEVER, mame_ui_manager::NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, nullptr, nullptr);
+ 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);
}
}
@@ -1832,9 +2008,6 @@ void menu_select_launch::draw(uint32_t flags)
// 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);
- // reset redraw icon stage
- m_redraw_icon = false;
-
// noinput
if (noinput)
{
@@ -2299,7 +2472,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());
- u32 const bios_flags(bios.get_value());
+ uint32_t const bios_flags(bios.get_value());
if (default_name && !std::strcmp(bios.get_name(), default_name))
{
diff --git a/src/frontend/mame/ui/selmenu.h b/src/frontend/mame/ui/selmenu.h
index 2dec44ac96c..5e85cb0ce96 100644
--- a/src/frontend/mame/ui/selmenu.h
+++ b/src/frontend/mame/ui/selmenu.h
@@ -1,5 +1,5 @@
// license:BSD-3-Clause
-// copyright-holders:Nicola Salmoria, Aaron Giles, Nathan Woods
+// copyright-holders:Nicola Salmoria, Aaron Giles, Nathan Woods, Vas Crabb
/***************************************************************************
ui/selmenu.h
@@ -7,7 +7,6 @@
MAME system/software selection menu.
***************************************************************************/
-
#ifndef MAME_FRONTEND_UI_SELMENU_H
#define MAME_FRONTEND_UI_SELMENU_H
@@ -35,6 +34,7 @@ public:
protected:
static constexpr std::size_t MAX_ICONS_RENDER = 128;
+ static constexpr std::size_t MAX_VISIBLE_SEARCH = 200;
// tab navigation
enum class focused_menu
@@ -45,6 +45,20 @@ protected:
RIGHTBOTTOM
};
+ struct texture_and_bitmap
+ {
+ template <typename T>
+ texture_and_bitmap(T &&tex) : texture(std::forward<T>(tex)) { }
+ texture_and_bitmap(texture_and_bitmap &&that) = default;
+ texture_and_bitmap &operator=(texture_and_bitmap &&that) = default;
+
+ texture_ptr texture;
+ bitmap_argb32 bitmap;
+ };
+
+ template <typename Key, typename Compare = std::less<Key> >
+ using texture_lru = util::lru_cache_map<Key, texture_and_bitmap, Compare>;
+
class system_flags
{
public:
@@ -117,11 +131,6 @@ protected:
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);
- // forcing refresh
- bool redraw_icon() const { return m_redraw_icon; }
- void set_redraw_icon() { m_redraw_icon = true; }
- void set_switch_image() { m_switch_image = true; }
-
bool draw_error_text();
template <typename Filter>
@@ -130,6 +139,14 @@ protected:
std::map<typename Filter::type, typename Filter::ptr> const &filters,
float x1, float y1, float x2, float y2);
+ // icon helpers
+ void check_for_icons(char const *listname);
+ std::string make_icon_paths(char const *listname) const;
+ bool scale_icon(bitmap_argb32 &&src, texture_and_bitmap &dst) const;
+
+ // forcing refresh
+ void set_switch_image() { m_switch_image = true; }
+
template <typename T> bool select_bios(T const &driver, bool inlist);
bool select_part(software_info const &info, ui_software_info const &ui_info);
@@ -235,7 +252,8 @@ private:
void draw_toolbar(float x1, float y1, float x2, float y2);
void draw_star(float x0, float y0);
- virtual float draw_icon(int linenum, void *selectedref, float x1, float y1);
+ 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);
@@ -294,7 +312,7 @@ private:
int m_right_visible_lines; // right box lines
- bool m_redraw_icon;
+ bool m_has_icons;
bool m_switch_image;
bool m_default_image;
uint8_t m_image_view;
diff --git a/src/frontend/mame/ui/selsoft.cpp b/src/frontend/mame/ui/selsoft.cpp
index 5891ab73f2b..84709e49d89 100644
--- a/src/frontend/mame/ui/selsoft.cpp
+++ b/src/frontend/mame/ui/selsoft.cpp
@@ -1,5 +1,5 @@
// license:BSD-3-Clause
-// copyright-holders:Maurizio Petrarota
+// copyright-holders:Maurizio Petrarota, Vas Crabb
/***************************************************************************
ui/selsoft.cpp
@@ -12,6 +12,7 @@
#include "ui/selsoft.h"
#include "ui/ui.h"
+#include "ui/icorender.h"
#include "ui/inifile.h"
#include "ui/selector.h"
@@ -24,69 +25,73 @@
#include "uiinput.h"
#include "luaengine.h"
+#include <algorithm>
+#include <iterator>
+#include <functional>
+
namespace ui {
+namespace {
+
//-------------------------------------------------
// compares two items in the software list and
// sort them by parent-clone
//-------------------------------------------------
-bool compare_software(ui_software_info a, ui_software_info b)
+bool compare_software(ui_software_info const &a, ui_software_info const &b)
{
- ui_software_info *x = &a;
- ui_software_info *y = &b;
-
- bool clonex = !x->parentname.empty();
- bool cloney = !y->parentname.empty();
-
- if (!clonex && !cloney)
- return (strmakelower(x->longname) < strmakelower(y->longname));
-
- std::string cx(x->parentlongname), cy(y->parentlongname);
-
- if (cx.empty())
- clonex = false;
-
- if (cy.empty())
- cloney = false;
+ bool const clonex = !a.parentname.empty() && !a.parentlongname.empty();
+ bool const cloney = !b.parentname.empty() && !b.parentlongname.empty();
if (!clonex && !cloney)
- return (strmakelower(x->longname) < strmakelower(y->longname));
- else if (clonex && cloney)
{
- if (!core_stricmp(x->parentname.c_str(), y->parentname.c_str()) && !core_stricmp(x->instance.c_str(), y->instance.c_str()))
- return (strmakelower(x->longname) < strmakelower(y->longname));
- else
- return (strmakelower(cx) < strmakelower(cy));
+ return 0 > core_stricmp(a.longname.c_str(), b.longname.c_str());
}
else if (!clonex && cloney)
{
- if (!core_stricmp(x->shortname.c_str(), y->parentname.c_str()) && !core_stricmp(x->instance.c_str(), y->instance.c_str()))
+ if ((a.shortname == b.parentname) && (a.instance == b.instance))
return true;
else
- return (strmakelower(x->longname) < strmakelower(cy));
+ return 0 > core_stricmp(a.longname.c_str(), b.parentlongname.c_str());
}
- else
+ else if (clonex && !cloney)
{
- if (!core_stricmp(x->parentname.c_str(), y->shortname.c_str()) && !core_stricmp(x->instance.c_str(), y->instance.c_str()))
+ if ((a.parentname == b.shortname) && (a.instance == b.instance))
return false;
else
- return (strmakelower(cx) < strmakelower(y->longname));
+ return 0 > core_stricmp(a.parentlongname.c_str(), b.longname.c_str());
+ }
+ else if ((a.parentname == b.parentname) && (a.instance == b.instance))
+ {
+ return 0 > core_stricmp(a.longname.c_str(), b.longname.c_str());
+ }
+ else
+ {
+ return 0 > core_stricmp(a.parentlongname.c_str(), b.parentlongname.c_str());
}
}
+} // anonymous namespace
+
+
//-------------------------------------------------
// ctor
//-------------------------------------------------
-menu_select_software::menu_select_software(mame_ui_manager &mui, render_container &container, const game_driver *driver)
+menu_select_software::menu_select_software(mame_ui_manager &mui, render_container &container, game_driver const &driver)
: 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()
{
reselect_last::reselect(false);
- m_driver = driver;
build_software_list();
load_sw_custom_filters();
m_filter_highlight = m_filter_type;
@@ -225,11 +230,14 @@ void menu_select_software::handle()
void menu_select_software::populate(float &customtop, float &custombottom)
{
+ for (auto &icon : m_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;
- machine_config config(*m_driver, machine().options());
+ machine_config config(m_driver, machine().options());
for (device_image_interface &image : image_interface_iterator(config.root_device()))
if (image.filename() == nullptr && image.must_be_loaded())
{
@@ -245,31 +253,28 @@ void menu_select_software::populate(float &customtop, float &custombottom)
item_append("[Start empty]", "", flags_ui, (void *)&m_swinfo[0]);
m_displaylist.clear();
- m_tmp.clear();
-
filter_map::const_iterator const it(m_filters.find(m_filter_type));
if (m_filters.end() == it)
- m_displaylist = m_sortedlist;
+ std::copy(std::next(m_swinfo.begin()), m_swinfo.end(), std::back_inserter(m_displaylist));
else
- it->second->apply(std::begin(m_sortedlist), std::end(m_sortedlist), std::back_inserter(m_displaylist));
+ it->second->apply(std::next(m_swinfo.begin()), m_swinfo.end(), std::back_inserter(m_displaylist));
// iterate over entries
for (size_t curitem = 0; curitem < m_displaylist.size(); ++curitem)
{
if (reselect_last::software() == "[Start empty]" && !reselect_last::driver().empty())
old_software = 0;
-
- else if (m_displaylist[curitem]->shortname == reselect_last::software() && m_displaylist[curitem]->listname == reselect_last::swlist())
+ 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;
- item_append(m_displaylist[curitem]->longname, m_displaylist[curitem]->devicetype,
- m_displaylist[curitem]->parentname.empty() ? flags_ui : (FLAG_INVERT | flags_ui), (void *)m_displaylist[curitem]);
+ 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());
}
}
-
else
{
- find_matches(m_search.c_str(), VISIBLE_GAMES_IN_SEARCH);
+ find_matches(m_search.c_str(), MAX_VISIBLE_SEARCH);
for (int curitem = 0; m_searchlist[curitem] != nullptr; ++curitem)
item_append(m_searchlist[curitem]->longname, m_searchlist[curitem]->devicetype,
@@ -299,21 +304,51 @@ void menu_select_software::populate(float &customtop, float &custombottom)
void menu_select_software::build_software_list()
{
// add start empty item
- m_swinfo.emplace_back(*m_driver);
+ m_swinfo.emplace_back(m_driver);
- machine_config config(*m_driver, machine().options());
+ machine_config config(m_driver, machine().options());
- // iterate thru all software lists
+ // 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)
{
- const char *instance_name = nullptr;
- const char *type_name = nullptr;
+ 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();
@@ -324,45 +359,35 @@ void menu_select_software::build_software_list()
break;
}
}
- if (!instance_name || !type_name)
- continue;
-
- ui_software_info tmpmatches(swinfo, part, *m_driver, swlist.list_name(), instance_name, type_name);
- m_filter_data.add_region(tmpmatches.longname);
- m_filter_data.add_publisher(tmpmatches.publisher);
- m_filter_data.add_year(tmpmatches.year);
- m_filter_data.add_device_type(tmpmatches.devicetype);
- m_swinfo.emplace_back(std::move(tmpmatches));
- }
- }
- }
- m_displaylist.resize(m_swinfo.size() + 1);
-
- // retrieve and set the long name of software for parents
- for (size_t y = 1; y < m_swinfo.size(); ++y)
- {
- if (!m_swinfo[y].parentname.empty())
- {
- std::string lparent(m_swinfo[y].parentname);
- bool found = false;
-
- // first scan backward
- for (int x = y; x > 0; --x)
- if (lparent == m_swinfo[x].shortname && m_swinfo[y].listname == m_swinfo[x].listname)
+ if (instance_name && type_name)
{
- m_swinfo[y].parentlongname = m_swinfo[x].longname;
- found = true;
- break;
- }
+ // 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());
- // not found? then scan forward
- for (size_t x = y; !found && x < m_swinfo.size(); ++x)
- if (lparent == m_swinfo[x].shortname && m_swinfo[y].listname == m_swinfo[x].listname)
- {
- m_swinfo[y].parentlongname = m_swinfo[x].longname;
- break;
+ 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);
}
+ }
}
}
@@ -401,9 +426,6 @@ void menu_select_software::build_software_list()
// sort array
std::stable_sort(m_swinfo.begin() + 1, m_swinfo.end(), compare_software);
m_filter_data.finalise();
-
- for (size_t x = 1; x < m_swinfo.size(); ++x)
- m_sortedlist.push_back(&m_swinfo[x]);
}
@@ -467,7 +489,7 @@ 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)
+ 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)
@@ -491,10 +513,10 @@ void menu_select_software::find_matches(const char *str, int count)
{
// pick the best match between shortname and longname
// TODO: search alternate title as well
- double curpenalty(util::edit_distance(search, ustr_from_utf8(normalize_unicode(m_displaylist[index]->shortname, unicode_normalization_form::D, true))));
+ double curpenalty(util::edit_distance(search, ustr_from_utf8(normalize_unicode(m_displaylist[index].get().shortname, unicode_normalization_form::D, true))));
if (curpenalty)
{
- double const tmp(util::edit_distance(search, ustr_from_utf8(normalize_unicode(m_displaylist[index]->longname, unicode_normalization_form::D, true))));
+ double const tmp(util::edit_distance(search, ustr_from_utf8(normalize_unicode(m_displaylist[index].get().longname, unicode_normalization_form::D, true))));
curpenalty = (std::min)(curpenalty, tmp);
}
@@ -512,7 +534,7 @@ void menu_select_software::find_matches(const char *str, int count)
m_searchlist[matchnum + 1] = m_searchlist[matchnum];
}
- m_searchlist[matchnum] = m_displaylist[index];
+ m_searchlist[matchnum] = &m_displaylist[index].get();
penalty[matchnum] = curpenalty;
}
}
@@ -530,6 +552,56 @@ float menu_select_software::draw_left_panel(float x1, float y1, float x2, float
//-------------------------------------------------
+// get (possibly cached) icon texture
+//-------------------------------------------------
+
+render_texture *menu_select_software::get_icon_texture(int linenum, void *selectedref)
+{
+ ui_software_info const *const swinfo(reinterpret_cast<ui_software_info const *>(selectedref));
+ assert(swinfo);
+
+ if (swinfo->startempty)
+ return nullptr;
+
+ icon_cache::iterator icon(m_icons.find(swinfo));
+ if ((m_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)
+ {
+ icon = m_icons.emplace(swinfo, texture_ptr(machine().render().texture_alloc(), machine().render())).first;
+ }
+ else
+ {
+ assert(!icon->second.texture);
+ icon->second.texture.reset(machine().render().texture_alloc());
+ }
+
+ bitmap_argb32 tmp;
+ emu_file snapfile(std::string(paths->second), OPEN_FLAG_READ);
+ if (snapfile.open(std::string(swinfo->shortname).append(".ico")) == osd_file::error::NONE)
+ {
+ render_load_ico_highest_detail(snapfile, tmp);
+ snapfile.close();
+ }
+ if (!tmp.valid() && !swinfo->parentname.empty() && (snapfile.open(std::string(swinfo->parentname).append(".ico")) == osd_file::error::NONE))
+ {
+ render_load_ico_highest_detail(snapfile, tmp);
+ snapfile.close();
+ }
+
+ scale_icon(std::move(tmp), icon->second);
+ }
+
+ return icon->second.bitmap.valid() ? icon->second.texture.get() : nullptr;
+}
+
+
+//-------------------------------------------------
// get selected software and/or driver
//-------------------------------------------------
@@ -545,7 +617,7 @@ void menu_select_software::make_topbox_text(std::string &line0, std::string &lin
// 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());
+ line1 = string_format(_("Driver: \"%1$s\" software list "), m_driver.type.fullname());
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);
@@ -581,13 +653,13 @@ void menu_select_software::filter_selected()
it->second->show_ui(
ui(),
container(),
- [this, driver = m_driver] (software_filter &filter)
+ [this, &driver = m_driver] (software_filter &filter)
{
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("custom_", driver->name, "_filter.ini") == osd_file::error::NONE)
+ if (file.open("custom_", driver.name, "_filter.ini") == osd_file::error::NONE)
{
filter.save_ini(file, 0);
file.close();
diff --git a/src/frontend/mame/ui/selsoft.h b/src/frontend/mame/ui/selsoft.h
index e65a5ddc61f..1becf695841 100644
--- a/src/frontend/mame/ui/selsoft.h
+++ b/src/frontend/mame/ui/selsoft.h
@@ -1,5 +1,5 @@
// license:BSD-3-Clause
-// copyright-holders:Maurizio Petrarota
+// copyright-holders:Maurizio Petrarota, Vas Crabb
/***************************************************************************
ui/selsoft.h
@@ -22,25 +22,19 @@ namespace ui {
class menu_select_software : public menu_select_launch
{
public:
- menu_select_software(mame_ui_manager &mui, render_container &container, const game_driver *driver);
+ menu_select_software(mame_ui_manager &mui, render_container &container, game_driver const &driver);
virtual ~menu_select_software() override;
private:
- enum { VISIBLE_GAMES_IN_SEARCH = 200 };
-
- typedef std::map<software_filter::type, software_filter::ptr> filter_map;
-
- const game_driver *m_driver;
- bool m_has_empty_start;
- software_filter_data m_filter_data;
- filter_map m_filters;
- software_filter::type m_filter_type;
+ using filter_map = std::map<software_filter::type, software_filter::ptr>;
+ using icon_cache = texture_lru<ui_software_info const *>;
virtual void populate(float &customtop, float &custombottom) override;
virtual void handle() override;
- // draw left panel
+ // drawing
virtual float draw_left_panel(float x1, float y1, float x2, float y2) 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;
@@ -56,10 +50,6 @@ private:
// toolbar
virtual void inkey_export() override { throw false; }
- ui_software_info *m_searchlist[VISIBLE_GAMES_IN_SEARCH + 1];
- std::vector<ui_software_info *> m_displaylist, m_tmp, m_sortedlist;
- std::vector<ui_software_info> m_swinfo;
-
void build_software_list();
void find_matches(const char *str, int count);
void load_sw_custom_filters();
@@ -67,9 +57,21 @@ private:
// handlers
void inkey_select(const event *menu_event);
- virtual void general_info(const game_driver *driver, std::string &buffer) override {}
+ virtual void general_info(const game_driver *driver, std::string &buffer) override { }
+
+ 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;
+ ui_software_info const *m_searchlist[MAX_VISIBLE_SEARCH + 1];
+ std::vector<std::reference_wrapper<ui_software_info const> > m_displaylist;
};
} // namespace ui
-#endif /* MAME_FRONTEND_UI_SELSOFT_H */
+#endif // MAME_FRONTEND_UI_SELSOFT_H
diff --git a/src/frontend/mame/ui/utils.cpp b/src/frontend/mame/ui/utils.cpp
index baebce20bb3..52857fc6dbf 100644
--- a/src/frontend/mame/ui/utils.cpp
+++ b/src/frontend/mame/ui/utils.cpp
@@ -84,6 +84,7 @@ constexpr char const *SOFTWARE_FILTER_NAMES[software_filter::COUNT] = {
__("Unfiltered"),
__("Available"),
__("Unavailable"),
+ __("Favorites"),
__("Parents"),
__("Clones"),
__("Year"),
@@ -1256,6 +1257,21 @@ 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)
+ : m_manager(mame_machine_manager::instance()->favorite())
+ {
+ }
+
+ virtual bool apply(ui_software_info const &info) const override { return m_manager.is_favorite_software(info); }
+
+private:
+ favorite_manager const &m_manager;
+};
+
+
class parents_software_filter : public simple_filter_impl_base<software_filter, software_filter::PARENTS>
{
public:
@@ -1423,6 +1439,7 @@ public:
case UNSUPPORTED: return (SUPPORTED == m) || (PARTIAL_SUPPORTED == m);
case ALL:
+ case FAVORITE:
case YEAR:
case PUBLISHERS:
case REGION:
@@ -1740,6 +1757,8 @@ software_filter::ptr software_filter::create(type n, software_filter_data const
return std::make_unique<available_software_filter>(data, value, file, indent);
case UNAVAILABLE:
return std::make_unique<unavailable_software_filter>(data, value, file, indent);
+ case FAVORITE:
+ return std::make_unique<favorite_software_filter>(data, value, file, indent);
case PARENTS:
return std::make_unique<parents_software_filter>(data, value, file, indent);
case CLONES:
diff --git a/src/frontend/mame/ui/utils.h b/src/frontend/mame/ui/utils.h
index b3f5350002b..e6f2acd6bc8 100644
--- a/src/frontend/mame/ui/utils.h
+++ b/src/frontend/mame/ui/utils.h
@@ -200,6 +200,7 @@ public:
ALL = 0,
AVAILABLE,
UNAVAILABLE,
+ FAVORITE,
PARENTS,
CLONES,
YEAR,
diff --git a/src/frontend/mame/ui/widgets.cpp b/src/frontend/mame/ui/widgets.cpp
index cc2c276abfc..d8907303b36 100644
--- a/src/frontend/mame/ui/widgets.cpp
+++ b/src/frontend/mame/ui/widgets.cpp
@@ -14,6 +14,7 @@
namespace ui {
+
/***************************************************************************
WIDGETS
***************************************************************************/
@@ -24,12 +25,12 @@ namespace ui {
widgets_manager::widgets_manager(running_machine &machine)
: m_hilight_bitmap(std::make_unique<bitmap_argb32>(256, 1))
- , m_hilight_texture()
+ , m_hilight_texture(nullptr, machine.render())
, m_hilight_main_bitmap(std::make_unique<bitmap_argb32>(1, 128))
- , m_hilight_main_texture()
+ , m_hilight_main_texture(nullptr, machine.render())
+ , m_arrow_texture(nullptr, machine.render())
{
render_manager &render(machine.render());
- auto const texture_free([&render](render_texture *texture) { render.texture_free(texture); });
// create a texture for hilighting items
for (unsigned x = 0; x < 256; ++x)
@@ -37,7 +38,7 @@ widgets_manager::widgets_manager(running_machine &machine)
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);
}
- m_hilight_texture = texture_ptr(render.texture_alloc(), texture_free);
+ m_hilight_texture.reset(render.texture_alloc());
m_hilight_texture->set_bitmap(*m_hilight_bitmap, m_hilight_bitmap->cliprect(), TEXFORMAT_ARGB32);
// create a texture for hilighting items in main menu
@@ -50,11 +51,11 @@ widgets_manager::widgets_manager(running_machine &machine)
unsigned const b = b1 + (y * (b2 - b1) / 128);
m_hilight_main_bitmap->pix32(y, 0) = rgb_t(r, g, b);
}
- m_hilight_main_texture = texture_ptr(render.texture_alloc(), texture_free);
+ 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);
// create a texture for arrow icons
- m_arrow_texture = texture_ptr(render.texture_alloc(render_triangle), texture_free);
+ m_arrow_texture.reset(render.texture_alloc(render_triangle));
}
diff --git a/src/frontend/mame/ui/widgets.h b/src/frontend/mame/ui/widgets.h
index 45f1725d7ec..567b0a4e499 100644
--- a/src/frontend/mame/ui/widgets.h
+++ b/src/frontend/mame/ui/widgets.h
@@ -21,6 +21,7 @@
namespace ui {
+
/***************************************************************************
TYPE DEFINITIONS
***************************************************************************/
@@ -34,8 +35,17 @@ public:
render_texture *hilight_main_texture() { return m_hilight_main_texture.get(); }
render_texture *arrow_texture() { return m_arrow_texture.get(); }
+ class texture_destroyer
+ {
+ public:
+ texture_destroyer(render_manager &manager) : m_manager(manager) { }
+ void operator()(render_texture *texture) const { m_manager.get().texture_free(texture); }
+ private:
+ std::reference_wrapper<render_manager> m_manager;
+ };
+
using bitmap_ptr = std::unique_ptr<bitmap_argb32>;
- using texture_ptr = std::unique_ptr<render_texture, std::function<void(render_texture *)> >;
+ using texture_ptr = std::unique_ptr<render_texture, texture_destroyer>;
private:
static void render_triangle(bitmap_argb32 &dest, bitmap_argb32 &source, const rectangle &sbounds, void *param);
diff --git a/src/lib/util/corefile.cpp b/src/lib/util/corefile.cpp
index 264d80f4113..78a484c5511 100644
--- a/src/lib/util/corefile.cpp
+++ b/src/lib/util/corefile.cpp
@@ -23,9 +23,10 @@
#include <ctype.h>
-
namespace util {
+
namespace {
+
/***************************************************************************
VALIDATION
***************************************************************************/
@@ -336,23 +337,6 @@ private:
/***************************************************************************
- INLINE FUNCTIONS
-***************************************************************************/
-
-/*-------------------------------------------------
- is_directory_separator - is a given character
- a directory separator? The following logic
- works for most platforms
--------------------------------------------------*/
-
-inline int is_directory_separator(char c)
-{
- return (c == '\\' || c == '/' || c == ':');
-}
-
-
-
-/***************************************************************************
core_text_file
***************************************************************************/
@@ -1275,7 +1259,7 @@ core_file::core_file()
std::string core_filename_extract_base(const std::string &name, bool strip_extension)
{
// find the start of the basename
- auto const start = std::find_if(name.rbegin(), name.rend(), [](char c) { return util::is_directory_separator(c); });
+ auto const start = std::find_if(name.rbegin(), name.rend(), &util::is_directory_separator);
// find the end of the basename
auto const chop_position = strip_extension
diff --git a/src/lib/util/corefile.h b/src/lib/util/corefile.h
index 2c4a5da8217..d286cb866ac 100644
--- a/src/lib/util/corefile.h
+++ b/src/lib/util/corefile.h
@@ -7,11 +7,11 @@
Core file I/O interface functions and definitions.
***************************************************************************/
+#ifndef MAME_LIB_UTIL_COREFILE_H
+#define MAME_LIB_UTIL_COREFILE_H
#pragma once
-#ifndef MAME_LIB_UTIL_COREFILE_H
-#define MAME_LIB_UTIL_COREFILE_H
#include "corestr.h"
#include "coretmpl.h"
@@ -23,6 +23,7 @@
namespace util {
+
/***************************************************************************
ADDITIONAL OPEN FLAGS
***************************************************************************/
@@ -130,6 +131,22 @@ protected:
core_file();
};
+
+/***************************************************************************
+ INLINE FUNCTIONS
+***************************************************************************/
+
+// is a given character a directory separator?
+
+constexpr bool is_directory_separator(char c)
+{
+#if defined(WIN32)
+ return ('\\' == c) || ('/' == c) || (':' == c);
+#else
+ return '/' == c;
+#endif
+}
+
} // namespace util