More UI stuff:

* Split up the different parts of ICO loading in the menus (locating files, scaling, drawing, etc.)
* Added icon support to software selection menu
* Added support for more ICO file variants, including PNG-in-ICO (new DIB parser is overkill for ICO but I can factor it out for BMP loading at some point)
* Added favourites filter for software menus - includes software that's favourited on any system, so GBC includes DMG favourties and vice versa
* Eliminated unnecessary member variables and O(n) walks in software selection menu
* Made the menus' cached texture structures a bit more efficient
This commit is contained in:
Vas Crabb 2019-01-19 17:34:43 +11:00
parent 3174c63078
commit 9198c2bd58
18 changed files with 1350 additions and 546 deletions

View File

@ -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",

View File

@ -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);
}

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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();

View File

@ -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());
render_load_ico_highest_detail(snapfile, tmp);
snapfile.close();
}
if (!tmp.valid() && cloneof && (snapfile.open(std::string(driver->parent).append(".ico")) == osd_file::error::NONE))
{
render_load_ico_highest_detail(snapfile, tmp);
snapfile.close();
}
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);
}
else
{
bitmap.reset();
}
scale_icon(std::move(tmp), icon->second);
}
if (icon->second.second.valid())
container().add_quad(x0, y0, x1, y1, rgb_t::white(), icon->second.first.get(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA));
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();
}

View File

@ -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);

View File

@ -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))
{

View File

@ -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;

View File

@ -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();
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));
std::string cx(x->parentlongname), cy(y->parentlongname);
if (cx.empty())
clonex = false;
if (cy.empty())
cloney = false;
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 if (clonex && !cloney)
{
if ((a.parentname == b.shortname) && (a.instance == b.instance))
return false;
else
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
{
if (!core_stricmp(x->parentname.c_str(), y->shortname.c_str()) && !core_stricmp(x->instance.c_str(), y->instance.c_str()))
return false;
else
return (strmakelower(cx) < strmakelower(y->longname));
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,47 +359,37 @@ 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);
if (instance_name && type_name)
{
// add to collection and try to resolve parent if applicable
auto const ins(m_swinfo.emplace(m_swinfo.end(), swinfo, part, m_driver, swlist.list_name(), instance_name, type_name));
if (!swinfo.parentname().empty())
{
if ((parentnames.end() == prevparent) || (swinfo.parentname() != prevparent->first))
prevparent = parentnames.find(swinfo.parentname());
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));
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);
}
}
}
}
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)
{
m_swinfo[y].parentlongname = m_swinfo[x].longname;
found = true;
break;
}
// 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;
}
}
}
std::string searchstr, curpath;
for (auto & elem : m_filter_data.list_names())
@ -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;
}
}
@ -529,6 +551,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();

View File

@ -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

View File

@ -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:

View File

@ -200,6 +200,7 @@ public:
ALL = 0,
AVAILABLE,
UNAVAILABLE,
FAVORITE,
PARENTS,
CLONES,
YEAR,

View File

@ -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));
}

View File

@ -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);

View File

@ -23,9 +23,10 @@
#include <ctype.h>
namespace util {
namespace {
/***************************************************************************
VALIDATION
***************************************************************************/
@ -335,23 +336,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

View File

@ -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