// license:BSD-3-Clause // copyright-holders:Nicola Salmoria, Aaron Giles, Nathan Woods /********************************************************************* ui/viewgfx.cpp Internal graphics viewer. *********************************************************************/ #include "emu.h" #include "ui/viewgfx.h" #include "emupal.h" #include "render.h" #include "rendfont.h" #include "rendutil.h" #include "screen.h" #include "tilemap.h" #include "uiinput.h" #include "util/unicode.h" #include "osdepend.h" #include #include namespace { class gfx_viewer { public: gfx_viewer(running_machine &machine) : m_machine(machine), m_palette(machine), m_gfxset(machine), m_tilemap(machine) { } // copy constructor needed to make std::any happy gfx_viewer(gfx_viewer const &that) : gfx_viewer(that.m_machine) { } ~gfx_viewer() { if (m_texture) m_machine.render().texture_free(m_texture); } uint32_t handle(mame_ui_manager &mui, render_container &container, bool uistate) { // implicitly cancel if there's nothing to display if (!is_relevant()) return cancel(uistate); // let the OSD do its thing mui.machine().osd().check_osd_inputs(); // always mark the bitmap dirty if not paused if (!m_machine.paused()) m_bitmap_dirty = true; // handle pointer events to show hover info ui_event event; while (m_machine.ui_input().pop_event(&event)) { switch (event.event_type) { case ui_event::type::POINTER_UPDATE: { // ignore pointer input in windows other than the one that displays the UI render_target &target(m_machine.render().ui_target()); if (&target != event.target) break; // don't change if the current pointer has buttons pressed and this one doesn't if (event.pointer_id == m_current_pointer) { assert(m_pointer_type == event.pointer_type); m_pointer_buttons = event.pointer_buttons; m_pointer_inside = target.map_point_container( event.pointer_x, event.pointer_y, container, m_pointer_x, m_pointer_y); } else if ((0 > m_current_pointer) || (!m_pointer_buttons && (!m_pointer_inside || event.pointer_buttons))) { float x, y; bool const inside(target.map_point_container(event.pointer_x, event.pointer_y, container, x, y)); if ((0 > m_current_pointer) || event.pointer_buttons || (!m_pointer_inside && inside)) { m_current_pointer = event.pointer_id; m_pointer_type = event.pointer_type; m_pointer_buttons = event.pointer_buttons; m_pointer_x = x; m_pointer_y = y; m_pointer_inside = inside; } } } break; case ui_event::type::POINTER_LEAVE: case ui_event::type::POINTER_ABORT: { // if this was our pointer, we've lost it render_target &target(m_machine.render().ui_target()); if ((&target == event.target) && (event.pointer_id == m_current_pointer)) { // keep the pointer position and type so we can show touch locations after release m_current_pointer = -1; m_pointer_buttons = 0U; m_pointer_inside = target.map_point_container( event.pointer_x, event.pointer_y, container, m_pointer_x, m_pointer_y); } } break; // ignore anything that isn't pointer-related default: break; } } // always draw non-touch pointer mame_ui_manager::display_pointer pointers[1]{ { m_machine.render().ui_target(), m_pointer_type, m_pointer_x, m_pointer_y } }; if (m_pointer_inside && (0 <= m_current_pointer) && (ui_event::pointer::TOUCH != m_pointer_type)) mui.set_pointers(std::begin(pointers), std::end(pointers)); else mui.set_pointers(std::begin(pointers), std::begin(pointers)); // try to display the selected view while (true) { switch (m_mode) { case view::PALETTE: if (m_palette.interface()) return handle_palette(mui, container, uistate); m_mode = view::GFXSET; break; case view::GFXSET: if (m_gfxset.has_gfx()) return handle_gfxset(mui, container, uistate); m_mode = view::TILEMAP; break; case view::TILEMAP: if (m_machine.tilemap().count()) return handle_tilemap(mui, container, uistate); m_mode = view::PALETTE; break; } } } private: enum class view { PALETTE = 0, GFXSET, TILEMAP }; class palette { public: enum class subset { PENS, INDIRECT }; palette(running_machine &machine) : m_count(palette_interface_enumerator(machine.root_device()).count()) { if (m_count) set_device(machine); } device_palette_interface *interface() const noexcept { return m_interface; } bool indirect() const noexcept { return subset::INDIRECT == m_which; } unsigned columns() const noexcept { return m_columns; } unsigned index(unsigned x, unsigned y) const noexcept { return m_offset + (y * m_columns) + x; } void handle_keys(running_machine &machine); private: void set_device(running_machine &machine) { m_interface = palette_interface_enumerator(machine.root_device()).byindex(m_index); } void next_group(running_machine &machine) noexcept { if ((subset::PENS == m_which) && m_interface->indirect_entries()) { m_which = subset::INDIRECT; } else if ((m_count - 1) > m_index) { ++m_index; set_device(machine); m_which = subset::PENS; } } void prev_group(running_machine &machine) noexcept { if (subset::INDIRECT == m_which) { m_which = subset::PENS; } else if (0 < m_index) { --m_index; set_device(machine); m_which = m_interface->indirect_entries() ? subset::INDIRECT : subset::PENS; } } device_palette_interface *m_interface = nullptr; unsigned const m_count; unsigned m_index = 0U; subset m_which = subset::PENS; unsigned m_columns = 16U; int m_offset = 0; }; class gfxset { public: struct setinfo { void next_color() noexcept { if ((m_color_count - 1) > m_color) ++m_color; else m_color = 0U; } void prev_color() noexcept { if (m_color) --m_color; else m_color = m_color_count - 1; } device_palette_interface *m_palette = nullptr; int m_offset = 0; unsigned m_color = 0; unsigned m_color_count = 0U; uint8_t m_rotate = 0U; uint8_t m_columns = 16U; bool m_integer_scale = false; }; class devinfo { public: devinfo(device_gfx_interface &interface, device_palette_interface *first_palette, u8 rotate) : m_interface(&interface), m_setcount(0U) { for (gfx_element *gfx; (MAX_GFX_ELEMENTS > m_setcount) && ((gfx = interface.gfx(m_setcount)) != nullptr); ++m_setcount) { auto &set = m_sets[m_setcount]; if (gfx->has_palette()) { set.m_palette = &gfx->palette(); set.m_color_count = gfx->colors(); } else { set.m_palette = first_palette; set.m_color_count = first_palette->entries() / gfx->granularity(); if (!set.m_color_count) set.m_color_count = 1U; } set.m_rotate = rotate; } } device_gfx_interface &interface() const noexcept { return *m_interface; } unsigned setcount() const noexcept { return m_setcount; } setinfo const &set(unsigned index) const noexcept { return m_sets[index]; } setinfo &set(unsigned index) noexcept { return m_sets[index]; } private: device_gfx_interface *m_interface; unsigned m_setcount; setinfo m_sets[MAX_GFX_ELEMENTS]; }; gfxset(running_machine &machine) { // get useful defaults uint8_t const rotate = machine.system().flags & machine_flags::MASK_ORIENTATION; device_palette_interface *const first_palette = palette_interface_enumerator(machine.root_device()).first(); // iterate over graphics decoders for (device_gfx_interface &interface : gfx_interface_enumerator(machine.root_device())) { // if there are any exposed graphics sets, add the device if (interface.gfx(0U)) m_devices.emplace_back(interface, first_palette, rotate); } } bool has_gfx() const noexcept { return !m_devices.empty(); } bool handle_keys(running_machine &machine, int xcells, int ycells); std::vector m_devices; unsigned m_device = 0U; unsigned m_set = 0U; private: bool next_group() noexcept { if ((m_devices[m_device].setcount() - 1) > m_set) { ++m_set; return true; } else if ((m_devices.size() - 1) > m_device) { ++m_device; m_set = 0U; return true; } else { return false; } } bool prev_group() noexcept { if (m_set) { --m_set; return true; } else if (m_device) { --m_device; m_set = m_devices[m_device].setcount() - 1; return true; } else { return false; } } }; class tilemap { public: tilemap(running_machine &machine) { uint8_t const rotate = machine.system().flags & machine_flags::MASK_ORIENTATION; m_info.resize(machine.tilemap().count()); for (auto &info : m_info) info.m_rotate = rotate; } unsigned index() const noexcept { return m_index; } float zoom_scale() const noexcept { auto const &info = m_info[m_index]; return info.m_zoom_frac ? (1.0f / float(info.m_zoom)) : float(info.m_zoom); } bool auto_zoom() const noexcept { return m_info[m_index].m_auto_zoom; } uint8_t rotate() const noexcept { return m_info[m_index].m_rotate; } uint32_t flags() const noexcept { return m_info[m_index].m_flags; } int xoffs() const noexcept { return m_info[m_index].m_xoffs; } int yoffs() const noexcept { return m_info[m_index].m_yoffs; } bool handle_keys(running_machine &machine, float pixelscale); private: static constexpr int MAX_ZOOM_LEVEL = 8; // maximum tilemap zoom ratio screen:native static constexpr int MIN_ZOOM_LEVEL = 8; // minimum tilemap zoom ratio native:screen struct info { bool zoom_in(float pixelscale) noexcept { if (m_auto_zoom) { // auto zoom never uses fractional factors m_zoom = std::min(std::lround(pixelscale) + 1, MAX_ZOOM_LEVEL); m_zoom_frac = false; m_auto_zoom = false; return true; } else if (m_zoom_frac) { m_zoom--; if (m_zoom == 1) m_zoom_frac = false; // entering integer zoom range return true; } else if (MAX_ZOOM_LEVEL > m_zoom) { m_zoom++; // remaining in integer zoom range return true; } else { return false; } } bool zoom_out(float pixelscale) noexcept { if (m_auto_zoom) { // auto zoom never uses fractional factors m_zoom = std::lround(pixelscale) - 1; m_zoom_frac = !m_zoom; if (m_zoom_frac) m_zoom = 2; m_auto_zoom = false; return true; } else if (!m_zoom_frac) { if (m_zoom == 1) { m_zoom++; m_zoom_frac = true; // entering fractional zoom range } else { m_zoom--; // remaining in integer zoom range } return true; } else if (MIN_ZOOM_LEVEL > m_zoom) { m_zoom++; // remaining in fractional zoom range return true; } else { return false; } } bool next_category() noexcept { if (TILEMAP_DRAW_ALL_CATEGORIES == m_flags) { m_flags = 0U; return true; } else if (TILEMAP_DRAW_CATEGORY_MASK > m_flags) { ++m_flags; return true; } else { return false; } } bool prev_catagory() noexcept { if (!m_flags) { m_flags = TILEMAP_DRAW_ALL_CATEGORIES; return true; } else if (TILEMAP_DRAW_ALL_CATEGORIES != m_flags) { --m_flags; return true; } else { return false; } } int m_xoffs = 0; int m_yoffs = 0; unsigned m_zoom = 1U; bool m_zoom_frac = false; bool m_auto_zoom = true; uint8_t m_rotate = 0U; uint32_t m_flags = TILEMAP_DRAW_ALL_CATEGORIES; }; static int scroll_step(running_machine &machine) { auto &input = machine.input(); if (input.code_pressed(KEYCODE_LCONTROL) || input.code_pressed(KEYCODE_RCONTROL)) return 64; else if (input.code_pressed(KEYCODE_LSHIFT) || input.code_pressed(KEYCODE_RSHIFT)) return 1; else return 8; } std::vector m_info; unsigned m_index = 0U; }; bool is_relevant() const noexcept { return m_palette.interface() || m_gfxset.has_gfx() || m_machine.tilemap().count(); } uint32_t handle_general_keys(bool uistate) { auto &input = m_machine.ui_input(); // UI select cycles through views if (input.pressed(IPT_UI_SELECT)) { m_mode = view((int(m_mode) + 1) % 3); if (0 > m_current_pointer) { m_pointer_type = ui_event::pointer::UNKNOWN; m_pointer_x = -1.0F; m_pointer_x = -1.0F; m_pointer_inside = false; } m_bitmap_dirty = true; } // pause does what you'd expect if (input.pressed(IPT_UI_PAUSE)) { if (m_machine.paused()) m_machine.resume(); else m_machine.pause(); } // cancel or graphics viewer dismisses the viewer if (input.pressed(IPT_UI_BACK) || input.pressed(IPT_UI_SHOW_GFX)) return cancel(uistate); return uistate; } uint32_t cancel(bool uistate) { if (!uistate) m_machine.resume(); m_machine.ui_input().reset(); m_current_pointer = -1; m_pointer_type = ui_event::pointer::UNKNOWN; m_pointer_buttons = 0U; m_pointer_x = -1.0F; m_pointer_y = -1.0F; m_pointer_inside = false; m_bitmap_dirty = true; return mame_ui_manager::HANDLER_CANCEL; } uint32_t handle_palette(mame_ui_manager &mui, render_container &container, bool uistate); uint32_t handle_gfxset(mame_ui_manager &mui, render_container &container, bool uistate); uint32_t handle_tilemap(mame_ui_manager &mui, render_container &container, bool uistate); void update_gfxset_bitmap(int xcells, int ycells, gfx_element &gfx); void update_tilemap_bitmap(int width, int height); void gfxset_draw_item(gfx_element &gfx, int index, int dstx, int dsty, gfxset::setinfo const &info); void draw_text(mame_ui_manager &mui, render_container &container, std::string_view str, float x, float y) { render_font *const font = mui.get_font(); float const height = mui.get_line_height(); float const aspect = m_machine.render().ui_aspect(&container); rgb_t const color = mui.colors().text_color(); int n; char32_t ch; while ((n = uchar_from_utf8(&ch, str)) != 0) { if (0 > n) ch = 0xfffd; str.remove_prefix((0 > n) ? 1 : n); container.add_char(x, y, height, aspect, color, *font, ch); x += font->char_width(height, aspect, ch); } } void resize_bitmap(int32_t width, int32_t height) { if (!m_bitmap.valid() || !m_texture || (m_bitmap.width() != width) || (m_bitmap.height() != height)) { // free the old stuff if (m_texture) m_machine.render().texture_free(m_texture); // allocate new stuff m_bitmap.resize(width, height); m_texture = m_machine.render().texture_alloc(); m_texture->set_bitmap(m_bitmap, m_bitmap.cliprect(), TEXFORMAT_ARGB32); // force a redraw m_bitmap_dirty = true; } } bool map_mouse(render_container &container, render_bounds const &clip, float &x, float &y) const { if (((0 > m_current_pointer) && (m_pointer_type != ui_event::pointer::TOUCH)) || !m_pointer_inside) return false; x = m_pointer_x; y = m_pointer_y; return clip.includes(x, y); } running_machine &m_machine; view m_mode = view::PALETTE; s32 m_current_pointer = -1; ui_event::pointer m_pointer_type = ui_event::pointer::UNKNOWN; u32 m_pointer_buttons = 0U; float m_pointer_x = -1.0F; float m_pointer_y = -1.0F; bool m_pointer_inside = false; bitmap_rgb32 m_bitmap; render_texture *m_texture = nullptr; bool m_bitmap_dirty = false; palette m_palette; gfxset m_gfxset; tilemap m_tilemap; }; void gfx_viewer::palette::handle_keys(running_machine &machine) { auto &input = machine.ui_input(); // handle zoom (minus,plus) if (input.pressed(IPT_UI_ZOOM_OUT)) m_columns = std::min(m_columns * 2, 64); if (input.pressed(IPT_UI_ZOOM_IN)) m_columns = std::max(m_columns / 2, 4); if (input.pressed(IPT_UI_ZOOM_DEFAULT)) m_columns = 16; // handle colormap selection (open bracket,close bracket) if (input.pressed(IPT_UI_PREV_GROUP)) prev_group(machine); if (input.pressed(IPT_UI_NEXT_GROUP)) next_group(machine); // cache some info in locals int const total = (subset::INDIRECT == m_which) ? m_interface->indirect_entries() : m_interface->entries(); // determine number of entries per row and total int const rowcount = m_columns; int const screencount = rowcount * rowcount; // handle keyboard navigation if (input.pressed_repeat(IPT_UI_UP, 4)) m_offset -= rowcount; if (input.pressed_repeat(IPT_UI_DOWN, 4)) m_offset += rowcount; if (input.pressed_repeat(IPT_UI_PAGE_UP, 6)) m_offset -= screencount; if (input.pressed_repeat(IPT_UI_PAGE_DOWN, 6)) m_offset += screencount; if (input.pressed_repeat(IPT_UI_HOME, 4)) m_offset = 0; if (input.pressed_repeat(IPT_UI_END, 4)) m_offset = total; // clamp within range if (m_offset + screencount > ((total + rowcount - 1) / rowcount) * rowcount) m_offset = ((total + rowcount - 1) / rowcount) * rowcount - screencount; if (m_offset < 0) m_offset = 0; } bool gfx_viewer::gfxset::handle_keys(running_machine &machine, int xcells, int ycells) { auto &input = machine.ui_input(); bool const shift_pressed = machine.input().code_pressed(KEYCODE_LSHIFT) || machine.input().code_pressed(KEYCODE_RSHIFT); bool result = false; // handle previous/next group if (input.pressed(IPT_UI_PREV_GROUP) && prev_group()) result = true; if (input.pressed(IPT_UI_NEXT_GROUP) && next_group()) result = true; auto &info = m_devices[m_device]; auto &set = info.set(m_set); auto &gfx = *info.interface().gfx(m_set); // handle cells per line (0/-/=) if (input.pressed(IPT_UI_ZOOM_OUT) && (xcells < 128)) { set.m_columns = xcells + 1; set.m_integer_scale = shift_pressed; result = true; } if (input.pressed(IPT_UI_ZOOM_IN) && (xcells > 2)) { set.m_columns = xcells - 1; set.m_integer_scale = shift_pressed; result = true; } if (input.pressed(IPT_UI_ZOOM_DEFAULT) && ((xcells != 16) || (set.m_integer_scale != shift_pressed))) { set.m_columns = 16; set.m_integer_scale = shift_pressed; result = true; } // handle rotation (R) if (input.pressed(IPT_UI_ROTATE)) { set.m_rotate = orientation_add(ROT90, set.m_rotate); result = true; } // handle navigation within the cells (up,down,pgup,pgdown) if (input.pressed_repeat(IPT_UI_UP, 4)) { set.m_offset -= xcells; result = true; } if (input.pressed_repeat(IPT_UI_DOWN, 4)) { set.m_offset += xcells; result = true; } if (input.pressed_repeat(IPT_UI_PAGE_UP, 6)) { set.m_offset -= xcells * ycells; result = true; } if (input.pressed_repeat(IPT_UI_PAGE_DOWN, 6)) { set.m_offset += xcells * ycells; result = true; } if (input.pressed_repeat(IPT_UI_HOME, 4)) { set.m_offset = 0; result = true; } if (input.pressed_repeat(IPT_UI_END, 4)) { set.m_offset = gfx.elements(); result = true; } // clamp within range if (set.m_offset + xcells * ycells > ((gfx.elements() + xcells - 1) / xcells) * xcells) { set.m_offset = ((gfx.elements() + xcells - 1) / xcells) * xcells - xcells * ycells; result = true; } if (set.m_offset < 0) { set.m_offset = 0; result = true; } // handle color selection (left,right) if (input.pressed_repeat(IPT_UI_LEFT, 4)) { set.prev_color(); result = true; } if (input.pressed_repeat(IPT_UI_RIGHT, 4)) { set.next_color(); result = true; } return result; } bool gfx_viewer::tilemap::handle_keys(running_machine &machine, float pixelscale) { auto &input = machine.ui_input(); bool result = false; // handle tilemap selection (open bracket,close bracket) if (input.pressed(IPT_UI_PREV_GROUP) && m_index > 0) { m_index--; result = true; } if (input.pressed(IPT_UI_NEXT_GROUP) && ((m_info.size() - 1) > m_index)) { m_index++; result = true; } auto &info = m_info[m_index]; // handle zoom (minus,plus) if (input.pressed(IPT_UI_ZOOM_OUT) && info.zoom_out(pixelscale)) { result = true; machine.popmessage(info.m_zoom_frac ? _("gfxview", "Zoom = 1/%1$d") : _("gfxview", "Zoom = %1$d"), info.m_zoom); } if (input.pressed(IPT_UI_ZOOM_IN) && info.zoom_in(pixelscale)) { result = true; machine.popmessage(info.m_zoom_frac ? _("gfxview", "Zoom = 1/%1$d") : _("gfxview", "Zoom = %1$d"), info.m_zoom); } if (input.pressed(IPT_UI_ZOOM_DEFAULT) && !info.m_auto_zoom) { info.m_auto_zoom = true; machine.popmessage(_("gfxview", "Expand to fit")); } // handle rotation (R) if (input.pressed(IPT_UI_ROTATE)) { info.m_rotate = orientation_add(ROT90, info.m_rotate); result = true; } // return to (0,0) (HOME) if (input.pressed(IPT_UI_HOME)) { info.m_xoffs = 0; info.m_yoffs = 0; result = true; } // handle flags (category) if (input.pressed(IPT_UI_PAGE_UP) && info.prev_catagory()) { result = true; if (TILEMAP_DRAW_ALL_CATEGORIES == info.m_flags) machine.popmessage(_("gfxview", "All categories")); else machine.popmessage(_("gfxview", "Category %1$d"), info.m_flags); } if (input.pressed(IPT_UI_PAGE_DOWN) && info.next_category()) { result = true; machine.popmessage(_("gfxview", "Category %1$d"), info.m_flags); } // handle navigation (up,down,left,right), taking orientation into account int const step = scroll_step(machine); // this may be applied more than once if multiple directions are pressed if (input.pressed_repeat(IPT_UI_UP, 4)) { if (info.m_rotate & ORIENTATION_SWAP_XY) info.m_xoffs -= (info.m_rotate & ORIENTATION_FLIP_Y) ? -step : step; else info.m_yoffs -= (info.m_rotate & ORIENTATION_FLIP_Y) ? -step : step; result = true; } if (input.pressed_repeat(IPT_UI_DOWN, 4)) { if (info.m_rotate & ORIENTATION_SWAP_XY) info.m_xoffs += (info.m_rotate & ORIENTATION_FLIP_Y) ? -step : step; else info.m_yoffs += (info.m_rotate & ORIENTATION_FLIP_Y) ? -step : step; result = true; } if (input.pressed_repeat(IPT_UI_LEFT, 6)) { if (info.m_rotate & ORIENTATION_SWAP_XY) info.m_yoffs -= (info.m_rotate & ORIENTATION_FLIP_X) ? -step : step; else info.m_xoffs -= (info.m_rotate & ORIENTATION_FLIP_X) ? -step : step; result = true; } if (input.pressed_repeat(IPT_UI_RIGHT, 6)) { if (info.m_rotate & ORIENTATION_SWAP_XY) info.m_yoffs += (info.m_rotate & ORIENTATION_FLIP_X) ? -step : step; else info.m_xoffs += (info.m_rotate & ORIENTATION_FLIP_X) ? -step : step; result = true; } // cache some info in locals tilemap_t *const tilemap = machine.tilemap().find(m_index); uint32_t const mapwidth = tilemap->width(); uint32_t const mapheight = tilemap->height(); // clamp within range while (info.m_xoffs < 0) info.m_xoffs += mapwidth; while (info.m_xoffs >= mapwidth) info.m_xoffs -= mapwidth; while (info.m_yoffs < 0) info.m_yoffs += mapheight; while (info.m_yoffs >= mapheight) info.m_yoffs -= mapheight; return result; } uint32_t gfx_viewer::handle_palette(mame_ui_manager &mui, render_container &container, bool uistate) { device_palette_interface &palette = *m_palette.interface(); palette_device *const paldev = dynamic_cast(&palette.device()); bool const indirect = m_palette.indirect(); unsigned const total = indirect ? palette.indirect_entries() : palette.entries(); rgb_t const *const raw_color = palette.palette()->entry_list_raw(); // add a half character padding for the box render_font *const ui_font = mui.get_font(); float const aspect = m_machine.render().ui_aspect(&container); float const chheight = mui.get_line_height(); float const chwidth = ui_font->char_width(chheight, aspect, '0'); render_bounds const boxbounds{ 0.0f + (0.5f * chwidth), 0.0f + (0.5f * chheight), 1.0f - (0.5f * chwidth), 1.0f - (0.5f * chheight) }; // the character cell box bounds starts a half character in from the box render_bounds cellboxbounds = boxbounds; cellboxbounds.x0 += 0.5f * chwidth; cellboxbounds.y0 += 0.5f * chheight; cellboxbounds.x1 -= 0.5f * chwidth; 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; // compute the cell size float const cellwidth = (cellboxbounds.x1 - cellboxbounds.x0) / float(m_palette.columns()); float const cellheight = (cellboxbounds.y1 - cellboxbounds.y0) / float(m_palette.columns()); // figure out the title std::ostringstream title_buf; util::stream_format(title_buf, !palette.indirect_entries() ? _("gfxview", "[root%1$s]") : indirect ? _("gfxview", "[root%1$s] colors") : _("gfxview", "[root%1$s] pens"), palette.device().tag()); // if the mouse pointer is over one of our cells, add some info about the corresponding palette entry float mouse_x, mouse_y; if (map_mouse(container, cellboxbounds, mouse_x, mouse_y)) { int const index = m_palette.index(int((mouse_x - cellboxbounds.x0) / cellwidth), int((mouse_y - cellboxbounds.y0) / cellheight)); if (index < total) { rgb_t const col = indirect ? palette.indirect_color(index) : raw_color[index]; if (palette.indirect_entries() && indirect) { util::stream_format(title_buf, _("gfxview", u8" #%1$X \u2192 %2$X (A:%3$02X R:%4$02X G:%5$02X B:%6$02X)"), index, palette.pen_indirect(index), col.a(), col.r(), col.g(), col.b()); } else if (paldev && paldev->basemem().base()) { util::stream_format(title_buf, _("gfxview", " #%1$X = %2$X (A:%3$02X R:%4$02X G:%5$02X B:%6$02X)"), index, paldev->read_entry(index), col.a(), col.r(), col.g(), col.b()); } else { util::stream_format(title_buf, _("gfxview", " #%1$X (A:%2$02X R:%3$02X G:%4$02X B:%5$02X)"), index, col.a(), col.r(), col.g(), col.b()); } // keep touch pointer displayed after release so they know what it's pointing at mame_ui_manager::display_pointer pointers[1]{ { m_machine.render().ui_target(), m_pointer_type, m_pointer_x, m_pointer_y } }; if (ui_event::pointer::TOUCH == m_pointer_type) mui.set_pointers(std::begin(pointers), std::end(pointers)); } } float x0, y0; // expand the outer box to fit the title std::string const title = std::move(title_buf).str(); float const titlewidth = ui_font->string_width(chheight, 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, mui.colors().gfxviewer_bg_color()); // draw the title draw_text(mui, container, title, 0.5f - 0.5f * titlewidth, boxbounds.y0 + 0.5f * chheight); // draw the top column headers int const rowskip = int(chwidth / cellwidth); for (int x = 0; x < m_palette.columns(); x += 1 + rowskip) { 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, 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 (rowskip) 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 int const colskip = int(chheight / cellheight); for (int y = 0; y < m_palette.columns(); y += 1 + colskip) { // only display if there is data to show unsigned const index = m_palette.index(0, y); if (index < total) { // 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 (colskip != 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 auto buffer = util::string_format("%5X", index); for (int x = 4; x >= 0; x--) { x0 -= ui_font->char_width(chheight, aspect, buffer[x]); container.add_char(x0, y0 + 0.5f * (cellheight - chheight), chheight, aspect, rgb_t::white(), *ui_font, buffer[x]); } } } // now add the rectangles for the colors for (int y = 0; y < m_palette.columns(); y++) { for (int x = 0; x < m_palette.columns(); x++) { int const index = m_palette.index(x, y); if (index < total) { pen_t const pen = indirect ? 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 m_palette.handle_keys(m_machine); return handle_general_keys(uistate); } uint32_t gfx_viewer::handle_gfxset(mame_ui_manager &mui, render_container &container, bool uistate) { // get graphics info auto &info = m_gfxset.m_devices[m_gfxset.m_device]; auto &set = info.set(m_gfxset.m_set); device_gfx_interface &interface = info.interface(); gfx_element &gfx = *interface.gfx(m_gfxset.m_set); // get some UI metrics render_font *const ui_font = mui.get_font(); int const targwidth = m_machine.render().ui_target().width(); int const targheight = m_machine.render().ui_target().height(); float const aspect = m_machine.render().ui_aspect(&container); float const chheight = mui.get_line_height(); float const chwidth = ui_font->char_width(chheight, aspect, '0'); // add a half character padding for the box render_bounds boxbounds{ 0.0f + (0.5f * chwidth), 0.0f + (0.5f * chheight), 1.0f - (0.5f * chwidth), 1.0f - (0.5f * chheight) }; // the character cell box bounds starts a half character in from the box render_bounds cellboxbounds = boxbounds; cellboxbounds.x0 += 0.5f * chwidth; cellboxbounds.y0 += 0.5f * chheight; cellboxbounds.x1 -= 0.5f * chwidth; 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 float cellboxwidth = cellboxbounds.width() * float(targwidth); float cellboxheight = cellboxbounds.height() * float(targheight); // compute the number of source pixels in a cell int const cellxpix = 1 + ((set.m_rotate & ORIENTATION_SWAP_XY) ? gfx.height() : gfx.width()); int const cellypix = 1 + ((set.m_rotate & ORIENTATION_SWAP_XY) ? gfx.width() : gfx.height()); // compute the largest pixel scale factor that still fits int xcells = set.m_columns; float pixelscale = 0.0f; while (xcells > 1) { pixelscale = cellboxwidth / (xcells * cellxpix); if (set.m_integer_scale) pixelscale = std::floor(pixelscale); if (0.25f <= pixelscale) break; xcells--; } if (0.0f == pixelscale) pixelscale = cellboxwidth / (xcells * cellxpix); // in the Y direction, we just display as many as we can int ycells = int(cellboxheight / (pixelscale * cellypix)); if (!ycells) { ycells = 1; pixelscale = cellboxheight / cellypix; xcells = int(cellboxwidth / (pixelscale * cellxpix)); } // now determine the actual cellbox size set.m_columns = xcells; cellboxwidth = std::min(cellboxwidth, xcells * pixelscale * cellxpix); cellboxheight = std::min(cellboxheight, ycells * pixelscale * cellypix); // compute the size of a single cell at this pixel scale factor float const cellwidth = (cellboxwidth / xcells) / targwidth; float const cellheight = (cellboxheight / ycells) / targheight; // working from the new width/height, recompute the boxbounds float const fullwidth = cellboxwidth / targwidth + 6.5f * chwidth; float const fullheight = cellboxheight / targheight + 4.0f * chheight; // recompute boxbounds from this boxbounds.x0 = (1.0f - fullwidth) * 0.5f; boxbounds.y0 = (1.0f - fullheight) * 0.5f; boxbounds.x1 = boxbounds.x0 + fullwidth; boxbounds.y1 = boxbounds.y0 + fullheight; // recompute cellboxbounds cellboxbounds.x0 = boxbounds.x0 + 6.0f * chwidth; cellboxbounds.y0 = boxbounds.y0 + 3.5f * chheight; cellboxbounds.x1 = cellboxbounds.x0 + cellboxwidth / float(targwidth); cellboxbounds.y1 = cellboxbounds.y0 + cellboxheight / float(targheight); // figure out the title std::ostringstream title_buf; util::stream_format(title_buf, _("gfxview", "[root%1$s] %2$d/%3$d"), interface.device().tag(), m_gfxset.m_set, info.setcount() - 1); // if the mouse pointer is over a pixel in a tile, add some info about the tile and pixel bool found_pixel = false; float mouse_x, mouse_y; if (map_mouse(container, cellboxbounds, mouse_x, mouse_y)) { int const code = set.m_offset + int((mouse_x - cellboxbounds.x0) / cellwidth) + int((mouse_y - cellboxbounds.y0) / cellheight) * xcells; int xpixel = int((mouse_x - cellboxbounds.x0) / (cellwidth / cellxpix)) % cellxpix; int ypixel = int((mouse_y - cellboxbounds.y0) / (cellheight / cellypix)) % cellypix; if ((code < gfx.elements()) && (xpixel < (cellxpix - 1)) && (ypixel < (cellypix - 1))) { found_pixel = true; if (set.m_rotate & ORIENTATION_FLIP_X) xpixel = (cellxpix - 2) - xpixel; if (set.m_rotate & ORIENTATION_FLIP_Y) ypixel = (cellypix - 2) - ypixel; if (set.m_rotate & ORIENTATION_SWAP_XY) std::swap(xpixel, ypixel); uint8_t const pixdata = gfx.get_data(code)[xpixel + ypixel * gfx.rowbytes()]; util::stream_format(title_buf, _("gfxview", " #%1$X:%2$X (%3$d %4$d) = %5$X"), code, set.m_color, xpixel, ypixel, gfx.colorbase() + (set.m_color * gfx.granularity()) + pixdata); // keep touch pointer displayed after release so they know what it's pointing at mame_ui_manager::display_pointer pointers[1]{ { m_machine.render().ui_target(), m_pointer_type, m_pointer_x, m_pointer_y } }; if (ui_event::pointer::TOUCH == m_pointer_type) mui.set_pointers(std::begin(pointers), std::end(pointers)); } } if (!found_pixel) util::stream_format(title_buf, _("gfxview", u8" %1$d\u00d7%2$d color %3$X/%4$X"), gfx.width(), gfx.height(), set.m_color, set.m_color_count); float x0, y0; // expand the outer box to fit the title std::string const title = std::move(title_buf).str(); float const titlewidth = ui_font->string_width(chheight, 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, mui.colors().gfxviewer_bg_color()); // draw the title draw_text(mui, container, title, 0.5f - 0.5f * titlewidth, boxbounds.y0 + 0.5f * chheight); // draw the top column headers int const colskip = int(chwidth / cellwidth); for (int x = 0; x < xcells; x += 1 + colskip) { 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, 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 (colskip) 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 int const rowskip = int(chheight / cellheight); for (int y = 0; y < ycells; y += 1 + rowskip) { // only display if there is data to show if (set.m_offset + (y * xcells) < gfx.elements()) { // 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 (rowskip) 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 auto buffer = util::string_format("%5X", set.m_offset + (y * xcells)); for (int x = 4; x >= 0; x--) { x0 -= ui_font->char_width(chheight, aspect, buffer[x]); container.add_char(x0, y0 + 0.5f * (cellheight - chheight), chheight, aspect, rgb_t::white(), *ui_font, buffer[x]); } } } // update the bitmap update_gfxset_bitmap(xcells, ycells, gfx); // add the final quad container.add_quad( cellboxbounds.x0, cellboxbounds.y0, cellboxbounds.x1, cellboxbounds.y1, rgb_t::white(), m_texture, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); // handle keyboard navigation before drawing if (m_gfxset.handle_keys(m_machine, xcells, ycells)) m_bitmap_dirty = true; return handle_general_keys(uistate); } uint32_t gfx_viewer::handle_tilemap(mame_ui_manager &mui, render_container &container, bool uistate) { // get some UI metrics render_font *const ui_font = mui.get_font(); int const targwidth = m_machine.render().ui_target().width(); int const targheight = m_machine.render().ui_target().height(); float const aspect = m_machine.render().ui_aspect(&container); float const chheight = mui.get_line_height(); float const chwidth = ui_font->char_width(chheight, aspect, '0'); // get the size of the tilemap itself tilemap_t &tilemap = *m_machine.tilemap().find(m_tilemap.index()); uint32_t mapwidth = tilemap.width(); uint32_t mapheight = tilemap.height(); if (m_tilemap.rotate() & ORIENTATION_SWAP_XY) std::swap(mapwidth, mapheight); // add a half character padding for the box render_bounds boxbounds{ 0.0f + (0.5f * chwidth), 0.0f + (0.5f * chheight), 1.0f - (0.5f * chwidth), 1.0f - (0.5f * chheight) }; // the tilemap box bounds starts a half character in from the box render_bounds 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 int mapboxwidth = mapboxbounds.width() * float(targwidth); int mapboxheight = mapboxbounds.height() * float(targheight); float pixelscale; if (m_tilemap.auto_zoom()) { // determine the maximum integral scaling factor pixelscale = std::min(std::floor(mapboxwidth / mapwidth), std::floor(mapboxheight / mapheight)); pixelscale = std::max(pixelscale, 1.0f); } else { pixelscale = m_tilemap.zoom_scale(); } // recompute the final box size mapboxwidth = std::min(mapboxwidth, std::lround(mapwidth * pixelscale)); mapboxheight = std::min(mapboxheight, std::lround(mapheight * pixelscale)); // recompute the bounds, centered within the existing bounds mapboxbounds.x0 += 0.5f * ((mapboxbounds.x1 - mapboxbounds.x0) - float(mapboxwidth) / targwidth); mapboxbounds.x1 = mapboxbounds.x0 + float(mapboxwidth) / targwidth; mapboxbounds.y0 += 0.5f * ((mapboxbounds.y1 - mapboxbounds.y0) - float(mapboxheight) / targheight); mapboxbounds.y1 = mapboxbounds.y0 + float(mapboxheight) / 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 std::ostringstream title_buf; util::stream_format(title_buf, (m_tilemap.flags() != TILEMAP_DRAW_ALL_CATEGORIES) ? _("gfxview", "Tilemap %1$d/%2$d category %3$u") : _("gfxview", "Tilemap %1$d/%2$d "), m_tilemap.index() + 1, m_machine.tilemap().count(), m_tilemap.flags()); // if the mouse pointer is over a tile, add some info about its coordinates and color float mouse_x, mouse_y; if (map_mouse(container, mapboxbounds, mouse_x, mouse_y)) { int xpixel = (mouse_x - mapboxbounds.x0) * targwidth; int ypixel = (mouse_y - mapboxbounds.y0) * targheight; if (m_tilemap.rotate() & ORIENTATION_FLIP_X) xpixel = (mapboxwidth - 1) - xpixel; if (m_tilemap.rotate() & ORIENTATION_FLIP_Y) ypixel = (mapboxheight - 1) - ypixel; if (m_tilemap.rotate() & ORIENTATION_SWAP_XY) std::swap(xpixel, ypixel); uint32_t const col = ((std::lround(xpixel / pixelscale) + m_tilemap.xoffs()) / tilemap.tilewidth()) % tilemap.cols(); uint32_t const row = ((std::lround(ypixel / pixelscale) + m_tilemap.yoffs()) / tilemap.tileheight()) % tilemap.rows(); uint8_t gfxnum; uint32_t code, color; tilemap.get_info_debug(col, row, gfxnum, code, color); util::stream_format(title_buf, _("gfxview", " (%1$u %2$u) = GFX%3$u #%4$X:%5$X"), col * tilemap.tilewidth(), row * tilemap.tileheight(), gfxnum, code, color); // keep touch pointer displayed after release so they know what it's pointing at mame_ui_manager::display_pointer pointers[1]{ { m_machine.render().ui_target(), m_pointer_type, m_pointer_x, m_pointer_y } }; if (ui_event::pointer::TOUCH == m_pointer_type) mui.set_pointers(std::begin(pointers), std::end(pointers)); } else { util::stream_format(title_buf, _("gfxview", u8" %1$d\u00d7%2$d origin (%3$d %4$d)"), tilemap.width(), tilemap.height(), m_tilemap.xoffs(), m_tilemap.yoffs()); } // expand the outer box to fit the title std::string const title = std::move(title_buf).str(); float const titlewidth = ui_font->string_width(chheight, 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, mui.colors().gfxviewer_bg_color()); // draw the title draw_text(mui, container, title, 0.5f - 0.5f * titlewidth, boxbounds.y0 + 0.5f * chheight); // update the bitmap update_tilemap_bitmap(std::lround(mapboxwidth / pixelscale), std::lround(mapboxheight / pixelscale)); // add the final quad container.add_quad( mapboxbounds.x0, mapboxbounds.y0, mapboxbounds.x1, mapboxbounds.y1, rgb_t::white(), m_texture, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXORIENT(m_tilemap.rotate())); // handle keyboard input if (m_tilemap.handle_keys(m_machine, pixelscale)) m_bitmap_dirty = true; return handle_general_keys(uistate); } void gfx_viewer::update_gfxset_bitmap(int xcells, int ycells, gfx_element &gfx) { auto const &info = m_gfxset.m_devices[m_gfxset.m_device]; auto const &set = info.set(m_gfxset.m_set); // compute the number of source pixels in a cell int const cellxpix = 1 + ((set.m_rotate & ORIENTATION_SWAP_XY) ? gfx.height() : gfx.width()); int const cellypix = 1 + ((set.m_rotate & ORIENTATION_SWAP_XY) ? gfx.width() : gfx.height()); // reallocate the bitmap if it is too small resize_bitmap(cellxpix * xcells, cellypix * ycells); // handle the redraw if (m_bitmap_dirty) { // pre-fill with transparency m_bitmap.fill(0); // loop over rows for (int y = 0, index = set.m_offset; y < ycells; y++) { // make a rectangle that covers this row rectangle cellbounds(0, m_bitmap.width() - 1, y * cellypix, (y + 1) * cellypix - 1); // only display if there is data to show if (index < gfx.elements()) { // draw the individual cells for (int x = 0; x < xcells; x++, index++) { // update the bounds for this cell cellbounds.min_x = x * cellxpix; cellbounds.max_x = (x + 1) * cellxpix - 1; if (index < gfx.elements()) // only render if there is data gfxset_draw_item(gfx, index, cellbounds.min_x, cellbounds.min_y, set); else // otherwise, fill with transparency m_bitmap.fill(0, cellbounds); } } } // reset the texture to force an update m_texture->set_bitmap(m_bitmap, m_bitmap.cliprect(), TEXFORMAT_ARGB32); m_bitmap_dirty = false; } } void gfx_viewer::update_tilemap_bitmap(int width, int height) { // swap the coordinates back if they were talking about a rotated surface if (m_tilemap.rotate() & ORIENTATION_SWAP_XY) std::swap(width, height); // reallocate the bitmap if it is too small resize_bitmap(width, height); // handle the redraw if (m_bitmap_dirty) { m_bitmap.fill(0); tilemap_t &tilemap = *m_machine.tilemap().find(m_tilemap.index()); screen_device *const first_screen = screen_device_enumerator(m_machine.root_device()).first(); if (first_screen) tilemap.draw_debug(*first_screen, m_bitmap, m_tilemap.xoffs(), m_tilemap.yoffs(), m_tilemap.flags()); // reset the texture to force an update m_texture->set_bitmap(m_bitmap, m_bitmap.cliprect(), TEXFORMAT_RGB32); m_bitmap_dirty = false; } } void gfx_viewer::gfxset_draw_item(gfx_element &gfx, int index, int dstx, int dsty, gfxset::setinfo const &info) { int const width = (info.m_rotate & ORIENTATION_SWAP_XY) ? gfx.height() : gfx.width(); int const height = (info.m_rotate & ORIENTATION_SWAP_XY) ? gfx.width() : gfx.height(); rgb_t const *const palette = info.m_palette->palette()->entry_list_raw() + gfx.colorbase() + info.m_color * gfx.granularity(); uint8_t const *const src = gfx.get_data(index); // loop over rows in the cell for (int y = 0; y < height; y++) { uint32_t *dest = &m_bitmap.pix(dsty + y, dstx); // loop over columns in the cell for (int x = 0; x < width; x++) { // compute effective x,y values after rotation int effx = x, effy = y; if (!(info.m_rotate & ORIENTATION_SWAP_XY)) { if (info.m_rotate & ORIENTATION_FLIP_X) effx = gfx.width() - 1 - effx; if (info.m_rotate & ORIENTATION_FLIP_Y) effy = gfx.height() - 1 - effy; } else { if (info.m_rotate & ORIENTATION_FLIP_X) effx = gfx.height() - 1 - effx; if (info.m_rotate & ORIENTATION_FLIP_Y) effy = gfx.width() - 1 - effy; std::swap(effx, effy); } // get a pointer to the start of this source row uint8_t const *const s = src + (effy * gfx.rowbytes()); // extract the pixel *dest++ = 0xff000000 | palette[s[effx]]; } } } } // anonymous namespace /*************************************************************************** MAIN ENTRY POINT ***************************************************************************/ //------------------------------------------------- // ui_gfx_ui_handler - primary UI handler // // NOTE: this must not be called before machine // initialization is complete, as some drivers // create or modify gfx sets in VIDEO_START //------------------------------------------------- uint32_t ui_gfx_ui_handler(render_container &container, mame_ui_manager &mui, bool uistate) { return mui.get_session_data(mui.machine()).handle(mui, container, uistate); }