/*********************************************************************

    uigfx.c

    Internal graphics viewer.

    Copyright Nicola Salmoria and the MAME Team.
    Visit http://mamedev.org for licensing and usage restrictions.

*********************************************************************/

#include "ui.h"
#include "uiinput.h"
#include "render.h"
#include "rendfont.h"
#include "rendutil.h"
#include "uigfx.h"



/***************************************************************************
    TYPE DEFINITIONS
***************************************************************************/

typedef struct _ui_gfx_state ui_gfx_state;
struct _ui_gfx_state
{
	UINT8			mode;				/* which mode are we in? */

	/* intermediate bitmaps */
	UINT8			bitmap_dirty;		/* is the bitmap dirty? */
	bitmap_t *		bitmap;				/* bitmap for drawing gfx and tilemaps */
	render_texture *texture;			/* texture for rendering the above bitmap */

	/* palette-specific data */
	struct
	{
		int		which;					/* which subset (palette or colortable)? */
		int		offset;					/* current offset of top,left item */
		int		count;					/* number of items per row */
	} palette;

	/* graphics-viewer-specific data */
	struct
	{
		int		set;					/* which set is visible */
		int		offset[MAX_GFX_ELEMENTS]; /* current offset of top,left item */
		int		color[MAX_GFX_ELEMENTS]; /* current color selected */
		int		count[MAX_GFX_ELEMENTS]; /* number of items per row */
		UINT8	rotate[MAX_GFX_ELEMENTS]; /* current rotation (orientation) value */
	} gfxset;

	/* tilemap-viewer-specific data */
	struct
	{
		int		which;					/* which tilemap are we viewing? */
		int		xoffs;					/* current X offset */
		int		yoffs;					/* current Y offset */
		int		zoom;					/* zoom factor */
		UINT8	rotate;					/* current rotation (orientation) value */
	} tilemap;
};



/***************************************************************************
    GLOBAL VARIABLES
***************************************************************************/

static ui_gfx_state ui_gfx;



/***************************************************************************
    FUNCTION PROTOTYPES
***************************************************************************/

static void ui_gfx_exit(running_machine *machine);

/* palette handling */
static void palette_handle_keys(running_machine *machine, ui_gfx_state *state);
static void palette_handler(running_machine *machine, ui_gfx_state *state);

/* graphics set handling */
static void gfxset_handle_keys(running_machine *machine, ui_gfx_state *state, int xcells, int ycells);
static void gfxset_draw_item(running_machine *machine, const gfx_element *gfx, int index, bitmap_t *bitmap, int dstx, int dsty, int color, int rotate);
static void gfxset_update_bitmap(running_machine *machine, ui_gfx_state *state, int xcells, int ycells, gfx_element *gfx);
static void gfxset_handler(running_machine *machine, ui_gfx_state *state);

/* tilemap handling */
static void tilemap_handle_keys(running_machine *machine, ui_gfx_state *state, int viswidth, int visheight);
static void tilemap_update_bitmap(running_machine *machine, ui_gfx_state *state, int width, int height);
static void tilemap_handler(running_machine *machine, ui_gfx_state *state);



/***************************************************************************
    CORE IMPLEMENTATION
***************************************************************************/

/*-------------------------------------------------
    ui_gfx_init - initialize the menu system
-------------------------------------------------*/

void ui_gfx_init(running_machine *machine)
{
	ui_gfx_state *state = &ui_gfx;
	int gfx;

	/* make sure we clean up after ourselves */
	add_exit_callback(machine, ui_gfx_exit);

	/* initialize our global state */
	memset(state, 0, sizeof(*state));

	/* set up the palette state */
	state->palette.count = 16;

	/* set up the graphics state */
	for (gfx = 0; gfx < MAX_GFX_ELEMENTS; gfx++)
	{
		state->gfxset.rotate[gfx] = machine->gamedrv->flags & ORIENTATION_MASK;
		state->gfxset.count[gfx] = 16;
	}

	/* set up the tilemap state */
	state->tilemap.rotate = machine->gamedrv->flags & ORIENTATION_MASK;
}


/*-------------------------------------------------
    ui_gfx_exit - clean up after ourselves
-------------------------------------------------*/

static void ui_gfx_exit(running_machine *machine)
{
	/* free the texture */
	if (ui_gfx.texture != NULL)
		render_texture_free(ui_gfx.texture);
	ui_gfx.texture = NULL;

	/* free the bitmap */
	if (ui_gfx.bitmap != NULL)
		bitmap_free(ui_gfx.bitmap);
	ui_gfx.bitmap = NULL;
}


/*-------------------------------------------------
    ui_gfx_ui_handler - primary UI handler
-------------------------------------------------*/

UINT32 ui_gfx_ui_handler(running_machine *machine, UINT32 uistate)
{
	ui_gfx_state *state = &ui_gfx;

	/* if we have nothing, implicitly cancel */
	if (machine->config->total_colors == 0 && machine->colortable == NULL && machine->gfx[0] == NULL && tilemap_count(machine) == 0)
		goto cancel;

	/* if we're not paused, mark the bitmap dirty */
	if (!mame_is_paused(machine))
		state->bitmap_dirty = TRUE;

	/* switch off the state to display something */
again:
	switch (state->mode)
	{
		case 0:
			/* if we have a palette, display it */
			if (machine->config->total_colors > 0)
			{
				palette_handler(machine, state);
				break;
			}

			/* fall through...*/
			state->mode++;

		case 1:
			/* if we have graphics sets, display them */
			if (machine->gfx[0] != NULL)
			{
				gfxset_handler(machine, state);
				break;
			}

			/* fall through...*/
			state->mode++;

		case 2:
			/* if we have tilemaps, display them */
			if (tilemap_count(machine) > 0)
			{
				tilemap_handler(machine, state);
				break;
			}

			state->mode = 0;
			goto again;
	}

	/* handle keys */
	if (ui_input_pressed(machine, IPT_UI_SELECT))
	{
		state->mode = (state->mode + 1) % 3;
		state->bitmap_dirty = TRUE;
	}

	if (ui_input_pressed(machine, IPT_UI_PAUSE))
		mame_pause(machine, !mame_is_paused(machine));

	if (ui_input_pressed(machine, IPT_UI_CANCEL) || ui_input_pressed(machine, IPT_UI_SHOW_GFX))
		goto cancel;

	return uistate;

cancel:
	if (!uistate)
		mame_pause(machine, FALSE);
	state->bitmap_dirty = TRUE;
	return UI_HANDLER_CANCEL;
}



/***************************************************************************
    PALETTE VIEWER
***************************************************************************/

/*-------------------------------------------------
    palette_handler - handler for the palette
    viewer
-------------------------------------------------*/

static void palette_handler(running_machine *machine, ui_gfx_state *state)
{
	int total = state->palette.which ? colortable_palette_get_size(machine->colortable) : machine->config->total_colors;
	const char *title = state->palette.which ? "COLORTABLE" : "PALETTE";
	const rgb_t *raw_color = palette_entry_list_raw(machine->palette);
	render_font *ui_font = ui_get_font();
	float cellwidth, cellheight;
	float chwidth, chheight;
	float titlewidth;
	float x0, y0;
	render_bounds cellboxbounds;
	render_bounds boxbounds;
	int x, y, skip;

	/* add a half character padding for the box */
	chheight = ui_get_line_height();
	chwidth = render_font_get_char_width(ui_font, chheight, render_get_ui_aspect(), '0');
	boxbounds.x0 = 0.0f + 0.5f * chwidth;
	boxbounds.x1 = 1.0f - 0.5f * chwidth;
	boxbounds.y0 = 0.0f + 0.5f * chheight;
	boxbounds.y1 = 1.0f - 0.5f * chheight;

	/* the character cell box bounds starts a half character in from the box */
	cellboxbounds = boxbounds;
	cellboxbounds.x0 += 0.5f * chwidth;
	cellboxbounds.x1 -= 0.5f * chwidth;
	cellboxbounds.y0 += 0.5f * chheight;
	cellboxbounds.y1 -= 0.5f * chheight;

	/* add space on the left for 5 characters of text, plus a half character of padding */
	cellboxbounds.x0 += 5.5f * chwidth;

	/* add space on the top for a title, a half line of padding, a header, and another half line */
	cellboxbounds.y0 += 3.0f * chheight;

	/* figure out the title and expand the outer box to fit */
	titlewidth = render_font_get_string_width(ui_font, chheight, render_get_ui_aspect(), title);
	x0 = 0.0f;
	if (boxbounds.x1 - boxbounds.x0 < titlewidth + chwidth)
		x0 = boxbounds.x0 - (0.5f - 0.5f * (titlewidth + chwidth));

	/* go ahead and draw the outer box now */
	ui_draw_outlined_box(boxbounds.x0 - x0, boxbounds.y0, boxbounds.x1 + x0, boxbounds.y1, UI_GFXVIEWER_BG_COLOR);

	/* draw the title */
	x0 = 0.5f - 0.5f * titlewidth;
	y0 = boxbounds.y0 + 0.5f * chheight;
	for (x = 0; title[x] != 0; x++)
	{
		render_ui_add_char(x0, y0, chheight, render_get_ui_aspect(), ARGB_WHITE, ui_font, title[x]);
		x0 += render_font_get_char_width(ui_font, chheight, render_get_ui_aspect(), title[x]);
	}

	/* compute the cell size */
	cellwidth = (cellboxbounds.x1 - cellboxbounds.x0) / (float)state->palette.count;
	cellheight = (cellboxbounds.y1 - cellboxbounds.y0) / (float)state->palette.count;

	/* draw the top column headers */
	skip = (int)(chwidth / cellwidth);
	for (x = 0; x < state->palette.count; x += 1 + skip)
	{
		x0 = boxbounds.x0 + 6.0f * chwidth + (float)x * cellwidth;
		y0 = boxbounds.y0 + 2.0f * chheight;
		render_ui_add_char(x0 + 0.5f * (cellwidth - chwidth), y0, chheight, render_get_ui_aspect(), ARGB_WHITE, ui_font, "0123456789ABCDEF"[x & 0xf]);

		/* if we're skipping, draw a point between the character and the box to indicate which */
		/* one it's referring to */
		if (skip != 0)
			render_ui_add_point(x0 + 0.5f * cellwidth, 0.5f * (y0 + chheight + cellboxbounds.y0), UI_LINE_WIDTH, ARGB_WHITE, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA));
	}

	/* draw the side column headers */
	skip = (int)(chheight / cellheight);
	for (y = 0; y < state->palette.count; y += 1 + skip)

		/* only display if there is data to show */
		if (state->palette.offset + y * state->palette.count < total)
		{
			char buffer[10];

			/* if we're skipping, draw a point between the character and the box to indicate which */
			/* one it's referring to */
			x0 = boxbounds.x0 + 5.5f * chwidth;
			y0 = boxbounds.y0 + 3.5f * chheight + (float)y * cellheight;
			if (skip != 0)
				render_ui_add_point(0.5f * (x0 + cellboxbounds.x0), y0 + 0.5f * cellheight, UI_LINE_WIDTH, ARGB_WHITE, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA));

			/* draw the row header */
			sprintf(buffer, "%5X", state->palette.offset + y * state->palette.count);
			for (x = 4; x >= 0; x--)
			{
				x0 -= render_font_get_char_width(ui_font, chheight, render_get_ui_aspect(), buffer[x]);
				render_ui_add_char(x0, y0 + 0.5f * (cellheight - chheight), chheight, render_get_ui_aspect(), ARGB_WHITE, ui_font, buffer[x]);
			}
		}

	/* now add the rectangles for the colors */
	for (y = 0; y < state->palette.count; y++)
		for (x = 0; x < state->palette.count; x++)
		{
			int index = state->palette.offset + y * state->palette.count + x;
			if (index < total)
			{
				pen_t pen = state->palette.which ? colortable_palette_get_color(machine->colortable, index) : raw_color[index];
				render_ui_add_rect(cellboxbounds.x0 + x * cellwidth, cellboxbounds.y0 + y * cellheight,
									cellboxbounds.x0 + (x + 1) * cellwidth, cellboxbounds.y0 + (y + 1) * cellheight,
									0xff000000 | pen, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA));
			}
		}

	/* handle keys */
	palette_handle_keys(machine, state);
}


/*-------------------------------------------------
    palette_handler - handle key inputs for the
    palette viewer
-------------------------------------------------*/

static void palette_handle_keys(running_machine *machine, ui_gfx_state *state)
{
	int rowcount, screencount;
	int total;

	/* handle zoom (minus,plus) */
	if (ui_input_pressed(machine, IPT_UI_ZOOM_OUT))
		state->palette.count /= 2;
	if (ui_input_pressed(machine, IPT_UI_ZOOM_IN))
		state->palette.count *= 2;

	/* clamp within range */
	if (state->palette.count <= 4)
		state->palette.count = 4;
	if (state->palette.count > 64)
		state->palette.count = 64;

	/* handle colormap selection (open bracket,close bracket) */
	if (ui_input_pressed(machine, IPT_UI_PREV_GROUP))
		state->palette.which--;
	if (ui_input_pressed(machine, IPT_UI_NEXT_GROUP))
		state->palette.which++;

	/* clamp within range */
	if (state->palette.which < 0)
		state->palette.which = 1;
	if (state->palette.which > (int)(machine->colortable != NULL))
		state->palette.which = (int)(machine->colortable != NULL);

	/* cache some info in locals */
	total = state->palette.which ? colortable_palette_get_size(machine->colortable) : machine->config->total_colors;

	/* determine number of entries per row and total */
	rowcount = state->palette.count;
	screencount = rowcount * rowcount;

	/* handle keyboard navigation */
	if (ui_input_pressed_repeat(machine, IPT_UI_UP, 4))
		state->palette.offset -= rowcount;
	if (ui_input_pressed_repeat(machine, IPT_UI_DOWN, 4))
		state->palette.offset += rowcount;
	if (ui_input_pressed_repeat(machine, IPT_UI_PAGE_UP, 6))
		state->palette.offset -= screencount;
	if (ui_input_pressed_repeat(machine, IPT_UI_PAGE_DOWN, 6))
		state->palette.offset += screencount;
	if (ui_input_pressed_repeat(machine, IPT_UI_HOME, 4))
		state->palette.offset = 0;
	if (ui_input_pressed_repeat(machine, IPT_UI_END, 4))
		state->palette.offset = total;

	/* clamp within range */
	if (state->palette.offset + screencount > ((total + rowcount - 1) / rowcount) * rowcount)
		state->palette.offset = ((total + rowcount - 1) / rowcount) * rowcount - screencount;
	if (state->palette.offset < 0)
		state->palette.offset = 0;
}



/***************************************************************************
    GRAPHICS VIEWER
***************************************************************************/

/*-------------------------------------------------
    gfxset_handler - handler for the graphics
    viewer
-------------------------------------------------*/

static void gfxset_handler(running_machine *machine, ui_gfx_state *state)
{
	render_font *ui_font = ui_get_font();
	int set = state->gfxset.set;
	gfx_element *gfx = machine->gfx[set];
	float fullwidth, fullheight;
	float cellwidth, cellheight;
	float chwidth, chheight;
	float titlewidth;
	//float cellaspect;
	float x0, y0;
	render_bounds cellboxbounds;
	render_bounds boxbounds;
	int cellboxwidth, cellboxheight;
	int targwidth, targheight;
	int cellxpix, cellypix;
	int xcells, ycells;
	int pixelscale = 0;
	int x, y, skip;
	char title[100];

	/* get the bounds of our target */
	render_target_get_bounds(render_get_ui_target(), &targwidth, &targheight, NULL);

	/* add a half character padding for the box */
	chheight = ui_get_line_height();
	chwidth = render_font_get_char_width(ui_font, chheight, render_get_ui_aspect(), '0');
	boxbounds.x0 = 0.0f + 0.5f * chwidth;
	boxbounds.x1 = 1.0f - 0.5f * chwidth;
	boxbounds.y0 = 0.0f + 0.5f * chheight;
	boxbounds.y1 = 1.0f - 0.5f * chheight;

	/* the character cell box bounds starts a half character in from the box */
	cellboxbounds = boxbounds;
	cellboxbounds.x0 += 0.5f * chwidth;
	cellboxbounds.x1 -= 0.5f * chwidth;
	cellboxbounds.y0 += 0.5f * chheight;
	cellboxbounds.y1 -= 0.5f * chheight;

	/* add space on the left for 5 characters of text, plus a half character of padding */
	cellboxbounds.x0 += 5.5f * chwidth;

	/* add space on the top for a title, a half line of padding, a header, and another half line */
	cellboxbounds.y0 += 3.0f * chheight;

	/* convert back to pixels */
	cellboxwidth = (cellboxbounds.x1 - cellboxbounds.x0) * (float)targwidth;
	cellboxheight = (cellboxbounds.y1 - cellboxbounds.y0) * (float)targheight;

	/* compute the number of source pixels in a cell */
	cellxpix = 1 + ((state->gfxset.rotate[state->gfxset.set] & ORIENTATION_SWAP_XY) ? gfx->height : gfx->width);
	cellypix = 1 + ((state->gfxset.rotate[state->gfxset.set] & ORIENTATION_SWAP_XY) ? gfx->width : gfx->height);

	/* compute the largest pixel scale factor that still fits */
	xcells = state->gfxset.count[set];
	while (xcells > 1)
	{
		pixelscale = (cellboxwidth / xcells) / cellxpix;
		if (pixelscale != 0)
			break;
		xcells--;
	}

	/* worst case, we need a pixel scale of 1 */
	pixelscale = MAX(1, pixelscale);

	/* in the Y direction, we just display as many as we can */
	ycells = cellboxheight / (pixelscale * cellypix);

	/* now determine the actual cellbox size */
	cellboxwidth = MIN(cellboxwidth, xcells * pixelscale * cellxpix);
	cellboxheight = MIN(cellboxheight, ycells * pixelscale * cellypix);

	/* compute the size of a single cell at this pixel scale factor, as well as the aspect ratio */
	cellwidth = (cellboxwidth / (float)xcells) / (float)targwidth;
	cellheight = (cellboxheight / (float)ycells) / (float)targheight;
	//cellaspect = cellwidth / cellheight;

	/* working from the new width/height, recompute the boxbounds */
	fullwidth = (float)cellboxwidth / (float)targwidth + 6.5f * chwidth;
	fullheight = (float)cellboxheight / (float)targheight + 4.0f * chheight;

	/* recompute boxbounds from this */
	boxbounds.x0 = (1.0f - fullwidth) * 0.5f;
	boxbounds.x1 = boxbounds.x0 + fullwidth;
	boxbounds.y0 = (1.0f - fullheight) * 0.5f;
	boxbounds.y1 = boxbounds.y0 + fullheight;

	/* figure out the title and expand the outer box to fit */
	for (x = 0; x < MAX_GFX_ELEMENTS && machine->gfx[x] != NULL; x++) ;
	sprintf(title, "GFX %d/%d %dx%d COLOR %X", state->gfxset.set, x - 1, gfx->width, gfx->height, state->gfxset.color[set]);
	titlewidth = render_font_get_string_width(ui_font, chheight, render_get_ui_aspect(), title);
	x0 = 0.0f;
	if (boxbounds.x1 - boxbounds.x0 < titlewidth + chwidth)
		x0 = boxbounds.x0 - (0.5f - 0.5f * (titlewidth + chwidth));

	/* go ahead and draw the outer box now */
	ui_draw_outlined_box(boxbounds.x0 - x0, boxbounds.y0, boxbounds.x1 + x0, boxbounds.y1, UI_GFXVIEWER_BG_COLOR);

	/* draw the title */
	x0 = 0.5f - 0.5f * titlewidth;
	y0 = boxbounds.y0 + 0.5f * chheight;
	for (x = 0; title[x] != 0; x++)
	{
		render_ui_add_char(x0, y0, chheight, render_get_ui_aspect(), ARGB_WHITE, ui_font, title[x]);
		x0 += render_font_get_char_width(ui_font, chheight, render_get_ui_aspect(), title[x]);
	}

	/* draw the top column headers */
	skip = (int)(chwidth / cellwidth);
	for (x = 0; x < xcells; x += 1 + skip)
	{
		x0 = boxbounds.x0 + 6.0f * chwidth + (float)x * cellwidth;
		y0 = boxbounds.y0 + 2.0f * chheight;
		render_ui_add_char(x0 + 0.5f * (cellwidth - chwidth), y0, chheight, render_get_ui_aspect(), ARGB_WHITE, ui_font, "0123456789ABCDEF"[x & 0xf]);

		/* if we're skipping, draw a point between the character and the box to indicate which */
		/* one it's referring to */
		if (skip != 0)
			render_ui_add_point(x0 + 0.5f * cellwidth, 0.5f * (y0 + chheight + boxbounds.y0 + 3.5f * chheight), UI_LINE_WIDTH, ARGB_WHITE, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA));
	}

	/* draw the side column headers */
	skip = (int)(chheight / cellheight);
	for (y = 0; y < ycells; y += 1 + skip)

		/* only display if there is data to show */
		if (state->gfxset.offset[set] + y * xcells < gfx->total_elements)
		{
			char buffer[10];

			/* if we're skipping, draw a point between the character and the box to indicate which */
			/* one it's referring to */
			x0 = boxbounds.x0 + 5.5f * chwidth;
			y0 = boxbounds.y0 + 3.5f * chheight + (float)y * cellheight;
			if (skip != 0)
				render_ui_add_point(0.5f * (x0 + boxbounds.x0 + 6.0f * chwidth), y0 + 0.5f * cellheight, UI_LINE_WIDTH, ARGB_WHITE, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA));

			/* draw the row header */
			sprintf(buffer, "%5X", state->gfxset.offset[set] + y * xcells);
			for (x = 4; x >= 0; x--)
			{
				x0 -= render_font_get_char_width(ui_font, chheight, render_get_ui_aspect(), buffer[x]);
				render_ui_add_char(x0, y0 + 0.5f * (cellheight - chheight), chheight, render_get_ui_aspect(), ARGB_WHITE, ui_font, buffer[x]);
			}
		}

	/* update the bitmap */
	gfxset_update_bitmap(machine, state, xcells, ycells, gfx);

	/* add the final quad */
	render_ui_add_quad(boxbounds.x0 + 6.0f * chwidth, boxbounds.y0 + 3.5f * chheight,
						boxbounds.x0 + 6.0f * chwidth + (float)cellboxwidth / (float)targwidth,
						boxbounds.y0 + 3.5f * chheight + (float)cellboxheight / (float)targheight,
						ARGB_WHITE, state->texture, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA));

	/* handle keyboard navigation before drawing */
	gfxset_handle_keys(machine, state, xcells, ycells);
}


/*-------------------------------------------------
    gfxset_handle_keys - handle keys for the
    graphics viewer
-------------------------------------------------*/

static void gfxset_handle_keys(running_machine *machine, ui_gfx_state *state, int xcells, int ycells)
{
	ui_gfx_state oldstate = *state;
	gfx_element *gfx;
	int temp, set;

	/* handle gfxset selection (open bracket,close bracket) */
	if (ui_input_pressed(machine, IPT_UI_PREV_GROUP))
	{
		for (temp = state->gfxset.set - 1; temp >= 0; temp--)
			if (machine->gfx[temp] != NULL)
				break;
		if (temp >= 0)
			state->gfxset.set = temp;
	}
	if (ui_input_pressed(machine, IPT_UI_NEXT_GROUP))
	{
		for (temp = state->gfxset.set + 1; temp < MAX_GFX_ELEMENTS; temp++)
			if (machine->gfx[temp] != NULL)
				break;
		if (temp < MAX_GFX_ELEMENTS)
			state->gfxset.set = temp;
	}

	/* cache some info in locals */
	set = state->gfxset.set;
	gfx = machine->gfx[set];

	/* handle cells per line (minus,plus) */
	if (ui_input_pressed(machine, IPT_UI_ZOOM_OUT))
		state->gfxset.count[set] = xcells - 1;
	if (ui_input_pressed(machine, IPT_UI_ZOOM_IN))
		state->gfxset.count[set] = xcells + 1;

	/* clamp within range */
	if (state->gfxset.count[set] < 2)
		state->gfxset.count[set] = 2;
	if (state->gfxset.count[set] > 32)
		state->gfxset.count[set] = 32;

	/* handle rotation (R) */
	if (ui_input_pressed(machine, IPT_UI_ROTATE))
		state->gfxset.rotate[set] = orientation_add(ROT90, state->gfxset.rotate[set]);

	/* handle navigation within the cells (up,down,pgup,pgdown) */
	if (ui_input_pressed_repeat(machine, IPT_UI_UP, 4))
		state->gfxset.offset[set] -= xcells;
	if (ui_input_pressed_repeat(machine, IPT_UI_DOWN, 4))
		state->gfxset.offset[set] += xcells;
	if (ui_input_pressed_repeat(machine, IPT_UI_PAGE_UP, 6))
		state->gfxset.offset[set] -= xcells * ycells;
	if (ui_input_pressed_repeat(machine, IPT_UI_PAGE_DOWN, 6))
		state->gfxset.offset[set] += xcells * ycells;
	if (ui_input_pressed_repeat(machine, IPT_UI_HOME, 4))
		state->gfxset.offset[set] = 0;
	if (ui_input_pressed_repeat(machine, IPT_UI_END, 4))
		state->gfxset.offset[set] = gfx->total_elements;

	/* clamp within range */
	if (state->gfxset.offset[set] + xcells * ycells > ((gfx->total_elements + xcells - 1) / xcells) * xcells)
		state->gfxset.offset[set] = ((gfx->total_elements + xcells - 1) / xcells) * xcells - xcells * ycells;
	if (state->gfxset.offset[set] < 0)
		state->gfxset.offset[set] = 0;

	/* handle color selection (left,right) */
	if (ui_input_pressed_repeat(machine, IPT_UI_LEFT, 4))
		state->gfxset.color[set] -= 1;
	if (ui_input_pressed_repeat(machine, IPT_UI_RIGHT, 4))
		state->gfxset.color[set] += 1;

	/* clamp within range */
	if (state->gfxset.color[set] >= (int)gfx->total_colors)
		state->gfxset.color[set] = gfx->total_colors - 1;
	if (state->gfxset.color[set] < 0)
		state->gfxset.color[set] = 0;

	/* if something changed, we need to force an update to the bitmap */
	if (state->gfxset.set != oldstate.gfxset.set ||
		state->gfxset.offset[set] != oldstate.gfxset.offset[set] ||
		state->gfxset.rotate[set] != oldstate.gfxset.rotate[set] ||
		state->gfxset.color[set] != oldstate.gfxset.color[set] ||
		state->gfxset.count[set] != oldstate.gfxset.count[set])
	{
		state->bitmap_dirty = TRUE;
	}
}


/*-------------------------------------------------
    gfxset_update_bitmap - redraw the current
    graphics view bitmap
-------------------------------------------------*/

static void gfxset_update_bitmap(running_machine *machine, ui_gfx_state *state, int xcells, int ycells, gfx_element *gfx)
{
	int set = state->gfxset.set;
	int cellxpix, cellypix;
	int x, y;

	/* compute the number of source pixels in a cell */
	cellxpix = 1 + ((state->gfxset.rotate[set] & ORIENTATION_SWAP_XY) ? gfx->height : gfx->width);
	cellypix = 1 + ((state->gfxset.rotate[set] & ORIENTATION_SWAP_XY) ? gfx->width : gfx->height);

	/* realloc the bitmap if it is too small */
	if (state->bitmap == NULL || state->texture == NULL || state->bitmap->bpp != 32 || state->bitmap->width != cellxpix * xcells || state->bitmap->height != cellypix * ycells)
	{
		/* free the old stuff */
		if (state->texture != NULL)
			render_texture_free(state->texture);
		if (state->bitmap != NULL)
			bitmap_free(state->bitmap);

		/* allocate new stuff */
		state->bitmap = bitmap_alloc(cellxpix * xcells, cellypix * ycells, BITMAP_FORMAT_ARGB32);
		state->texture = render_texture_alloc(NULL, NULL);
		render_texture_set_bitmap(state->texture, state->bitmap, NULL, TEXFORMAT_ARGB32, NULL);

		/* force a redraw */
		state->bitmap_dirty = TRUE;
	}

	/* handle the redraw */
	if (state->bitmap_dirty)
	{
		/* loop over rows */
		for (y = 0; y < ycells; y++)
		{
			rectangle cellbounds;

			/* make a rect that covers this row */
			cellbounds.min_x = 0;
			cellbounds.max_x = state->bitmap->width - 1;
			cellbounds.min_y = y * cellypix;
			cellbounds.max_y = (y + 1) * cellypix - 1;

			/* only display if there is data to show */
			if (state->gfxset.offset[set] + y * xcells < gfx->total_elements)
			{
				/* draw the individual cells */
				for (x = 0; x < xcells; x++)
				{
					int index = state->gfxset.offset[set] + y * xcells + x;

					/* update the bounds for this cell */
					cellbounds.min_x = x * cellxpix;
					cellbounds.max_x = (x + 1) * cellxpix - 1;

					/* only render if there is data */
					if (index < gfx->total_elements)
						gfxset_draw_item(machine, gfx, index, state->bitmap, cellbounds.min_x, cellbounds.min_y, state->gfxset.color[set], state->gfxset.rotate[set]);

					/* otherwise, fill with transparency */
					else
						bitmap_fill(state->bitmap, &cellbounds, 0);
				}
			}

			/* otherwise, fill with transparency */
			else
				bitmap_fill(state->bitmap, &cellbounds, 0);
		}

		/* reset the texture to force an update */
		render_texture_set_bitmap(state->texture, state->bitmap, NULL, TEXFORMAT_ARGB32, NULL);
		state->bitmap_dirty = FALSE;
	}
}


/*-------------------------------------------------
    gfxset_draw_item - draw a single item into
    the view
-------------------------------------------------*/

static void gfxset_draw_item(running_machine *machine, const gfx_element *gfx, int index, bitmap_t *bitmap, int dstx, int dsty, int color, int rotate)
{
	static const pen_t default_palette[] =
	{
		MAKE_RGB(0,0,0), MAKE_RGB(0,0,255), MAKE_RGB(0,255,0), MAKE_RGB(0,255,255),
		MAKE_RGB(255,0,0), MAKE_RGB(255,0,255), MAKE_RGB(255,255,0), MAKE_RGB(255,255,255)
	};
	int width = (rotate & ORIENTATION_SWAP_XY) ? gfx->height : gfx->width;
	int height = (rotate & ORIENTATION_SWAP_XY) ? gfx->width : gfx->height;
	const rgb_t *palette = (machine->config->total_colors != 0) ? palette_entry_list_raw(machine->palette) : NULL;
	UINT32 rowpixels = bitmap->rowpixels;
	UINT32 palette_mask = ~0;
	int x, y;

	if (palette != NULL)
		palette += gfx->color_base + color * gfx->color_granularity;
	else
	{
		palette = default_palette;
		palette_mask = 7;
	}

	/* loop over rows in the cell */
	for (y = 0; y < height; y++)
	{
		UINT32 *dest = (UINT32 *)bitmap->base + (dsty + y) * rowpixels + dstx;
		const UINT8 *src = gfx_element_get_data(gfx, index);

		/* loop over columns in the cell */
		for (x = 0; x < width; x++)
		{
			int effx = x, effy = y;
			rgb_t pixel;
			const UINT8 *s;

			/* compute effective x,y values after rotation */
			if (!(rotate & ORIENTATION_SWAP_XY))
			{
				if (rotate & ORIENTATION_FLIP_X)
					effx = gfx->width - 1 - effx;
				if (rotate & ORIENTATION_FLIP_Y)
					effy = gfx->height - 1 - effy;
			}
			else
			{
				int temp;
				if (rotate & ORIENTATION_FLIP_X)
					effx = gfx->height - 1 - effx;
				if (rotate & ORIENTATION_FLIP_Y)
					effy = gfx->width - 1 - effy;
				temp = effx; effx = effy; effy = temp;
			}

			/* get a pointer to the start of this source row */
			s = src + effy * gfx->line_modulo;

			/* extract the pixel */
			if (gfx->flags & GFX_ELEMENT_PACKED)
				pixel = (s[effx/2] >> ((effx & 1) * 4)) & 0xf;
			else
				pixel = s[effx];
			*dest++ = 0xff000000 | palette[pixel & palette_mask];
		}
	}
}



/***************************************************************************
    TILEMAP VIEWER
***************************************************************************/

/*-------------------------------------------------
    tilemap_handler - handler for the tilemap
    viewer
-------------------------------------------------*/

static void tilemap_handler(running_machine *machine, ui_gfx_state *state)
{
	render_font *ui_font = ui_get_font();
	float chwidth, chheight;
	render_bounds mapboxbounds;
	render_bounds boxbounds;
	int targwidth, targheight;
	float titlewidth;
	float x0, y0;
	int mapboxwidth, mapboxheight;
	int maxxscale, maxyscale;
	UINT32 mapwidth, mapheight;
	int x, pixelscale;
	char title[100];

	/* get the bounds of our target */
	render_target_get_bounds(render_get_ui_target(), &targwidth, &targheight, NULL);

	/* get the size of the tilemap itself */
	tilemap_size_by_index(machine, state->tilemap.which, &mapwidth, &mapheight);
	if (state->tilemap.rotate & ORIENTATION_SWAP_XY)
		{ UINT32 temp = mapwidth; mapwidth = mapheight; mapheight = temp; }

	/* add a half character padding for the box */
	chheight = ui_get_line_height();
	chwidth = render_font_get_char_width(ui_font, chheight, render_get_ui_aspect(), '0');
	boxbounds.x0 = 0.0f + 0.5f * chwidth;
	boxbounds.x1 = 1.0f - 0.5f * chwidth;
	boxbounds.y0 = 0.0f + 0.5f * chheight;
	boxbounds.y1 = 1.0f - 0.5f * chheight;

	/* the tilemap box bounds starts a half character in from the box */
	mapboxbounds = boxbounds;
	mapboxbounds.x0 += 0.5f * chwidth;
	mapboxbounds.x1 -= 0.5f * chwidth;
	mapboxbounds.y0 += 0.5f * chheight;
	mapboxbounds.y1 -= 0.5f * chheight;

	/* add space on the top for a title and a half line of padding */
	mapboxbounds.y0 += 1.5f * chheight;

	/* convert back to pixels */
	mapboxwidth = (mapboxbounds.x1 - mapboxbounds.x0) * (float)targwidth;
	mapboxheight = (mapboxbounds.y1 - mapboxbounds.y0) * (float)targheight;

	/* determine the maximum integral scaling factor */
	pixelscale = state->tilemap.zoom;
	if (pixelscale == 0)
	{
		for (maxxscale = 1; mapwidth * (maxxscale + 1) < mapboxwidth; maxxscale++) ;
		for (maxyscale = 1; mapheight * (maxyscale + 1) < mapboxheight; maxyscale++) ;
		pixelscale = MIN(maxxscale, maxyscale);
	}

	/* recompute the final box size */
	mapboxwidth = MIN(mapboxwidth, mapwidth * pixelscale);
	mapboxheight = MIN(mapboxheight, mapheight * pixelscale);

	/* recompute the bounds, centered within the existing bounds */
	mapboxbounds.x0 += 0.5f * ((mapboxbounds.x1 - mapboxbounds.x0) - (float)mapboxwidth / (float)targwidth);
	mapboxbounds.x1 = mapboxbounds.x0 + (float)mapboxwidth / (float)targwidth;
	mapboxbounds.y0 += 0.5f * ((mapboxbounds.y1 - mapboxbounds.y0) - (float)mapboxheight / (float)targheight);
	mapboxbounds.y1 = mapboxbounds.y0 + (float)mapboxheight / (float)targheight;

	/* now recompute the outer box against this new info */
	boxbounds.x0 = mapboxbounds.x0 - 0.5f * chwidth;
	boxbounds.x1 = mapboxbounds.x1 + 0.5f * chwidth;
	boxbounds.y0 = mapboxbounds.y0 - 2.0f * chheight;
	boxbounds.y1 = mapboxbounds.y1 + 0.5f * chheight;

	/* figure out the title and expand the outer box to fit */
	sprintf(title, "TMAP %d/%d %dx%d OFFS %d,%d", state->tilemap.which, tilemap_count(machine) - 1, mapwidth, mapheight, state->tilemap.xoffs, state->tilemap.yoffs);
	titlewidth = render_font_get_string_width(ui_font, chheight, render_get_ui_aspect(), title);
	if (boxbounds.x1 - boxbounds.x0 < titlewidth + chwidth)
	{
		boxbounds.x0 = 0.5f - 0.5f * (titlewidth + chwidth);
		boxbounds.x1 = boxbounds.x0 + titlewidth + chwidth;
	}

	/* go ahead and draw the outer box now */
	ui_draw_outlined_box(boxbounds.x0, boxbounds.y0, boxbounds.x1, boxbounds.y1, UI_GFXVIEWER_BG_COLOR);

	/* draw the title */
	x0 = 0.5f - 0.5f * titlewidth;
	y0 = boxbounds.y0 + 0.5f * chheight;
	for (x = 0; title[x] != 0; x++)
	{
		render_ui_add_char(x0, y0, chheight, render_get_ui_aspect(), ARGB_WHITE, ui_font, title[x]);
		x0 += render_font_get_char_width(ui_font, chheight, render_get_ui_aspect(), title[x]);
	}

	/* update the bitmap */
	tilemap_update_bitmap(machine, state, mapboxwidth / pixelscale, mapboxheight / pixelscale);

	/* add the final quad */
	render_ui_add_quad(mapboxbounds.x0, mapboxbounds.y0,
						mapboxbounds.x1, mapboxbounds.y1,
						ARGB_WHITE, state->texture,
						PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXORIENT(state->tilemap.rotate));

	/* handle keyboard input */
	tilemap_handle_keys(machine, state, mapboxwidth, mapboxheight);
}


/*-------------------------------------------------
    tilemap_handle_keys - handle keys for the
    tilemap view
-------------------------------------------------*/

static void tilemap_handle_keys(running_machine *machine, ui_gfx_state *state, int viswidth, int visheight)
{
	ui_gfx_state oldstate = *state;
	UINT32 mapwidth, mapheight;
	int step;

	/* handle tilemap selection (open bracket,close bracket) */
	if (ui_input_pressed(machine, IPT_UI_PREV_GROUP))
		state->tilemap.which--;
	if (ui_input_pressed(machine, IPT_UI_NEXT_GROUP))
		state->tilemap.which++;

	/* clamp within range */
	if (state->tilemap.which < 0)
		state->tilemap.which = 0;
	if (state->tilemap.which >= tilemap_count(machine))
		state->tilemap.which = tilemap_count(machine) - 1;

	/* cache some info in locals */
	tilemap_size_by_index(machine, state->tilemap.which, &mapwidth, &mapheight);

	/* handle zoom (minus,plus) */
	if (ui_input_pressed(machine, IPT_UI_ZOOM_OUT))
		state->tilemap.zoom--;
	if (ui_input_pressed(machine, IPT_UI_ZOOM_IN))
		state->tilemap.zoom++;

	/* clamp within range */
	if (state->tilemap.zoom < 0)
		state->tilemap.zoom = 0;
	if (state->tilemap.zoom > 8)
		state->tilemap.zoom = 8;
	if (state->tilemap.zoom != oldstate.tilemap.zoom)
	{
		if (state->tilemap.zoom != 0)
			popmessage("Zoom = %d", state->tilemap.zoom);
		else
			popmessage("Zoom Auto");
	}

	/* handle rotation (R) */
	if (ui_input_pressed(machine, IPT_UI_ROTATE))
		state->tilemap.rotate = orientation_add(ROT90, state->tilemap.rotate);

	/* handle navigation (up,down,left,right) */
	step = 8;
	if (input_code_pressed(machine, KEYCODE_LSHIFT)) step = 1;
	if (input_code_pressed(machine, KEYCODE_LCONTROL)) step = 64;
	if (ui_input_pressed_repeat(machine, IPT_UI_UP, 4))
		state->tilemap.yoffs -= step;
	if (ui_input_pressed_repeat(machine, IPT_UI_DOWN, 4))
		state->tilemap.yoffs += step;
	if (ui_input_pressed_repeat(machine, IPT_UI_LEFT, 6))
		state->tilemap.xoffs -= step;
	if (ui_input_pressed_repeat(machine, IPT_UI_RIGHT, 6))
		state->tilemap.xoffs += step;

	/* clamp within range */
	while (state->tilemap.xoffs < 0)
		state->tilemap.xoffs += mapwidth;
	while (state->tilemap.xoffs >= mapwidth)
		state->tilemap.xoffs -= mapwidth;
	while (state->tilemap.yoffs < 0)
		state->tilemap.yoffs += mapheight;
	while (state->tilemap.yoffs >= mapheight)
		state->tilemap.yoffs -= mapheight;

	/* if something changed, we need to force an update to the bitmap */
	if (state->tilemap.which != oldstate.tilemap.which ||
		state->tilemap.xoffs != oldstate.tilemap.xoffs ||
		state->tilemap.yoffs != oldstate.tilemap.yoffs ||
		state->tilemap.rotate != oldstate.tilemap.rotate)
	{
		state->bitmap_dirty = TRUE;
	}
}


/*-------------------------------------------------
    tilemap_update_bitmap - update the bitmap
    for the tilemap view
-------------------------------------------------*/

static void tilemap_update_bitmap(running_machine *machine, ui_gfx_state *state, int width, int height)
{
	bitmap_format screen_format = video_screen_get_format(machine->primary_screen);
	palette_t *palette = NULL;
	int screen_texformat;

	/* convert the screen format to a texture format */
	switch (screen_format)
	{
		case BITMAP_FORMAT_INDEXED16:	screen_texformat = TEXFORMAT_PALETTE16;	palette = machine->palette;	break;
		case BITMAP_FORMAT_RGB15:		screen_texformat = TEXFORMAT_RGB15;		palette = NULL;				break;
		case BITMAP_FORMAT_RGB32:		screen_texformat = TEXFORMAT_RGB32;		palette = NULL;				break;
		default:						fatalerror("Invalid bitmap format!");								break;
	}

	/* swap the coordinates back if they were talking about a rotated surface */
	if (state->tilemap.rotate & ORIENTATION_SWAP_XY)
		{ UINT32 temp = width; width = height; height = temp; }

	/* realloc the bitmap if it is too small */
	if (state->bitmap == NULL || state->texture == NULL || state->bitmap->format != screen_format || state->bitmap->width != width || state->bitmap->height != height)
	{
		/* free the old stuff */
		if (state->texture != NULL)
			render_texture_free(state->texture);
		if (state->bitmap != NULL)
			bitmap_free(state->bitmap);

		/* allocate new stuff */
		state->bitmap = bitmap_alloc(width, height, screen_format);
		state->texture = render_texture_alloc(NULL, NULL);
		render_texture_set_bitmap(state->texture, state->bitmap, NULL, screen_texformat, palette);

		/* force a redraw */
		state->bitmap_dirty = TRUE;
	}

	/* handle the redraw */
	if (state->bitmap_dirty)
	{
		tilemap_draw_by_index(machine, state->bitmap, state->tilemap.which, state->tilemap.xoffs, state->tilemap.yoffs);

		/* reset the texture to force an update */
		render_texture_set_bitmap(state->texture, state->bitmap, NULL, screen_texformat, palette);
		state->bitmap_dirty = FALSE;
	}
}
