mirror of
https://github.com/holub/mame
synced 2025-05-20 20:58:51 +03:00

intended differences from previous behavior. For drivers, the main change is that input_port_read() no longer exists. Instead, the port must be fetched from the appropriate device, and then read() is called. For member functions, this is actually simpler/cleaner: value = ioport("tag")->read() For legacy functions which have a driver_data state, it goes: value = state->ioport("tag")->read() For other legacy functions, they need to fetch the root device: value = machine.root_device().ioport("tag")->read() The other big change for drivers is that IPT_VBLANK is gone. Instead, it has been replaced by a device line callback on the screen device. There's a new macro PORT_VBLANK("tag") which automatically points things to the right spot. Here's a set of imperfect search & replace strings to convert the input_port_read calls and fix up IPT_VBLANK: input_port_read( *\( *)(machine\(\)) *, *([^)]+ *\)) ioport\1\3->read\(\) input_port_read( *\( *)(.*machine[()]*) *, *([^)]+ *\)) \2\.root_device\(\)\.ioport\1\3->read\(\) (state = .*driver_data[^}]+)space->machine\(\)\.root_device\(\)\. \1state-> (state = .*driver_data[^}]+)device->machine\(\)\.root_device\(\)\. \1state-> input_port_read_safe( *\( *)(machine\(\)) *, *([^,]+), *([^)]+\)) ioport\1\3->read_safe\(\4\) IPT_VBLANK( *\)) IPT_CUSTOM\1 PORT_VBLANK("screen")
1114 lines
33 KiB
C
1114 lines
33 KiB
C
/*********************************************************************
|
|
|
|
uimenu.c
|
|
|
|
Internal MAME menus for the user interface.
|
|
|
|
Copyright Nicola Salmoria and the MAME Team.
|
|
Visit http://mamedev.org for licensing and usage restrictions.
|
|
|
|
*********************************************************************/
|
|
|
|
#include "emu.h"
|
|
#include "emuopts.h"
|
|
#include "ui.h"
|
|
#include "rendutil.h"
|
|
#include "uiinput.h"
|
|
#include "cheat.h"
|
|
#include "uimain.h"
|
|
#include <ctype.h>
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
CONSTANTS
|
|
***************************************************************************/
|
|
|
|
#define UI_MENU_POOL_SIZE 65536
|
|
#define UI_MENU_ALLOC_ITEMS 256
|
|
|
|
/***************************************************************************
|
|
GLOBAL VARIABLES
|
|
***************************************************************************/
|
|
|
|
ui_menu *ui_menu::menu_stack;
|
|
ui_menu *ui_menu::menu_free;
|
|
bitmap_rgb32 *ui_menu::hilight_bitmap;
|
|
render_texture *ui_menu::hilight_texture;
|
|
render_texture *ui_menu::arrow_texture;
|
|
|
|
/***************************************************************************
|
|
INLINE FUNCTIONS
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
is_selectable - return TRUE if the given
|
|
item is selectable
|
|
-------------------------------------------------*/
|
|
|
|
inline bool ui_menu_item::is_selectable() const
|
|
{
|
|
return ((flags & (MENU_FLAG_MULTILINE | MENU_FLAG_DISABLE)) == 0 && strcmp(text, MENU_SEPARATOR_ITEM) != 0);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
exclusive_input_pressed - return TRUE if the
|
|
given key is pressed and we haven't already
|
|
reported a key
|
|
-------------------------------------------------*/
|
|
|
|
inline bool ui_menu::exclusive_input_pressed(int key, int repeat)
|
|
{
|
|
if (menu_event.iptkey == IPT_INVALID && ui_input_pressed_repeat(machine(), key, repeat))
|
|
{
|
|
menu_event.iptkey = key;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
CORE SYSTEM MANAGEMENT
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
init - initialize the menu system
|
|
-------------------------------------------------*/
|
|
|
|
void ui_menu::init(running_machine &machine)
|
|
{
|
|
int x;
|
|
|
|
/* initialize the menu stack */
|
|
ui_menu::stack_reset(machine);
|
|
|
|
/* create a texture for hilighting items */
|
|
hilight_bitmap = auto_bitmap_rgb32_alloc(machine, 256, 1);
|
|
for (x = 0; x < 256; x++)
|
|
{
|
|
int alpha = 0xff;
|
|
if (x < 25) alpha = 0xff * x / 25;
|
|
if (x > 256 - 25) alpha = 0xff * (255 - x) / 25;
|
|
hilight_bitmap->pix32(0, x) = MAKE_ARGB(alpha,0xff,0xff,0xff);
|
|
}
|
|
hilight_texture = machine.render().texture_alloc();
|
|
hilight_texture->set_bitmap(*hilight_bitmap, hilight_bitmap->cliprect(), TEXFORMAT_ARGB32);
|
|
|
|
/* create a texture for arrow icons */
|
|
arrow_texture = machine.render().texture_alloc(render_triangle);
|
|
|
|
/* add an exit callback to free memory */
|
|
machine.add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(FUNC(ui_menu::exit), &machine));
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
exit - clean up after ourselves
|
|
-------------------------------------------------*/
|
|
|
|
void ui_menu::exit(running_machine &machine)
|
|
{
|
|
/* free menus */
|
|
ui_menu::stack_reset(machine);
|
|
ui_menu::clear_free_list(machine);
|
|
|
|
/* free textures */
|
|
machine.render().texture_free(hilight_texture);
|
|
machine.render().texture_free(arrow_texture);
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
CORE MENU MANAGEMENT
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
ui_menu - menu constructor
|
|
-------------------------------------------------*/
|
|
ui_menu::ui_menu(running_machine &machine, render_container *_container) : m_machine(machine)
|
|
{
|
|
special_main_menu = false;
|
|
container = _container;
|
|
|
|
reset(UI_MENU_RESET_SELECT_FIRST);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
~ui_menu - menu destructor
|
|
-------------------------------------------------*/
|
|
|
|
ui_menu::~ui_menu()
|
|
{
|
|
/* free the pools */
|
|
while (pool)
|
|
{
|
|
ui_menu_pool *ppool = pool;
|
|
pool = pool->next;
|
|
auto_free(machine(), ppool);
|
|
}
|
|
|
|
/* free the item array */
|
|
if (item)
|
|
auto_free(machine(), item);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
reset - free all items in the menu,
|
|
and all memory allocated from the memory pool
|
|
-------------------------------------------------*/
|
|
|
|
void ui_menu::reset(ui_menu_reset_options options)
|
|
{
|
|
/* based on the reset option, set the reset info */
|
|
resetpos = 0;
|
|
resetref = NULL;
|
|
if (options == UI_MENU_RESET_REMEMBER_POSITION)
|
|
resetpos = selected;
|
|
else if (options == UI_MENU_RESET_REMEMBER_REF)
|
|
resetref = item[selected].ref;
|
|
|
|
/* reset all the pools and the numitems back to 0 */
|
|
for (ui_menu_pool *ppool = pool; ppool != NULL; ppool = ppool->next)
|
|
ppool->top = (UINT8 *)(ppool + 1);
|
|
numitems = 0;
|
|
visitems = 0;
|
|
selected = 0;
|
|
astring backtext;
|
|
backtext.printf("Return to %s",emulator_info::get_capstartgamenoun());
|
|
|
|
/* add an item to return */
|
|
if (parent == NULL)
|
|
item_append(backtext.cstr(), NULL, 0, NULL);
|
|
else if (parent->is_special_main_menu())
|
|
item_append("Exit", NULL, 0, NULL);
|
|
else
|
|
item_append("Return to Prior Menu", NULL, 0, NULL);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
is_special_main_menu - returns whether the
|
|
menu has special needs
|
|
-------------------------------------------------*/
|
|
bool ui_menu::is_special_main_menu() const
|
|
{
|
|
return special_main_menu;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
set_special_main_menu - set whether the
|
|
menu has special needs
|
|
-------------------------------------------------*/
|
|
void ui_menu::set_special_main_menu(bool special)
|
|
{
|
|
special_main_menu = special;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
populated - returns true if the menu
|
|
has any non-default items in it
|
|
-------------------------------------------------*/
|
|
|
|
bool ui_menu::populated()
|
|
{
|
|
return numitems > 1;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
item_append - append a new item to the
|
|
end of the menu
|
|
-------------------------------------------------*/
|
|
|
|
void ui_menu::item_append(const char *text, const char *subtext, UINT32 flags, void *ref)
|
|
{
|
|
ui_menu_item *pitem;
|
|
int index;
|
|
|
|
/* only allow multiline as the first item */
|
|
if ((flags & MENU_FLAG_MULTILINE) != 0)
|
|
assert(numitems == 1);
|
|
|
|
/* only allow a single multi-line item */
|
|
else if (numitems >= 2)
|
|
assert((item[0].flags & MENU_FLAG_MULTILINE) == 0);
|
|
|
|
/* realloc the item array if necessary */
|
|
if (numitems >= allocitems)
|
|
{
|
|
int olditems = allocitems;
|
|
allocitems += UI_MENU_ALLOC_ITEMS;
|
|
ui_menu_item *newitems = auto_alloc_array(machine(), ui_menu_item, allocitems);
|
|
for (int itemnum = 0; itemnum < olditems; itemnum++)
|
|
newitems[itemnum] = item[itemnum];
|
|
auto_free(machine(), item);
|
|
item = newitems;
|
|
}
|
|
index = numitems++;
|
|
|
|
/* copy the previous last item to the next one */
|
|
if (index != 0)
|
|
{
|
|
index--;
|
|
item[index + 1] = item[index];
|
|
}
|
|
|
|
/* allocate a new item and populate it */
|
|
pitem = &item[index];
|
|
pitem->text = (text != NULL) ? pool_strdup(text) : NULL;
|
|
pitem->subtext = (subtext != NULL) ? pool_strdup(subtext) : NULL;
|
|
pitem->flags = flags;
|
|
pitem->ref = ref;
|
|
|
|
/* update the selection if we need to */
|
|
if (resetpos == index || (resetref != NULL && resetref == ref))
|
|
selected = index;
|
|
if (resetpos == numitems - 1)
|
|
selected = numitems - 1;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
process - process a menu, drawing it
|
|
and returning any interesting events
|
|
-------------------------------------------------*/
|
|
|
|
const ui_menu_event *ui_menu::process(UINT32 flags)
|
|
{
|
|
/* reset the menu_event */
|
|
menu_event.iptkey = IPT_INVALID;
|
|
|
|
/* first make sure our selection is valid */
|
|
validate_selection(1);
|
|
|
|
/* draw the menu */
|
|
if (numitems > 1 && (item[0].flags & MENU_FLAG_MULTILINE) != 0)
|
|
draw_text_box();
|
|
else
|
|
draw(flags & UI_MENU_PROCESS_CUSTOM_ONLY);
|
|
|
|
/* process input */
|
|
if (!(flags & UI_MENU_PROCESS_NOKEYS))
|
|
{
|
|
/* read events */
|
|
handle_events();
|
|
|
|
/* handle the keys if we don't already have an menu_event */
|
|
if (menu_event.iptkey == IPT_INVALID)
|
|
handle_keys(flags);
|
|
}
|
|
|
|
/* update the selected item in the menu_event */
|
|
if (menu_event.iptkey != IPT_INVALID && selected >= 0 && selected < numitems)
|
|
{
|
|
menu_event.itemref = item[selected].ref;
|
|
return &menu_event;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
m_pool_alloc - allocate temporary memory
|
|
from the menu's memory pool
|
|
-------------------------------------------------*/
|
|
|
|
void *ui_menu::m_pool_alloc(size_t size)
|
|
{
|
|
ui_menu_pool *ppool;
|
|
|
|
assert(size < UI_MENU_POOL_SIZE);
|
|
|
|
/* find a pool with enough room */
|
|
for (ppool = pool; ppool != NULL; ppool = ppool->next)
|
|
if (ppool->end - ppool->top >= size)
|
|
{
|
|
void *result = ppool->top;
|
|
ppool->top += size;
|
|
return result;
|
|
}
|
|
|
|
/* allocate a new pool */
|
|
ppool = (ui_menu_pool *)auto_alloc_array_clear(machine(), UINT8, sizeof(*ppool) + UI_MENU_POOL_SIZE);
|
|
|
|
/* wire it up */
|
|
ppool->next = pool;
|
|
pool = ppool;
|
|
ppool->top = (UINT8 *)(ppool + 1);
|
|
ppool->end = ppool->top + UI_MENU_POOL_SIZE;
|
|
return m_pool_alloc(size);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
pool_strdup - make a temporary string
|
|
copy in the menu's memory pool
|
|
-------------------------------------------------*/
|
|
|
|
const char *ui_menu::pool_strdup(const char *string)
|
|
{
|
|
return strcpy((char *)m_pool_alloc(strlen(string) + 1), string);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
get_selection - retrieves the index
|
|
of the currently selected menu item
|
|
-------------------------------------------------*/
|
|
|
|
void *ui_menu::get_selection()
|
|
{
|
|
return (selected >= 0 && selected < numitems) ? item[selected].ref : NULL;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
set_selection - changes the index
|
|
of the currently selected menu item
|
|
-------------------------------------------------*/
|
|
|
|
void ui_menu::set_selection(void *selected_itemref)
|
|
{
|
|
int itemnum;
|
|
|
|
selected = -1;
|
|
for (itemnum = 0; itemnum < numitems; itemnum++)
|
|
if (item[itemnum].ref == selected_itemref)
|
|
{
|
|
selected = itemnum;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
INTERNAL MENU PROCESSING
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
draw - draw a menu
|
|
-------------------------------------------------*/
|
|
|
|
void ui_menu::draw(bool customonly)
|
|
{
|
|
float line_height = ui_get_line_height(machine());
|
|
float lr_arrow_width = 0.4f * line_height * machine().render().ui_aspect();
|
|
float ud_arrow_width = line_height * machine().render().ui_aspect();
|
|
float gutter_width = lr_arrow_width * 1.3f;
|
|
float x1, y1, x2, y2;
|
|
|
|
float effective_width, effective_left;
|
|
float visible_width, visible_main_menu_height;
|
|
float visible_extra_menu_height = 0;
|
|
float visible_top, visible_left;
|
|
int selected_subitem_too_big = FALSE;
|
|
int visible_lines;
|
|
int top_line;
|
|
int itemnum, linenum;
|
|
int mouse_hit, mouse_button;
|
|
render_target *mouse_target;
|
|
INT32 mouse_target_x, mouse_target_y;
|
|
float mouse_x = -1, mouse_y = -1;
|
|
|
|
/* compute the width and height of the full menu */
|
|
visible_width = 0;
|
|
visible_main_menu_height = 0;
|
|
for (itemnum = 0; itemnum < numitems; itemnum++)
|
|
{
|
|
const ui_menu_item &pitem = item[itemnum];
|
|
float total_width;
|
|
|
|
/* compute width of left hand side */
|
|
total_width = gutter_width + ui_get_string_width(machine(), pitem.text) + gutter_width;
|
|
|
|
/* add in width of right hand side */
|
|
if (pitem.subtext)
|
|
total_width += 2.0f * gutter_width + ui_get_string_width(machine(), pitem.subtext);
|
|
|
|
/* track the maximum */
|
|
if (total_width > visible_width)
|
|
visible_width = total_width;
|
|
|
|
/* track the height as well */
|
|
visible_main_menu_height += line_height;
|
|
}
|
|
|
|
/* account for extra space at the top and bottom */
|
|
visible_extra_menu_height = customtop + custombottom;
|
|
|
|
/* add a little bit of slop for rounding */
|
|
visible_width += 0.01f;
|
|
visible_main_menu_height += 0.01f;
|
|
|
|
/* if we are too wide or too tall, clamp it down */
|
|
if (visible_width + 2.0f * UI_BOX_LR_BORDER > 1.0f)
|
|
visible_width = 1.0f - 2.0f * UI_BOX_LR_BORDER;
|
|
|
|
/* if the menu and extra menu won't fit, take away part of the regular menu, it will scroll */
|
|
if (visible_main_menu_height + visible_extra_menu_height + 2.0f * UI_BOX_TB_BORDER > 1.0f)
|
|
visible_main_menu_height = 1.0f - 2.0f * UI_BOX_TB_BORDER - visible_extra_menu_height;
|
|
|
|
visible_lines = floor(visible_main_menu_height / line_height);
|
|
visible_main_menu_height = (float)visible_lines * line_height;
|
|
|
|
/* compute top/left of inner menu area by centering */
|
|
visible_left = (1.0f - visible_width) * 0.5f;
|
|
visible_top = (1.0f - (visible_main_menu_height + visible_extra_menu_height)) * 0.5f;
|
|
|
|
/* if the menu is at the bottom of the extra, adjust */
|
|
visible_top += customtop;
|
|
|
|
/* first add us a box */
|
|
x1 = visible_left - UI_BOX_LR_BORDER;
|
|
y1 = visible_top - UI_BOX_TB_BORDER;
|
|
x2 = visible_left + visible_width + UI_BOX_LR_BORDER;
|
|
y2 = visible_top + visible_main_menu_height + UI_BOX_TB_BORDER;
|
|
if (!customonly)
|
|
ui_draw_outlined_box(container, x1, y1, x2, y2, UI_BACKGROUND_COLOR);
|
|
|
|
/* determine the first visible line based on the current selection */
|
|
top_line = selected - visible_lines / 2;
|
|
if (top_line < 0)
|
|
top_line = 0;
|
|
if (top_line + visible_lines >= numitems)
|
|
top_line = numitems - visible_lines;
|
|
|
|
/* determine effective positions taking into account the hilighting arrows */
|
|
effective_width = visible_width - 2.0f * gutter_width;
|
|
effective_left = visible_left + gutter_width;
|
|
|
|
/* locate mouse */
|
|
mouse_hit = FALSE;
|
|
mouse_button = FALSE;
|
|
if (!customonly)
|
|
{
|
|
mouse_target = ui_input_find_mouse(machine(), &mouse_target_x, &mouse_target_y, &mouse_button);
|
|
if (mouse_target != NULL)
|
|
if (mouse_target->map_point_container(mouse_target_x, mouse_target_y, *container, mouse_x, mouse_y))
|
|
mouse_hit = TRUE;
|
|
}
|
|
|
|
/* loop over visible lines */
|
|
hover = numitems + 1;
|
|
if (!customonly)
|
|
for (linenum = 0; linenum < visible_lines; linenum++)
|
|
{
|
|
float line_y = visible_top + (float)linenum * line_height;
|
|
itemnum = top_line + linenum;
|
|
const ui_menu_item &pitem = item[itemnum];
|
|
const char *itemtext = pitem.text;
|
|
rgb_t fgcolor = UI_TEXT_COLOR;
|
|
rgb_t bgcolor = UI_TEXT_BG_COLOR;
|
|
rgb_t fgcolor2 = UI_SUBITEM_COLOR;
|
|
rgb_t fgcolor3 = UI_CLONE_COLOR;
|
|
float line_x0 = x1 + 0.5f * UI_LINE_WIDTH;
|
|
float line_y0 = line_y;
|
|
float line_x1 = x2 - 0.5f * UI_LINE_WIDTH;
|
|
float line_y1 = line_y + line_height;
|
|
|
|
/* set the hover if this is our item */
|
|
if (mouse_hit && line_x0 <= mouse_x && line_x1 > mouse_x && line_y0 <= mouse_y && line_y1 > mouse_y && pitem.is_selectable())
|
|
hover = itemnum;
|
|
|
|
/* if we're selected, draw with a different background */
|
|
if (itemnum == selected)
|
|
{
|
|
fgcolor = UI_SELECTED_COLOR;
|
|
bgcolor = UI_SELECTED_BG_COLOR;
|
|
fgcolor2 = UI_SELECTED_COLOR;
|
|
fgcolor3 = UI_SELECTED_COLOR;
|
|
}
|
|
|
|
/* else if the mouse is over this item, draw with a different background */
|
|
else if (itemnum == hover)
|
|
{
|
|
fgcolor = UI_MOUSEOVER_COLOR;
|
|
bgcolor = UI_MOUSEOVER_BG_COLOR;
|
|
fgcolor2 = UI_MOUSEOVER_COLOR;
|
|
fgcolor3 = UI_MOUSEOVER_COLOR;
|
|
}
|
|
|
|
/* if we have some background hilighting to do, add a quad behind everything else */
|
|
if (bgcolor != UI_TEXT_BG_COLOR)
|
|
container->add_quad(line_x0, line_y0, line_x1, line_y1, bgcolor, hilight_texture,
|
|
PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(TRUE));
|
|
|
|
/* if we're on the top line, display the up arrow */
|
|
if (linenum == 0 && top_line != 0)
|
|
{
|
|
container->add_quad(
|
|
0.5f * (x1 + x2) - 0.5f * ud_arrow_width,
|
|
line_y + 0.25f * line_height,
|
|
0.5f * (x1 + x2) + 0.5f * ud_arrow_width,
|
|
line_y + 0.75f * line_height,
|
|
fgcolor,
|
|
arrow_texture,
|
|
PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXORIENT(ROT0));
|
|
if (hover == itemnum)
|
|
hover = -2;
|
|
}
|
|
|
|
/* if we're on the bottom line, display the down arrow */
|
|
else if (linenum == visible_lines - 1 && itemnum != numitems - 1)
|
|
{
|
|
container->add_quad(
|
|
0.5f * (x1 + x2) - 0.5f * ud_arrow_width,
|
|
line_y + 0.25f * line_height,
|
|
0.5f * (x1 + x2) + 0.5f * ud_arrow_width,
|
|
line_y + 0.75f * line_height,
|
|
fgcolor,
|
|
arrow_texture,
|
|
PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXORIENT(ROT0 ^ ORIENTATION_FLIP_Y));
|
|
if (hover == itemnum)
|
|
hover = -1;
|
|
}
|
|
|
|
/* if we're just a divider, draw a line */
|
|
else if (strcmp(itemtext, MENU_SEPARATOR_ITEM) == 0)
|
|
container->add_line(visible_left, line_y + 0.5f * line_height, visible_left + visible_width, line_y + 0.5f * line_height, UI_LINE_WIDTH, UI_BORDER_COLOR, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA));
|
|
|
|
/* if we don't have a subitem, just draw the string centered */
|
|
else if (pitem.subtext == NULL)
|
|
ui_draw_text_full(container, itemtext, effective_left, line_y, effective_width,
|
|
JUSTIFY_CENTER, WRAP_TRUNCATE, DRAW_NORMAL, fgcolor, bgcolor, NULL, NULL);
|
|
|
|
/* otherwise, draw the item on the left and the subitem text on the right */
|
|
else
|
|
{
|
|
int subitem_invert = pitem.flags & MENU_FLAG_INVERT;
|
|
const char *subitem_text = pitem.subtext;
|
|
float item_width, subitem_width;
|
|
|
|
/* draw the left-side text */
|
|
ui_draw_text_full(container, itemtext, effective_left, line_y, effective_width,
|
|
JUSTIFY_LEFT, WRAP_TRUNCATE, DRAW_NORMAL, fgcolor, bgcolor, &item_width, NULL);
|
|
|
|
/* give 2 spaces worth of padding */
|
|
item_width += 2.0f * gutter_width;
|
|
|
|
/* if the subitem doesn't fit here, display dots */
|
|
if (ui_get_string_width(machine(), subitem_text) > effective_width - item_width)
|
|
{
|
|
subitem_text = "...";
|
|
if (itemnum == selected)
|
|
selected_subitem_too_big = TRUE;
|
|
}
|
|
|
|
/* draw the subitem right-justified */
|
|
ui_draw_text_full(container, subitem_text, effective_left + item_width, line_y, effective_width - item_width,
|
|
JUSTIFY_RIGHT, WRAP_TRUNCATE, DRAW_NORMAL, subitem_invert ? fgcolor3 : fgcolor2, bgcolor, &subitem_width, NULL);
|
|
|
|
/* apply arrows */
|
|
if (itemnum == selected && (pitem.flags & MENU_FLAG_LEFT_ARROW))
|
|
{
|
|
container->add_quad(
|
|
effective_left + effective_width - subitem_width - gutter_width,
|
|
line_y + 0.1f * line_height,
|
|
effective_left + effective_width - subitem_width - gutter_width + lr_arrow_width,
|
|
line_y + 0.9f * line_height,
|
|
fgcolor,
|
|
arrow_texture,
|
|
PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXORIENT(ROT90 ^ ORIENTATION_FLIP_X));
|
|
}
|
|
if (itemnum == selected && (pitem.flags & MENU_FLAG_RIGHT_ARROW))
|
|
{
|
|
container->add_quad(
|
|
effective_left + effective_width + gutter_width - lr_arrow_width,
|
|
line_y + 0.1f * line_height,
|
|
effective_left + effective_width + gutter_width,
|
|
line_y + 0.9f * line_height,
|
|
fgcolor,
|
|
arrow_texture,
|
|
PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXORIENT(ROT90));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* if the selected subitem is too big, display it in a separate offset box */
|
|
if (selected_subitem_too_big)
|
|
{
|
|
const ui_menu_item &pitem = item[selected];
|
|
int subitem_invert = pitem.flags & MENU_FLAG_INVERT;
|
|
linenum = selected - top_line;
|
|
float line_y = visible_top + (float)linenum * line_height;
|
|
float target_width, target_height;
|
|
float target_x, target_y;
|
|
|
|
/* compute the multi-line target width/height */
|
|
ui_draw_text_full(container, pitem.subtext, 0, 0, visible_width * 0.75f,
|
|
JUSTIFY_RIGHT, WRAP_WORD, DRAW_NONE, ARGB_WHITE, ARGB_BLACK, &target_width, &target_height);
|
|
|
|
/* determine the target location */
|
|
target_x = visible_left + visible_width - target_width - UI_BOX_LR_BORDER;
|
|
target_y = line_y + line_height + UI_BOX_TB_BORDER;
|
|
if (target_y + target_height + UI_BOX_TB_BORDER > visible_main_menu_height)
|
|
target_y = line_y - target_height - UI_BOX_TB_BORDER;
|
|
|
|
/* add a box around that */
|
|
ui_draw_outlined_box(container, target_x - UI_BOX_LR_BORDER,
|
|
target_y - UI_BOX_TB_BORDER,
|
|
target_x + target_width + UI_BOX_LR_BORDER,
|
|
target_y + target_height + UI_BOX_TB_BORDER, subitem_invert ? UI_SELECTED_BG_COLOR : UI_BACKGROUND_COLOR);
|
|
ui_draw_text_full(container, pitem.subtext, target_x, target_y, target_width,
|
|
JUSTIFY_RIGHT, WRAP_WORD, DRAW_NORMAL, UI_SELECTED_COLOR, UI_SELECTED_BG_COLOR, NULL, NULL);
|
|
}
|
|
|
|
/* if there is something special to add, do it by calling the virtual method */
|
|
custom_render((selected >= 0 && selected < numitems) ? item[selected].ref : NULL, customtop, custombottom, x1, y1, x2, y2);
|
|
|
|
/* return the number of visible lines, minus 1 for top arrow and 1 for bottom arrow */
|
|
visitems = visible_lines - (top_line != 0) - (top_line + visible_lines != numitems);
|
|
}
|
|
|
|
void ui_menu::custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2)
|
|
{
|
|
}
|
|
|
|
/*-------------------------------------------------
|
|
draw_text_box - draw a multiline
|
|
word-wrapped text box with a menu item at the
|
|
bottom
|
|
-------------------------------------------------*/
|
|
|
|
void ui_menu::draw_text_box()
|
|
{
|
|
const char *text = item[0].text;
|
|
const char *backtext = item[1].text;
|
|
float line_height = ui_get_line_height(machine());
|
|
float lr_arrow_width = 0.4f * line_height * machine().render().ui_aspect();
|
|
float gutter_width = lr_arrow_width;
|
|
float target_width, target_height, prior_width;
|
|
float target_x, target_y;
|
|
|
|
/* compute the multi-line target width/height */
|
|
ui_draw_text_full(container, text, 0, 0, 1.0f - 2.0f * UI_BOX_LR_BORDER - 2.0f * gutter_width,
|
|
JUSTIFY_LEFT, WRAP_WORD, DRAW_NONE, ARGB_WHITE, ARGB_BLACK, &target_width, &target_height);
|
|
target_height += 2.0f * line_height;
|
|
if (target_height > 1.0f - 2.0f * UI_BOX_TB_BORDER)
|
|
target_height = floor((1.0f - 2.0f * UI_BOX_TB_BORDER) / line_height) * line_height;
|
|
|
|
/* maximum against "return to prior menu" text */
|
|
prior_width = ui_get_string_width(machine(), backtext) + 2.0f * gutter_width;
|
|
target_width = MAX(target_width, prior_width);
|
|
|
|
/* determine the target location */
|
|
target_x = 0.5f - 0.5f * target_width;
|
|
target_y = 0.5f - 0.5f * target_height;
|
|
|
|
/* make sure we stay on-screen */
|
|
if (target_x < UI_BOX_LR_BORDER + gutter_width)
|
|
target_x = UI_BOX_LR_BORDER + gutter_width;
|
|
if (target_x + target_width + gutter_width + UI_BOX_LR_BORDER > 1.0f)
|
|
target_x = 1.0f - UI_BOX_LR_BORDER - gutter_width - target_width;
|
|
if (target_y < UI_BOX_TB_BORDER)
|
|
target_y = UI_BOX_TB_BORDER;
|
|
if (target_y + target_height + UI_BOX_TB_BORDER > 1.0f)
|
|
target_y = 1.0f - UI_BOX_TB_BORDER - target_height;
|
|
|
|
/* add a box around that */
|
|
ui_draw_outlined_box(container, target_x - UI_BOX_LR_BORDER - gutter_width,
|
|
target_y - UI_BOX_TB_BORDER,
|
|
target_x + target_width + gutter_width + UI_BOX_LR_BORDER,
|
|
target_y + target_height + UI_BOX_TB_BORDER, (item[0].flags & MENU_FLAG_REDTEXT) ? UI_RED_COLOR : UI_BACKGROUND_COLOR);
|
|
ui_draw_text_full(container, text, target_x, target_y, target_width,
|
|
JUSTIFY_LEFT, WRAP_WORD, DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, NULL, NULL);
|
|
|
|
/* draw the "return to prior menu" text with a hilight behind it */
|
|
container->add_quad(
|
|
target_x + 0.5f * UI_LINE_WIDTH,
|
|
target_y + target_height - line_height,
|
|
target_x + target_width - 0.5f * UI_LINE_WIDTH,
|
|
target_y + target_height,
|
|
UI_SELECTED_BG_COLOR,
|
|
hilight_texture,
|
|
PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(TRUE));
|
|
ui_draw_text_full(container, backtext, target_x, target_y + target_height - line_height, target_width,
|
|
JUSTIFY_CENTER, WRAP_TRUNCATE, DRAW_NORMAL, UI_SELECTED_COLOR, UI_SELECTED_BG_COLOR, NULL, NULL);
|
|
|
|
/* artificially set the hover to the last item so a double-click exits */
|
|
hover = numitems - 1;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
handle_events - generically handle
|
|
input events for a menu
|
|
-------------------------------------------------*/
|
|
|
|
void ui_menu::handle_events()
|
|
{
|
|
int stop = FALSE;
|
|
ui_event local_menu_event;
|
|
|
|
/* loop while we have interesting events */
|
|
while (ui_input_pop_event(machine(), &local_menu_event) && !stop)
|
|
{
|
|
switch (local_menu_event.event_type)
|
|
{
|
|
/* if we are hovering over a valid item, select it with a single click */
|
|
case UI_EVENT_MOUSE_DOWN:
|
|
if (hover >= 0 && hover < numitems)
|
|
selected = hover;
|
|
else if (hover == -2)
|
|
{
|
|
selected -= visitems - 1;
|
|
validate_selection(1);
|
|
}
|
|
else if (hover == -1)
|
|
{
|
|
selected += visitems - 1;
|
|
validate_selection(1);
|
|
}
|
|
break;
|
|
|
|
/* if we are hovering over a valid item, fake a UI_SELECT with a double-click */
|
|
case UI_EVENT_MOUSE_DOUBLE_CLICK:
|
|
if (hover >= 0 && hover < numitems)
|
|
{
|
|
selected = hover;
|
|
if (local_menu_event.event_type == UI_EVENT_MOUSE_DOUBLE_CLICK)
|
|
{
|
|
menu_event.iptkey = IPT_UI_SELECT;
|
|
if (selected == numitems - 1)
|
|
{
|
|
menu_event.iptkey = IPT_UI_CANCEL;
|
|
ui_menu::stack_pop(machine());
|
|
}
|
|
}
|
|
stop = TRUE;
|
|
}
|
|
break;
|
|
|
|
/* translate CHAR events into specials */
|
|
case UI_EVENT_CHAR:
|
|
menu_event.iptkey = IPT_SPECIAL;
|
|
menu_event.unichar = local_menu_event.ch;
|
|
stop = TRUE;
|
|
break;
|
|
|
|
/* ignore everything else */
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
handle_keys - generically handle
|
|
keys for a menu
|
|
-------------------------------------------------*/
|
|
|
|
void ui_menu::handle_keys(UINT32 flags)
|
|
{
|
|
int ignorepause = ui_menu::stack_has_special_main_menu();
|
|
int ignoreright = FALSE;
|
|
int ignoreleft = FALSE;
|
|
int code;
|
|
|
|
/* bail if no items */
|
|
if (numitems == 0)
|
|
return;
|
|
|
|
/* if we hit select, return TRUE or pop the stack, depending on the item */
|
|
if (exclusive_input_pressed(IPT_UI_SELECT, 0))
|
|
{
|
|
if (selected == numitems - 1)
|
|
{
|
|
menu_event.iptkey = IPT_UI_CANCEL;
|
|
ui_menu::stack_pop(machine());
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* hitting cancel also pops the stack */
|
|
if (exclusive_input_pressed(IPT_UI_CANCEL, 0))
|
|
{
|
|
ui_menu::stack_pop(machine());
|
|
return;
|
|
}
|
|
|
|
/* validate the current selection */
|
|
validate_selection(1);
|
|
|
|
/* swallow left/right keys if they are not appropriate */
|
|
ignoreleft = ((item[selected].flags & MENU_FLAG_LEFT_ARROW) == 0);
|
|
ignoreright = ((item[selected].flags & MENU_FLAG_RIGHT_ARROW) == 0);
|
|
|
|
/* accept left/right keys as-is with repeat */
|
|
if (!ignoreleft && exclusive_input_pressed(IPT_UI_LEFT, (flags & UI_MENU_PROCESS_LR_REPEAT) ? 6 : 0))
|
|
return;
|
|
if (!ignoreright && exclusive_input_pressed(IPT_UI_RIGHT, (flags & UI_MENU_PROCESS_LR_REPEAT) ? 6 : 0))
|
|
return;
|
|
|
|
/* up backs up by one item */
|
|
if (exclusive_input_pressed(IPT_UI_UP, 6))
|
|
{
|
|
selected = (selected + numitems - 1) % numitems;
|
|
validate_selection(-1);
|
|
}
|
|
|
|
/* down advances by one item */
|
|
if (exclusive_input_pressed(IPT_UI_DOWN, 6))
|
|
{
|
|
selected = (selected + 1) % numitems;
|
|
validate_selection(1);
|
|
}
|
|
|
|
/* page up backs up by visitems */
|
|
if (exclusive_input_pressed(IPT_UI_PAGE_UP, 6))
|
|
{
|
|
selected -= visitems - 1;
|
|
validate_selection(1);
|
|
}
|
|
|
|
/* page down advances by visitems */
|
|
if (exclusive_input_pressed(IPT_UI_PAGE_DOWN, 6))
|
|
{
|
|
selected += visitems - 1;
|
|
validate_selection(-1);
|
|
}
|
|
|
|
/* home goes to the start */
|
|
if (exclusive_input_pressed(IPT_UI_HOME, 0))
|
|
{
|
|
selected = 0;
|
|
validate_selection(1);
|
|
}
|
|
|
|
/* end goes to the last */
|
|
if (exclusive_input_pressed(IPT_UI_END, 0))
|
|
{
|
|
selected = numitems - 1;
|
|
validate_selection(-1);
|
|
}
|
|
|
|
/* pause enables/disables pause */
|
|
if (!ignorepause && exclusive_input_pressed(IPT_UI_PAUSE, 0))
|
|
{
|
|
if (machine().paused())
|
|
machine().resume();
|
|
else
|
|
machine().pause();
|
|
}
|
|
|
|
/* handle a toggle cheats request */
|
|
if (ui_input_pressed_repeat(machine(), IPT_UI_TOGGLE_CHEAT, 0))
|
|
machine().cheat().set_enable(!machine().cheat().enabled());
|
|
|
|
/* see if any other UI keys are pressed */
|
|
if (menu_event.iptkey == IPT_INVALID)
|
|
for (code = IPT_UI_FIRST + 1; code < IPT_UI_LAST; code++)
|
|
{
|
|
if (code == IPT_UI_CONFIGURE || (code == IPT_UI_LEFT && ignoreleft) || (code == IPT_UI_RIGHT && ignoreright) || (code == IPT_UI_PAUSE && ignorepause))
|
|
continue;
|
|
if (exclusive_input_pressed(code, 0))
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
validate_selection - validate the
|
|
current selection and ensure it is on a
|
|
correct item
|
|
-------------------------------------------------*/
|
|
|
|
void ui_menu::validate_selection(int scandir)
|
|
{
|
|
/* clamp to be in range */
|
|
if (selected < 0)
|
|
selected = 0;
|
|
else if (selected >= numitems)
|
|
selected = numitems - 1;
|
|
|
|
/* skip past unselectable items */
|
|
while (!item[selected].is_selectable())
|
|
selected = (selected + numitems + scandir) % numitems;
|
|
}
|
|
|
|
|
|
|
|
/*-------------------------------------------------
|
|
clear_free_list - clear out anything
|
|
accumulated in the free list
|
|
-------------------------------------------------*/
|
|
|
|
void ui_menu::clear_free_list(running_machine &machine)
|
|
{
|
|
while (menu_free != NULL)
|
|
{
|
|
ui_menu *menu = menu_free;
|
|
menu_free = menu->parent;
|
|
auto_free(machine, menu);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
MENU STACK MANAGEMENT
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
ui_menu::stack_reset - reset the menu stack
|
|
-------------------------------------------------*/
|
|
|
|
void ui_menu::stack_reset(running_machine &machine)
|
|
{
|
|
while (menu_stack != NULL)
|
|
ui_menu::stack_pop(machine);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
stack_push - push a new menu onto the
|
|
stack
|
|
-------------------------------------------------*/
|
|
|
|
void ui_menu::stack_push(ui_menu *menu)
|
|
{
|
|
menu->parent = menu_stack;
|
|
menu_stack = menu;
|
|
menu->reset(UI_MENU_RESET_SELECT_FIRST);
|
|
ui_input_reset(menu->machine());
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
stack_pop - pop a menu from the stack
|
|
-------------------------------------------------*/
|
|
|
|
void ui_menu::stack_pop(running_machine &machine)
|
|
{
|
|
if (menu_stack != NULL)
|
|
{
|
|
ui_menu *menu = menu_stack;
|
|
menu_stack = menu->parent;
|
|
menu->parent = menu_free;
|
|
menu_free = menu;
|
|
ui_input_reset(machine);
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
ui_menu::stack_has_special_main_menu -
|
|
check in the special main menu is in the stack
|
|
-------------------------------------------------*/
|
|
|
|
bool ui_menu::stack_has_special_main_menu()
|
|
{
|
|
ui_menu *menu;
|
|
|
|
for (menu = menu_stack; menu != NULL; menu = menu->parent)
|
|
if (menu->is_special_main_menu())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void ui_menu::do_handle()
|
|
{
|
|
if(!populated())
|
|
populate();
|
|
handle();
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
UI SYSTEM INTERACTION
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
ui_menu_ui_handler - displays the current menu
|
|
and calls the menu handler
|
|
-------------------------------------------------*/
|
|
|
|
UINT32 ui_menu::ui_handler(running_machine &machine, render_container *container, UINT32 state)
|
|
{
|
|
/* if we have no menus stacked up, start with the main menu */
|
|
if (menu_stack == NULL)
|
|
stack_push(auto_alloc_clear(machine, ui_menu_main(machine, container)));
|
|
|
|
/* update the menu state */
|
|
if (menu_stack != NULL)
|
|
menu_stack->do_handle();
|
|
|
|
/* clear up anything pending to be released */
|
|
clear_free_list(machine);
|
|
|
|
/* if the menus are to be hidden, return a cancel here */
|
|
if ((ui_input_pressed(machine, IPT_UI_CONFIGURE) && !stack_has_special_main_menu()) || menu_stack == NULL)
|
|
return UI_HANDLER_CANCEL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***************************************************************************
|
|
MENU HELPERS
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
render_triangle - render a triangle that
|
|
is used for up/down arrows and left/right
|
|
indicators
|
|
-------------------------------------------------*/
|
|
|
|
void ui_menu::render_triangle(bitmap_argb32 &dest, bitmap_argb32 &source, const rectangle &sbounds, void *param)
|
|
{
|
|
int halfwidth = dest.width() / 2;
|
|
int height = dest.height();
|
|
int x, y;
|
|
|
|
/* start with all-transparent */
|
|
dest.fill(MAKE_ARGB(0x00,0x00,0x00,0x00));
|
|
|
|
/* render from the tip to the bottom */
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
int linewidth = (y * (halfwidth - 1) + (height / 2)) * 255 * 2 / height;
|
|
UINT32 *target = &dest.pix32(y, halfwidth);
|
|
|
|
/* don't antialias if height < 12 */
|
|
if (dest.height() < 12)
|
|
{
|
|
int pixels = (linewidth + 254) / 255;
|
|
if (pixels % 2 == 0) pixels++;
|
|
linewidth = pixels * 255;
|
|
}
|
|
|
|
/* loop while we still have data to generate */
|
|
for (x = 0; linewidth > 0; x++)
|
|
{
|
|
int dalpha;
|
|
|
|
/* first column we only consume one pixel */
|
|
if (x == 0)
|
|
{
|
|
dalpha = MIN(0xff, linewidth);
|
|
target[x] = MAKE_ARGB(dalpha,0xff,0xff,0xff);
|
|
}
|
|
|
|
/* remaining columns consume two pixels, one on each side */
|
|
else
|
|
{
|
|
dalpha = MIN(0x1fe, linewidth);
|
|
target[x] = target[-x] = MAKE_ARGB(dalpha/2,0xff,0xff,0xff);
|
|
}
|
|
|
|
/* account for the weight we consumed */
|
|
linewidth -= dalpha;
|
|
}
|
|
}
|
|
}
|