// license:BSD-3-Clause // copyright-holders:Nicola Salmoria, Aaron Giles, Nathan Woods /********************************************************************* ui/viewgfx.cpp Internal graphics viewer. *********************************************************************/ #include "emu.h" #include "ui/ui.h" #include "uiinput.h" #include "render.h" #include "rendfont.h" #include "rendutil.h" #include "ui/viewgfx.h" /*************************************************************************** CONSTANTS ***************************************************************************/ enum ui_gfx_modes { UI_GFX_PALETTE = 0, UI_GFX_GFXSET, UI_GFX_TILEMAP }; const int MAX_GFX_DECODERS = 8; /*************************************************************************** TYPE DEFINITIONS ***************************************************************************/ // information about a single gfx device struct ui_gfx_info { device_gfx_interface *interface; // pointer to device's gfx interface UINT8 setcount; // how many gfx sets device has UINT8 rotate[MAX_GFX_ELEMENTS]; // current rotation (orientation) value UINT8 columns[MAX_GFX_ELEMENTS]; // number of items per row int offset[MAX_GFX_ELEMENTS]; // current offset of top,left item int color[MAX_GFX_ELEMENTS]; // current color selected }; struct ui_gfx_state { bool started; // have we called ui_gfx_count_devices() yet? UINT8 mode; // which mode are we in? // intermediate bitmaps bool bitmap_dirty; // is the bitmap dirty? bitmap_rgb32 * bitmap; // bitmap for drawing gfx and tilemaps render_texture *texture; // texture for rendering the above bitmap // palette-specific data struct { palette_device *device; // pointer to current device int devcount; // how many palette devices exist int devindex; // which palette device is visible UINT8 which; // which subset (pens or indirect colors)? UINT8 columns; // number of items per row int offset; // current offset of top left item } palette; // graphics-specific data struct { UINT8 devcount; // how many gfx devices exist UINT8 devindex; // which device is visible UINT8 set; // which set is visible } gfxset; // information about each gfx device ui_gfx_info gfxdev[MAX_GFX_DECODERS]; // tilemap-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_count_devices(running_machine &machine, ui_gfx_state &state); static void ui_gfx_exit(running_machine &machine); // palette handling static void palette_set_device(running_machine &machine, ui_gfx_state &state); static void palette_handle_keys(running_machine &machine, ui_gfx_state &state); static void palette_handler(mame_ui_manager &mui, render_container *container, 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, gfx_element &gfx, int index, bitmap_rgb32 &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(mame_ui_manager &mui, render_container *container, 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(mame_ui_manager &mui, render_container *container, ui_gfx_state &state); /*************************************************************************** CORE IMPLEMENTATION ***************************************************************************/ //------------------------------------------------- // ui_gfx_init - initialize the graphics viewer //------------------------------------------------- void ui_gfx_init(running_machine &machine) { ui_gfx_state *state = &ui_gfx; int rotate = machine.system().flags & ORIENTATION_MASK; // make sure we clean up after ourselves machine.add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(FUNC(ui_gfx_exit), &machine)); // initialize our global state memset(state, 0, sizeof(*state)); // set up the palette state state->palette.columns = 16; // set up the graphics state for (int i = 0; i < MAX_GFX_DECODERS; i++) for (int j = 0; j < MAX_GFX_ELEMENTS; j++) { state->gfxdev[i].rotate[j] = rotate; state->gfxdev[i].columns[j] = 16; } // set up the tilemap state state->tilemap.rotate = rotate; } //------------------------------------------------- // ui_gfx_count_devices - count the palettes, // gfx decoders and gfx sets in the machine //------------------------------------------------- static void ui_gfx_count_devices(running_machine &machine, ui_gfx_state &state) { // count the palette devices state.palette.devcount = palette_device_iterator(machine.root_device()).count(); // set the pointer to the first palette if (state.palette.devcount > 0) palette_set_device(machine, state); // count the gfx devices state.gfxset.devcount = 0; for (device_gfx_interface &interface : gfx_interface_iterator(machine.root_device())) { // count the gfx sets in each device, skipping devices with none int count = 0; while (count < MAX_GFX_ELEMENTS && interface.gfx(count) != nullptr) count++; // count = index of first nullptr if (count > 0) { state.gfxdev[state.gfxset.devcount].interface = &interface; state.gfxdev[state.gfxset.devcount].setcount = count; if (++state.gfxset.devcount == MAX_GFX_DECODERS) break; } } state.started = true; } //------------------------------------------------- // ui_gfx_exit - clean up after ourselves //------------------------------------------------- static void ui_gfx_exit(running_machine &machine) { // free the texture machine.render().texture_free(ui_gfx.texture); ui_gfx.texture = nullptr; // free the bitmap global_free(ui_gfx.bitmap); ui_gfx.bitmap = nullptr; } //------------------------------------------------- // ui_gfx_is_relevant - returns 'true' if the // internal graphics viewer has relevance // // NOTE: this must not be called before machine // initialization is complete, as some drivers // create or modify gfx sets in VIDEO_START //------------------------------------------------- bool ui_gfx_is_relevant(running_machine &machine) { ui_gfx_state &state = ui_gfx; if (!state.started) ui_gfx_count_devices(machine, state); return state.palette.devcount > 0 || state.gfxset.devcount > 0 || machine.tilemap().count() > 0; } //------------------------------------------------- // ui_gfx_ui_handler - primary UI handler //------------------------------------------------- UINT32 ui_gfx_ui_handler(mame_ui_manager &mui, render_container *container, UINT32 uistate) { ui_gfx_state &state = ui_gfx; // if we have nothing, implicitly cancel if (!ui_gfx_is_relevant(mui.machine())) goto cancel; // if we're not paused, mark the bitmap dirty if (!mui.machine().paused()) state.bitmap_dirty = true; // switch off the state to display something again: switch (state.mode) { case UI_GFX_PALETTE: // if we have a palette, display it if (state.palette.devcount > 0) { palette_handler(mui, container, state); break; } // fall through... state.mode++; case UI_GFX_GFXSET: // if we have graphics sets, display them if (state.gfxset.devcount > 0) { gfxset_handler(mui, container, state); break; } // fall through... state.mode++; case UI_GFX_TILEMAP: // if we have tilemaps, display them if (mui.machine().tilemap().count() > 0) { tilemap_handler(mui, container, state); break; } state.mode = UI_GFX_PALETTE; goto again; } // handle keys if (mui.machine().ui_input().pressed(IPT_UI_SELECT)) { state.mode = (state.mode + 1) % 3; state.bitmap_dirty = true; } if (mui.machine().ui_input().pressed(IPT_UI_PAUSE)) { if (mui.machine().paused()) mui.machine().resume(); else mui.machine().pause(); } if (mui.machine().ui_input().pressed(IPT_UI_CANCEL) || mui.machine().ui_input().pressed(IPT_UI_SHOW_GFX)) goto cancel; return uistate; cancel: if (!uistate) mui.machine().resume(); state.bitmap_dirty = true; return UI_HANDLER_CANCEL; } /*************************************************************************** PALETTE VIEWER ***************************************************************************/ //------------------------------------------------- // palette_set_device - set the pointer to the // current palette device //------------------------------------------------- static void palette_set_device(running_machine &machine, ui_gfx_state &state) { palette_device_iterator pal_iter(machine.root_device()); state.palette.device = pal_iter.byindex(state.palette.devindex); } //------------------------------------------------- // palette_handler - handler for the palette // viewer //------------------------------------------------- static void palette_handler(mame_ui_manager &mui, render_container *container, ui_gfx_state &state) { palette_device *palette = state.palette.device; int total = state.palette.which ? palette->indirect_entries() : palette->entries(); const rgb_t *raw_color = palette->palette()->entry_list_raw(); render_font *ui_font = mui.get_font(); float cellwidth, cellheight; float chwidth, chheight; float titlewidth; float x0, y0; render_bounds cellboxbounds; render_bounds boxbounds; int x, y, skip; char title[100]; // add a half character padding for the box chheight = mui.get_line_height(); chwidth = ui_font->char_width(chheight, mui.machine().render().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 const char *suffix = palette->indirect_entries() == 0 ? "" : state.palette.which ? _(" COLORS") : _(" PENS"); sprintf(title, "'%s'%s", palette->tag(), suffix); titlewidth = ui_font->string_width(chheight, mui.machine().render().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 mui.draw_outlined_box(container, 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++) { container->add_char(x0, y0, chheight, mui.machine().render().ui_aspect(), rgb_t::white, *ui_font, title[x]); x0 += ui_font->char_width(chheight, mui.machine().render().ui_aspect(), title[x]); } // compute the cell size cellwidth = (cellboxbounds.x1 - cellboxbounds.x0) / (float)state.palette.columns; cellheight = (cellboxbounds.y1 - cellboxbounds.y0) / (float)state.palette.columns; // draw the top column headers skip = (int)(chwidth / cellwidth); for (x = 0; x < state.palette.columns; x += 1 + skip) { x0 = boxbounds.x0 + 6.0f * chwidth + (float)x * cellwidth; y0 = boxbounds.y0 + 2.0f * chheight; container->add_char(x0 + 0.5f * (cellwidth - chwidth), y0, chheight, mui.machine().render().ui_aspect(), rgb_t::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) container->add_point(x0 + 0.5f * cellwidth, 0.5f * (y0 + chheight + cellboxbounds.y0), UI_LINE_WIDTH, rgb_t::white, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } // draw the side column headers skip = (int)(chheight / cellheight); for (y = 0; y < state.palette.columns; y += 1 + skip) // only display if there is data to show if (state.palette.offset + y * state.palette.columns < 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) container->add_point(0.5f * (x0 + cellboxbounds.x0), y0 + 0.5f * cellheight, UI_LINE_WIDTH, rgb_t::white, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); // draw the row header sprintf(buffer, "%5X", state.palette.offset + y * state.palette.columns); for (x = 4; x >= 0; x--) { x0 -= ui_font->char_width(chheight, mui.machine().render().ui_aspect(), buffer[x]); container->add_char(x0, y0 + 0.5f * (cellheight - chheight), chheight, mui.machine().render().ui_aspect(), rgb_t::white, *ui_font, buffer[x]); } } // now add the rectangles for the colors for (y = 0; y < state.palette.columns; y++) for (x = 0; x < state.palette.columns; x++) { int index = state.palette.offset + y * state.palette.columns + x; if (index < total) { pen_t pen = state.palette.which ? palette->indirect_color(index) : raw_color[index]; container->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(mui.machine(), state); } //------------------------------------------------- // palette_handle_keys - handle key inputs for // the palette viewer //------------------------------------------------- static void palette_handle_keys(running_machine &machine, ui_gfx_state &state) { palette_device *palette = state.palette.device; int rowcount, screencount; int total; // handle zoom (minus,plus) if (machine.ui_input().pressed(IPT_UI_ZOOM_OUT)) state.palette.columns /= 2; if (machine.ui_input().pressed(IPT_UI_ZOOM_IN)) state.palette.columns *= 2; // clamp within range if (state.palette.columns <= 4) state.palette.columns = 4; if (state.palette.columns > 64) state.palette.columns = 64; // handle colormap selection (open bracket,close bracket) if (machine.ui_input().pressed(IPT_UI_PREV_GROUP)) { if (state.palette.which) state.palette.which = 0; else if (state.palette.devindex > 0) { state.palette.devindex--; palette_set_device(machine, state); palette = state.palette.device; state.palette.which = (palette->indirect_entries() > 0); } } if (machine.ui_input().pressed(IPT_UI_NEXT_GROUP)) { if (!state.palette.which && palette->indirect_entries() > 0) state.palette.which = 1; else if (state.palette.devindex < state.palette.devcount - 1) { state.palette.devindex++; palette_set_device(machine, state); palette = state.palette.device; state.palette.which = 0; } } // cache some info in locals total = state.palette.which ? palette->indirect_entries() : palette->entries(); // determine number of entries per row and total rowcount = state.palette.columns; screencount = rowcount * rowcount; // handle keyboard navigation if (machine.ui_input().pressed_repeat(IPT_UI_UP, 4)) state.palette.offset -= rowcount; if (machine.ui_input().pressed_repeat(IPT_UI_DOWN, 4)) state.palette.offset += rowcount; if (machine.ui_input().pressed_repeat(IPT_UI_PAGE_UP, 6)) state.palette.offset -= screencount; if (machine.ui_input().pressed_repeat(IPT_UI_PAGE_DOWN, 6)) state.palette.offset += screencount; if (machine.ui_input().pressed_repeat(IPT_UI_HOME, 4)) state.palette.offset = 0; if (machine.ui_input().pressed_repeat(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(mame_ui_manager &mui, render_container *container, ui_gfx_state &state) { render_font *ui_font = mui.get_font(); int dev = state.gfxset.devindex; int set = state.gfxset.set; ui_gfx_info &info = state.gfxdev[dev]; device_gfx_interface &interface = *info.interface; gfx_element &gfx = *interface.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 = mui.machine().render().ui_target().width(); int targheight = mui.machine().render().ui_target().height(); int cellxpix, cellypix; int xcells, ycells; int pixelscale = 0; int x, y, skip; char title[100]; // add a half character padding for the box chheight = mui.get_line_height(); chwidth = ui_font->char_width(chheight, mui.machine().render().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 + ((info.rotate[set] & ORIENTATION_SWAP_XY) ? gfx.height() : gfx.width()); cellypix = 1 + ((info.rotate[set] & ORIENTATION_SWAP_XY) ? gfx.width() : gfx.height()); // compute the largest pixel scale factor that still fits xcells = info.columns[set]; while (xcells > 1) { pixelscale = (cellboxwidth / xcells) / cellxpix; if (pixelscale != 0) break; xcells--; } info.columns[set] = 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 sprintf(title, "'%s' %d/%d %dx%d COLOR %X", interface.device().tag(), set, info.setcount - 1, gfx.width(), gfx.height(), info.color[set]); titlewidth = ui_font->string_width(chheight, mui.machine().render().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 mui.draw_outlined_box(container, 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++) { container->add_char(x0, y0, chheight, mui.machine().render().ui_aspect(), rgb_t::white, *ui_font, title[x]); x0 += ui_font->char_width(chheight, mui.machine().render().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; container->add_char(x0 + 0.5f * (cellwidth - chwidth), y0, chheight, mui.machine().render().ui_aspect(), rgb_t::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) container->add_point(x0 + 0.5f * cellwidth, 0.5f * (y0 + chheight + boxbounds.y0 + 3.5f * chheight), UI_LINE_WIDTH, rgb_t::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 (info.offset[set] + y * xcells < gfx.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) container->add_point(0.5f * (x0 + boxbounds.x0 + 6.0f * chwidth), y0 + 0.5f * cellheight, UI_LINE_WIDTH, rgb_t::white, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); // draw the row header sprintf(buffer, "%5X", info.offset[set] + y * xcells); for (x = 4; x >= 0; x--) { x0 -= ui_font->char_width(chheight, mui.machine().render().ui_aspect(), buffer[x]); container->add_char(x0, y0 + 0.5f * (cellheight - chheight), chheight, mui.machine().render().ui_aspect(), rgb_t::white, *ui_font, buffer[x]); } } // update the bitmap gfxset_update_bitmap(mui.machine(), state, xcells, ycells, gfx); // add the final quad container->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, rgb_t::white, state.texture, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); // handle keyboard navigation before drawing gfxset_handle_keys(mui.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) { // handle gfxset selection (open bracket,close bracket) if (machine.ui_input().pressed(IPT_UI_PREV_GROUP)) { if (state.gfxset.set > 0) state.gfxset.set--; else if (state.gfxset.devindex > 0) { state.gfxset.devindex--; state.gfxset.set = state.gfxdev[state.gfxset.devindex].setcount - 1; } state.bitmap_dirty = true; } if (machine.ui_input().pressed(IPT_UI_NEXT_GROUP)) { if (state.gfxset.set < state.gfxdev[state.gfxset.devindex].setcount - 1) state.gfxset.set++; else if (state.gfxset.devindex < state.gfxset.devcount - 1) { state.gfxset.devindex++; state.gfxset.set = 0; } state.bitmap_dirty = true; } // cache some info in locals int dev = state.gfxset.devindex; int set = state.gfxset.set; ui_gfx_info &info = state.gfxdev[dev]; gfx_element &gfx = *info.interface->gfx(set); // handle cells per line (minus,plus) if (machine.ui_input().pressed(IPT_UI_ZOOM_OUT)) { info.columns[set] = xcells - 1; state.bitmap_dirty = true; } if (machine.ui_input().pressed(IPT_UI_ZOOM_IN)) { info.columns[set] = xcells + 1; state.bitmap_dirty = true; } // clamp within range if (info.columns[set] < 2) { info.columns[set] = 2; state.bitmap_dirty = true; } if (info.columns[set] > 128) { info.columns[set] = 128; state.bitmap_dirty = true; } // handle rotation (R) if (machine.ui_input().pressed(IPT_UI_ROTATE)) { info.rotate[set] = orientation_add(ROT90, info.rotate[set]); state.bitmap_dirty = true; } // handle navigation within the cells (up,down,pgup,pgdown) if (machine.ui_input().pressed_repeat(IPT_UI_UP, 4)) { info.offset[set] -= xcells; state.bitmap_dirty = true; } if (machine.ui_input().pressed_repeat(IPT_UI_DOWN, 4)) { info.offset[set] += xcells; state.bitmap_dirty = true; } if (machine.ui_input().pressed_repeat(IPT_UI_PAGE_UP, 6)) { info.offset[set] -= xcells * ycells; state.bitmap_dirty = true; } if (machine.ui_input().pressed_repeat(IPT_UI_PAGE_DOWN, 6)) { info.offset[set] += xcells * ycells; state.bitmap_dirty = true; } if (machine.ui_input().pressed_repeat(IPT_UI_HOME, 4)) { info.offset[set] = 0; state.bitmap_dirty = true; } if (machine.ui_input().pressed_repeat(IPT_UI_END, 4)) { info.offset[set] = gfx.elements(); state.bitmap_dirty = true; } // clamp within range if (info.offset[set] + xcells * ycells > ((gfx.elements() + xcells - 1) / xcells) * xcells) { info.offset[set] = ((gfx.elements() + xcells - 1) / xcells) * xcells - xcells * ycells; state.bitmap_dirty = true; } if (info.offset[set] < 0) { info.offset[set] = 0; state.bitmap_dirty = true; } // handle color selection (left,right) if (machine.ui_input().pressed_repeat(IPT_UI_LEFT, 4)) { info.color[set] -= 1; state.bitmap_dirty = true; } if (machine.ui_input().pressed_repeat(IPT_UI_RIGHT, 4)) { info.color[set] += 1; state.bitmap_dirty = true; } // clamp within range if (info.color[set] >= (int)gfx.colors()) { info.color[set] = gfx.colors() - 1; state.bitmap_dirty = true; } if (info.color[set] < 0) { info.color[set] = 0; 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 dev = state.gfxset.devindex; int set = state.gfxset.set; ui_gfx_info &info = state.gfxdev[dev]; int cellxpix, cellypix; int x, y; // compute the number of source pixels in a cell cellxpix = 1 + ((info.rotate[set] & ORIENTATION_SWAP_XY) ? gfx.height() : gfx.width()); cellypix = 1 + ((info.rotate[set] & ORIENTATION_SWAP_XY) ? gfx.width() : gfx.height()); // realloc the bitmap if it is too small if (state.bitmap == nullptr || state.texture == nullptr || state.bitmap->bpp() != 32 || state.bitmap->width() != cellxpix * xcells || state.bitmap->height() != cellypix * ycells) { // free the old stuff machine.render().texture_free(state.texture); global_free(state.bitmap); // allocate new stuff state.bitmap = global_alloc(bitmap_rgb32(cellxpix * xcells, cellypix * ycells)); state.texture = machine.render().texture_alloc(); state.texture->set_bitmap(*state.bitmap, state.bitmap->cliprect(), TEXFORMAT_ARGB32); // 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.set(0, state.bitmap->width() - 1, y * cellypix, (y + 1) * cellypix - 1); // only display if there is data to show if (info.offset[set] + y * xcells < gfx.elements()) { // draw the individual cells for (x = 0; x < xcells; x++) { int index = info.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.elements()) gfxset_draw_item(machine, gfx, index, *state.bitmap, cellbounds.min_x, cellbounds.min_y, info.color[set], info.rotate[set]); // otherwise, fill with transparency else state.bitmap->fill(0, cellbounds); } } // otherwise, fill with transparency else state.bitmap->fill(0, cellbounds); } // reset the texture to force an update state.texture->set_bitmap(*state.bitmap, state.bitmap->cliprect(), TEXFORMAT_ARGB32); state.bitmap_dirty = false; } } //------------------------------------------------- // gfxset_draw_item - draw a single item into // the view //------------------------------------------------- static void gfxset_draw_item(running_machine &machine, gfx_element &gfx, int index, bitmap_rgb32 &bitmap, int dstx, int dsty, int color, int rotate) { int width = (rotate & ORIENTATION_SWAP_XY) ? gfx.height() : gfx.width(); int height = (rotate & ORIENTATION_SWAP_XY) ? gfx.width() : gfx.height(); const rgb_t *palette = gfx.palette().palette()->entry_list_raw() + gfx.colorbase() + color * gfx.granularity(); int x, y; // loop over rows in the cell for (y = 0; y < height; y++) { UINT32 *dest = &bitmap.pix32(dsty + y, dstx); const UINT8 *src = gfx.get_data(index); // loop over columns in the cell for (x = 0; x < width; x++) { int effx = x, effy = y; 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.rowbytes(); // extract the pixel *dest++ = 0xff000000 | palette[s[effx]]; } } } /*************************************************************************** TILEMAP VIEWER ***************************************************************************/ //------------------------------------------------- // tilemap_handler - handler for the tilemap // viewer //------------------------------------------------- static void tilemap_handler(mame_ui_manager &mui, render_container *container, ui_gfx_state &state) { render_font *ui_font = mui.get_font(); float chwidth, chheight; render_bounds mapboxbounds; render_bounds boxbounds; int targwidth = mui.machine().render().ui_target().width(); int targheight = mui.machine().render().ui_target().height(); float titlewidth; float x0, y0; int mapboxwidth, mapboxheight; int maxxscale, maxyscale; UINT32 mapwidth, mapheight; int x, pixelscale; char title[100]; // get the size of the tilemap itself tilemap_t *tilemap = mui.machine().tilemap().find(state.tilemap.which); mapwidth = tilemap->width(); mapheight = tilemap->height(); if (state.tilemap.rotate & ORIENTATION_SWAP_XY) { UINT32 temp = mapwidth; mapwidth = mapheight; mapheight = temp; } // add a half character padding for the box chheight = mui.get_line_height(); chwidth = ui_font->char_width(chheight, mui.machine().render().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, "TILEMAP %d/%d %dx%d OFFS %d,%d", state.tilemap.which, mui.machine().tilemap().count() - 1, mapwidth, mapheight, state.tilemap.xoffs, state.tilemap.yoffs); titlewidth = ui_font->string_width(chheight, mui.machine().render().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 mui.draw_outlined_box(container, 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++) { container->add_char(x0, y0, chheight, mui.machine().render().ui_aspect(), rgb_t::white, *ui_font, title[x]); x0 += ui_font->char_width(chheight, mui.machine().render().ui_aspect(), title[x]); } // update the bitmap tilemap_update_bitmap(mui.machine(), state, mapboxwidth / pixelscale, mapboxheight / pixelscale); // add the final quad container->add_quad(mapboxbounds.x0, mapboxbounds.y0, mapboxbounds.x1, mapboxbounds.y1, rgb_t::white, state.texture, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXORIENT(state.tilemap.rotate)); // handle keyboard input tilemap_handle_keys(mui.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) { UINT32 mapwidth, mapheight; int step; // handle tilemap selection (open bracket,close bracket) if (machine.ui_input().pressed(IPT_UI_PREV_GROUP) && state.tilemap.which > 0) { state.tilemap.which--; state.bitmap_dirty = true; } if (machine.ui_input().pressed(IPT_UI_NEXT_GROUP) && state.tilemap.which < machine.tilemap().count() - 1) { state.tilemap.which++; state.bitmap_dirty = true; } // cache some info in locals tilemap_t *tilemap = machine.tilemap().find(state.tilemap.which); mapwidth = tilemap->width(); mapheight = tilemap->height(); // handle zoom (minus,plus) if (machine.ui_input().pressed(IPT_UI_ZOOM_OUT) && state.tilemap.zoom > 0) { state.tilemap.zoom--; state.bitmap_dirty = true; if (state.tilemap.zoom != 0) machine.popmessage("Zoom = %d", state.tilemap.zoom); else machine.popmessage("Zoom Auto"); } if (machine.ui_input().pressed(IPT_UI_ZOOM_IN) && state.tilemap.zoom < 8) { state.tilemap.zoom++; state.bitmap_dirty = true; machine.popmessage("Zoom = %d", state.tilemap.zoom); } // handle rotation (R) if (machine.ui_input().pressed(IPT_UI_ROTATE)) { state.tilemap.rotate = orientation_add(ROT90, state.tilemap.rotate); state.bitmap_dirty = true; } // handle navigation (up,down,left,right) step = 8; if (machine.input().code_pressed(KEYCODE_LSHIFT)) step = 1; if (machine.input().code_pressed(KEYCODE_LCONTROL)) step = 64; if (machine.ui_input().pressed_repeat(IPT_UI_UP, 4)) { state.tilemap.yoffs -= step; state.bitmap_dirty = true; } if (machine.ui_input().pressed_repeat(IPT_UI_DOWN, 4)) { state.tilemap.yoffs += step; state.bitmap_dirty = true; } if (machine.ui_input().pressed_repeat(IPT_UI_LEFT, 6)) { state.tilemap.xoffs -= step; state.bitmap_dirty = true; } if (machine.ui_input().pressed_repeat(IPT_UI_RIGHT, 6)) { state.tilemap.xoffs += step; state.bitmap_dirty = true; } // 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; } //------------------------------------------------- // 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) { // 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 == nullptr || state.texture == nullptr || state.bitmap->width() != width || state.bitmap->height() != height) { // free the old stuff machine.render().texture_free(state.texture); global_free(state.bitmap); // allocate new stuff state.bitmap = global_alloc(bitmap_rgb32(width, height)); state.texture = machine.render().texture_alloc(); state.texture->set_bitmap(*state.bitmap, state.bitmap->cliprect(), TEXFORMAT_RGB32); // force a redraw state.bitmap_dirty = true; } // handle the redraw if (state.bitmap_dirty) { tilemap_t *tilemap = machine.tilemap().find(state.tilemap.which); tilemap->draw_debug(*machine.first_screen(), *state.bitmap, state.tilemap.xoffs, state.tilemap.yoffs); // reset the texture to force an update state.texture->set_bitmap(*state.bitmap, state.bitmap->cliprect(), TEXFORMAT_RGB32); state.bitmap_dirty = false; } }