mirror of
https://github.com/holub/mame
synced 2025-04-26 02:07:14 +03:00
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:
parent
3174c63078
commit
9198c2bd58
@ -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",
|
||||
|
@ -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);
|
||||
}
|
||||
|
781
src/frontend/mame/ui/icorender.cpp
Normal file
781
src/frontend/mame/ui/icorender.cpp
Normal 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
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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))
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -200,6 +200,7 @@ public:
|
||||
ALL = 0,
|
||||
AVAILABLE,
|
||||
UNAVAILABLE,
|
||||
FAVORITE,
|
||||
PARENTS,
|
||||
CLONES,
|
||||
YEAR,
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user