diff options
Diffstat (limited to 'src/frontend/mame/ui/menu.cpp')
-rw-r--r-- | src/frontend/mame/ui/menu.cpp | 954 |
1 files changed, 511 insertions, 443 deletions
diff --git a/src/frontend/mame/ui/menu.cpp b/src/frontend/mame/ui/menu.cpp index b0a84d3a95c..71e15631879 100644 --- a/src/frontend/mame/ui/menu.cpp +++ b/src/frontend/mame/ui/menu.cpp @@ -9,7 +9,6 @@ *********************************************************************/ #include "emu.h" - #include "ui/menu.h" #include "ui/ui.h" @@ -20,34 +19,29 @@ #include "cheat.h" #include "mame.h" +#include "corestr.h" #include "drivenum.h" +#include "fileio.h" #include "rendutil.h" #include "uiinput.h" +#include "osdepend.h" + #include <algorithm> #include <cassert> #include <cmath> -#include <utility> +#include <type_traits> namespace ui { /*************************************************************************** - GLOBAL VARIABLES -***************************************************************************/ - -std::mutex menu::s_global_state_guard; -menu::global_state_map menu::s_global_states; - -/*************************************************************************** INLINE FUNCTIONS ***************************************************************************/ -menu::global_state_ptr menu::get_global_state(running_machine &machine) +menu::global_state &menu::get_global_state(mame_ui_manager &ui) { - std::lock_guard<std::mutex> guard(s_global_state_guard); - auto const it(s_global_states.find(&machine)); - return (it != s_global_states.end()) ? it->second : global_state_ptr(); + return ui.get_session_data<menu, global_state_wrapper>(ui); } //------------------------------------------------- @@ -75,30 +69,30 @@ bool menu::exclusive_input_pressed(int &iptkey, int key, int repeat) CORE SYSTEM MANAGEMENT ***************************************************************************/ -menu::global_state::global_state(running_machine &machine, ui_options const &options) - : widgets_manager(machine) - , m_machine(machine) - , m_cleanup_callbacks() +menu::global_state::global_state(mame_ui_manager &ui) + : widgets_manager(ui.machine()) + , m_ui(ui) , m_bgrnd_bitmap() - , m_bgrnd_texture(nullptr, machine.render()) + , m_bgrnd_texture(nullptr, ui.machine().render()) , m_stack() , m_free() + , m_hide(false) { - render_manager &render(machine.render()); + render_manager &render(ui.machine().render()); // create a texture for main menu background m_bgrnd_texture.reset(render.texture_alloc(render_texture::hq_scale)); - if (options.use_background_image() && (&machine.system() == &GAME_NAME(___empty))) + if (ui.options().use_background_image() && (&ui.machine().system() == &GAME_NAME(___empty))) { m_bgrnd_bitmap = std::make_unique<bitmap_argb32>(0, 0); emu_file backgroundfile(".", OPEN_FLAG_READ); - if (backgroundfile.open("background.jpg") == osd_file::error::NONE) + if (!backgroundfile.open("background.jpg")) { render_load_jpeg(*m_bgrnd_bitmap, backgroundfile); backgroundfile.close(); } - if (!m_bgrnd_bitmap->valid() && (backgroundfile.open("background.png") == osd_file::error::NONE)) + if (!m_bgrnd_bitmap->valid() && !backgroundfile.open("background.png")) { render_load_png(*m_bgrnd_bitmap, backgroundfile); backgroundfile.close(); @@ -114,29 +108,20 @@ menu::global_state::global_state(running_machine &machine, ui_options const &opt menu::global_state::~global_state() { - // it shouldn't really be possible to get here with active menus because of reference loops - assert(!m_stack); - assert(!m_free); - stack_reset(); clear_free_list(); - - for (auto const &callback : m_cleanup_callbacks) - callback(m_machine); -} - - -void menu::global_state::add_cleanup_callback(cleanup_callback &&callback) -{ - m_cleanup_callbacks.emplace_back(std::move(callback)); } void menu::global_state::stack_push(std::unique_ptr<menu> &&menu) { + if (m_stack && m_stack->is_active()) + { + m_stack->m_active = false; + m_stack->menu_deactivated(); + } menu->m_parent = std::move(m_stack); m_stack = std::move(menu); - m_stack->reset(reset_options::SELECT_FIRST); m_stack->machine().ui_input().reset(); } @@ -145,11 +130,19 @@ void menu::global_state::stack_pop() { if (m_stack) { + if (m_stack->is_one_shot()) + m_hide = true; + if (m_stack->is_active()) + { + m_stack->m_active = false; + m_stack->menu_deactivated(); + } + m_stack->menu_dismissed(); std::unique_ptr<menu> menu(std::move(m_stack)); m_stack = std::move(menu->m_parent); menu->m_parent = std::move(m_free); m_free = std::move(menu); - m_machine.ui_input().reset(); + m_ui.machine().ui_input().reset(); } } @@ -188,40 +181,45 @@ bool menu::global_state::stack_has_special_main_menu() const } - - -//------------------------------------------------- -// init - initialize the menu system -//------------------------------------------------- - -void menu::init(running_machine &machine, ui_options &mopt) +uint32_t menu::global_state::ui_handler(render_container &container) { - // initialize the menu stack + // if we have no menus stacked up, start with the main menu + if (!m_stack) + stack_push(std::make_unique<menu_main>(m_ui, container)); + + // ensure topmost menu is active - need a loop because it could push another menu + while (m_stack && !m_stack->is_active()) { - std::lock_guard<std::mutex> guard(s_global_state_guard); - auto const ins(s_global_states.emplace(&machine, std::make_shared<global_state>(machine, mopt))); - assert(ins.second); // calling init twice is bad - if (ins.second) - machine.add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(&menu::exit, &machine)); // add an exit callback to free memory - else - ins.first->second->stack_reset(); + m_stack->m_active = true; + m_stack->menu_activated(); } -} + // update the menu state + m_hide = false; + bool need_update = m_stack && m_stack->do_handle(); -//------------------------------------------------- -// exit - clean up after ourselves -//------------------------------------------------- + // clear up anything pending being released + clear_free_list(); -void menu::exit(running_machine &machine) -{ - // free menus - global_state_ptr const state(get_global_state(machine)); - state->stack_reset(); - state->clear_free_list(); + // if the menus are to be hidden, return a cancel here + if (m_ui.is_menu_active() && (m_hide || !m_stack)) + { + if (m_stack) + { + if (m_stack->is_one_shot()) + { + stack_pop(); + } + else if (m_stack->is_active()) + { + m_stack->m_active = false; + m_stack->menu_deactivated(); + } + } + return mame_ui_manager::HANDLER_CANCEL; + } - std::lock_guard<std::mutex> guard(s_global_state_guard); - s_global_states.erase(&machine); + return need_update ? mame_ui_manager::HANDLER_UPDATE : 0; } @@ -235,30 +233,43 @@ void menu::exit(running_machine &machine) //------------------------------------------------- menu::menu(mame_ui_manager &mui, render_container &container) - : m_selected(0) - , m_items() - , m_visible_lines(0) - , m_visible_items(0) - , m_global_state(get_global_state(mui.machine())) - , m_special_main_menu(false) + : m_global_state(get_global_state(mui)) , m_ui(mui) , m_container(container) , m_parent() + , m_heading() + , m_items() + , m_rebuilding(false) + , m_last_size(0, 0) + , m_last_aspect(0.0F) + , m_line_height(0.0F) + , m_gutter_width(0.0F) + , m_tb_border(0.0F) + , m_lr_border(0.0F) + , m_lr_arrow_width(0.0F) + , m_ud_arrow_width(0.0F) + , m_process_flags(0) + , m_selected(0) + , m_hover(1) + , m_special_main_menu(false) + , m_one_shot(false) + , m_needs_prev_menu_item(true) + , m_active(false) , m_event() - , m_customtop(0.0f) - , m_custombottom(0.0f) + , m_customtop(0.0F) + , m_custombottom(0.0F) , m_resetpos(0) , m_resetref(nullptr) , m_mouse_hit(false) , m_mouse_button(false) - , m_mouse_x(-1.0f) - , m_mouse_y(-1.0f) + , m_mouse_x(-1.0F) + , m_mouse_y(-1.0F) { - assert(m_global_state); // not calling init is bad - reset(reset_options::SELECT_FIRST); top_line = 0; + m_visible_lines = 0; + m_visible_items = 0; } @@ -289,37 +300,6 @@ void menu::reset(reset_options options) m_items.clear(); m_visible_items = 0; m_selected = 0; - - // add an item to return - if (!m_parent) - { - item_append(_("Return to Machine"), "", 0, nullptr); - } - else if (m_parent->is_special_main_menu()) - { - if (machine().options().ui() == emu_options::UI_SIMPLE) - item_append(_("Exit"), "", 0, nullptr); - else - item_append(_("Exit"), "", FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW, nullptr); - } - else - { - if (machine().options().ui() != emu_options::UI_SIMPLE && stack_has_special_main_menu()) - item_append(_("Return to Previous Menu"), "", FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW, nullptr); - else - item_append(_("Return to Previous Menu"), "", 0, nullptr); - } -} - - -//------------------------------------------------- -// is_special_main_menu - returns whether the -// menu has special needs -//------------------------------------------------- - -bool menu::is_special_main_menu() const -{ - return m_special_main_menu; } @@ -339,30 +319,13 @@ void menu::set_special_main_menu(bool special) // end of the menu //------------------------------------------------- -void menu::item_append(menu_item item) -{ - item_append(item.text, item.subtext, item.flags, item.ref, item.type); -} - -//------------------------------------------------- -// item_append - append a new item to the -// end of the menu -//------------------------------------------------- - -void menu::item_append(menu_item_type type, uint32_t flags) +int menu::item_append(menu_item_type type, uint32_t flags) { + assert(menu_item_type::SEPARATOR == type); if (type == menu_item_type::SEPARATOR) - item_append(MENU_SEPARATOR_ITEM, "", flags, nullptr, menu_item_type::SEPARATOR); -} - -//------------------------------------------------- -// item_append - append a new item to the -// end of the menu -//------------------------------------------------- - -void menu::item_append(const std::string &text, const std::string &subtext, uint32_t flags, void *ref, menu_item_type type) -{ - item_append(std::string(text), std::string(subtext), flags, ref, type); + return item_append(MENU_SEPARATOR_ITEM, flags, nullptr, menu_item_type::SEPARATOR); + else + return -1; } //------------------------------------------------- @@ -370,39 +333,34 @@ void menu::item_append(const std::string &text, const std::string &subtext, uint // end of the menu //------------------------------------------------- -void menu::item_append(std::string &&text, std::string &&subtext, uint32_t flags, void *ref, menu_item_type type) +int menu::item_append(std::string &&text, std::string &&subtext, uint32_t flags, void *ref, menu_item_type type) { - // only allow multiline as the first item - if ((flags & FLAG_MULTILINE) != 0) - assert(m_items.size() == 1); - - // only allow a single multi-line item - else if (m_items.size() >= 2) - assert((m_items[0].flags & FLAG_MULTILINE) == 0); + assert(m_rebuilding); // allocate a new item and populate it - menu_item pitem; - pitem.text = std::move(text); - pitem.subtext = std::move(subtext); - pitem.flags = flags; - pitem.ref = ref; - pitem.type = type; + menu_item pitem(type, ref, flags); + pitem.set_text(std::move(text)); + pitem.set_subtext(std::move(subtext)); // append to array auto index = m_items.size(); - if (!m_items.empty()) + if (!m_items.empty() && m_needs_prev_menu_item) { m_items.emplace(m_items.end() - 1, std::move(pitem)); --index; } else + { m_items.emplace_back(std::move(pitem)); + } // update the selection if we need to - if (m_resetpos == index || (m_resetref != nullptr && m_resetref == ref)) + if ((m_resetpos == index) || (m_resetref && (m_resetref == ref))) m_selected = index; if (m_resetpos == (m_items.size() - 1)) m_selected = m_items.size() - 1; + + return int(std::make_signed_t<decltype(index)>(index)); } @@ -411,25 +369,26 @@ void menu::item_append(std::string &&text, std::string &&subtext, uint32_t flags // item to the end of the menu //------------------------------------------------- -void menu::item_append_on_off(const std::string &text, bool state, uint32_t flags, void *ref, menu_item_type type) +int menu::item_append_on_off(const std::string &text, bool state, uint32_t flags, void *ref, menu_item_type type) { if (flags & FLAG_DISABLE) ref = nullptr; else flags |= state ? FLAG_LEFT_ARROW : FLAG_RIGHT_ARROW; - item_append(std::string(text), state ? _("On") : _("Off"), flags, ref, type); + return item_append(std::string(text), state ? _("On") : _("Off"), flags, ref, type); } //------------------------------------------------- -// repopulate - repopulate menu items +// set_custom_space - set space required for +// custom rendering above and below menu //------------------------------------------------- -void menu::repopulate(reset_options options) +void menu::set_custom_space(float top, float bottom) { - reset(options); - populate(m_customtop, m_custombottom); + m_customtop = top; + m_custombottom = bottom; } @@ -438,7 +397,7 @@ void menu::repopulate(reset_options options) // and returning any interesting events //------------------------------------------------- -const menu::event *menu::process(uint32_t flags, float x0, float y0) +std::pair<const menu::event *, bool> menu::process() { // reset the event m_event.iptkey = IPT_INVALID; @@ -446,33 +405,45 @@ const menu::event *menu::process(uint32_t flags, float x0, float y0) // first make sure our selection is valid validate_selection(1); - // draw the menu - if (m_items.size() > 1 && (m_items[0].flags & FLAG_MULTILINE) != 0) - draw_text_box(); - else - draw(flags); + // if we're not running the emulation, draw parent menus in the background + auto const draw_parent = + [] (auto &self, menu *parent) -> bool + { + if (!parent || !(parent->is_special_main_menu() || self(self, parent->m_parent.get()))) + return false; + else + parent->draw(PROCESS_NOINPUT); + return true; + }; + if (draw_parent(draw_parent, m_parent.get())) + container().add_rect(0.0F, 0.0F, 1.0F, 1.0F, rgb_t(114, 0, 0, 0), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + + // draw the menu proper + // FIXME: this should happen after processing events to fix the egregious latency + // The main thing preventing this is that mouse handling is mixed up with drawing + draw(m_process_flags); // process input - if (!(flags & PROCESS_NOKEYS) && !(flags & PROCESS_NOINPUT)) + if (!(m_process_flags & (PROCESS_NOKEYS | PROCESS_NOINPUT))) { // read events - handle_events(flags, m_event); + handle_events(m_process_flags, m_event); // handle the keys if we don't already have an event if (m_event.iptkey == IPT_INVALID) - handle_keys(flags, m_event.iptkey); + handle_keys(m_process_flags, m_event.iptkey); } // update the selected item in the event if ((m_event.iptkey != IPT_INVALID) && selection_valid()) { m_event.itemref = get_selection_ref(); - m_event.type = m_items[m_selected].type; - return &m_event; + m_event.item = &m_items[m_selected]; + return std::make_pair(&m_event, false); } else { - return nullptr; + return std::make_pair(nullptr, false); } } @@ -487,7 +458,7 @@ void menu::set_selection(void *selected_itemref) m_selected = -1; for (int itemnum = 0; itemnum < m_items.size(); itemnum++) { - if (m_items[itemnum].ref == selected_itemref) + if (m_items[itemnum].ref() == selected_itemref) { m_selected = itemnum; break; @@ -508,23 +479,23 @@ void menu::set_selection(void *selected_itemref) void menu::draw(uint32_t flags) { // first draw the FPS counter + // FIXME: provide a way to do this in the UI manager itself while menus are on-screen if (ui().show_fps_counter()) { - ui().draw_text_full(container(), machine().video().speed_text().c_str(), 0.0f, 0.0f, 1.0f, - ui::text_layout::RIGHT, ui::text_layout::WORD, mame_ui_manager::OPAQUE_, rgb_t::white(), rgb_t::black(), nullptr, nullptr); + ui().draw_text_full( + container(), + machine().video().speed_text(), + 0.0F, 0.0F, 1.0F, + text_layout::text_justify::RIGHT, text_layout::word_wrapping::WORD, + mame_ui_manager::OPAQUE_, rgb_t::white(), rgb_t::black(), + nullptr, nullptr); } bool const customonly = (flags & PROCESS_CUSTOM_ONLY); - bool const noimage = (flags & PROCESS_NOIMAGE); bool const noinput = (flags & PROCESS_NOINPUT); - float const aspect = machine().render().ui_aspect(&container()); - float const line_height = ui().get_line_height(); - float const lr_arrow_width = 0.4f * line_height * aspect; - float const ud_arrow_width = line_height * aspect; - float const gutter_width = lr_arrow_width * 1.3f; - float const lr_border = ui().box_lr_border() * aspect; - - if (&machine().system() == &GAME_NAME(___empty) && !noimage) + float const max_width = 1.0F - ((lr_border() + (machine().render().ui_aspect(&container()) * UI_LINE_WIDTH)) * 2.0F); + + if (is_special_main_menu()) draw_background(); // compute the width and height of the full menu @@ -533,56 +504,86 @@ void menu::draw(uint32_t flags) for (auto const &pitem : m_items) { // compute width of left hand side - float total_width = gutter_width + ui().get_string_width(pitem.text.c_str()) + gutter_width; + float total_width = gutter_width() + get_string_width(pitem.text()) + gutter_width(); // add in width of right hand side - if (!pitem.subtext.empty()) - total_width += 2.0f * gutter_width + ui().get_string_width(pitem.subtext.c_str()); + if (!pitem.subtext().empty()) + total_width += 2.0F * gutter_width() + get_string_width(pitem.subtext()); + else if (pitem.flags() & FLAG_UI_HEADING) + total_width += 4.0F * ud_arrow_width(); // track the maximum - if (total_width > visible_width) - visible_width = total_width; + visible_width = std::max(total_width, visible_width); // track the height as well - visible_main_menu_height += line_height; + visible_main_menu_height += line_height(); + } + + // lay out the heading if present + std::optional<text_layout> heading_layout; + if (m_heading) + { + heading_layout.emplace(create_layout(max_width - (gutter_width() * 2.0F), text_layout::text_justify::CENTER)); + heading_layout->add_text(*m_heading, ui().colors().text_color()); + + // readjust visible width if heading width exceeds that of the menu + visible_width = std::max(gutter_width() + heading_layout->actual_width() + gutter_width(), visible_width); } // account for extra space at the top and bottom - float const visible_extra_menu_height = m_customtop + m_custombottom; + float const top_extra_menu_height = m_customtop + (heading_layout ? (heading_layout->actual_height() + (tb_border() * 3.0F)) : 0.0F); + float const visible_extra_menu_height = top_extra_menu_height + m_custombottom; // add a little bit of slop for rounding - visible_width += 0.01f; - visible_main_menu_height += 0.01f; + visible_width += 0.01F; + visible_main_menu_height += 0.01F; // if we are too wide or too tall, clamp it down - if (visible_width + 2.0f * lr_border > 1.0f) - visible_width = 1.0f - 2.0f * lr_border; + visible_width = std::min(visible_width, max_width); // if the menu and extra menu won't fit, take away part of the regular menu, it will scroll - if (visible_main_menu_height + visible_extra_menu_height + 2.0f * ui().box_tb_border() > 1.0f) - visible_main_menu_height = 1.0f - 2.0f * ui().box_tb_border() - visible_extra_menu_height; + if (visible_main_menu_height + visible_extra_menu_height + 2.0F * tb_border() > 1.0F) + visible_main_menu_height = 1.0F - 2.0F * tb_border() - visible_extra_menu_height; - m_visible_lines = std::min(int(std::floor(visible_main_menu_height / line_height)), int(unsigned(m_items.size()))); - visible_main_menu_height = float(m_visible_lines) * line_height; + m_visible_lines = std::min(int(std::floor(visible_main_menu_height / line_height())), int(unsigned(m_items.size()))); + visible_main_menu_height = float(m_visible_lines) * line_height(); // compute top/left of inner menu area by centering - float const visible_left = (1.0f - visible_width) * 0.5f; - float const visible_top = ((1.0f - visible_main_menu_height - visible_extra_menu_height) * 0.5f) + m_customtop; + float const visible_left = (1.0F - visible_width) * 0.5F; + float const visible_top = std::round((((1.0F - visible_main_menu_height - visible_extra_menu_height) * 0.5F) + top_extra_menu_height) * float(m_last_size.second)) / float(m_last_size.second); // first add us a box - float const x1 = visible_left - lr_border; - float const y1 = visible_top - ui().box_tb_border(); - float const x2 = visible_left + visible_width + lr_border; - float const y2 = visible_top + visible_main_menu_height + ui().box_tb_border(); + float const x1 = visible_left - lr_border(); + float const y1 = visible_top - tb_border(); + float const x2 = visible_left + visible_width + lr_border(); + float const y2 = visible_top + visible_main_menu_height + tb_border(); if (!customonly) - ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color()); + { + if (heading_layout) + { + ui().draw_outlined_box( + container(), + x1, y1 - top_extra_menu_height, + x2, y1 - m_customtop - tb_border(), + UI_GREEN_COLOR); + heading_layout->emit(container(), (1.0F - heading_layout->width()) * 0.5F, y1 - top_extra_menu_height + tb_border()); + } + ui().draw_outlined_box( + container(), + x1, y1, + x2, y2, + ui().colors().background_color()); + } + + if ((m_selected >= (top_line + m_visible_lines)) || (m_selected < (top_line + 1))) + top_line = m_selected - (m_visible_lines / 2); if (top_line < 0 || is_first_selected()) top_line = 0; - if (m_selected >= (top_line + m_visible_lines)) - top_line = m_selected - (m_visible_lines / 2); - if ((top_line > (m_items.size() - m_visible_lines)) || is_last_selected()) + else if ((top_line > (m_items.size() - m_visible_lines)) || is_last_selected()) top_line = m_items.size() - m_visible_lines; + else if (m_selected >= (top_line + m_visible_lines - 2)) + top_line = m_selected - m_visible_lines + ((m_selected == (m_items.size() - 1)) ? 1: 2); // if scrolling, show arrows bool const show_top_arrow((m_items.size() > m_visible_lines) && !first_item_visible()); @@ -592,8 +593,8 @@ void menu::draw(uint32_t flags) m_visible_items = m_visible_lines - (show_top_arrow ? 1 : 0) - (show_bottom_arrow ? 1 : 0); // determine effective positions taking into account the hilighting arrows - float const effective_width = visible_width - 2.0f * gutter_width; - float const effective_left = visible_left + gutter_width; + float const effective_width = visible_width - 2.0F * gutter_width(); + float const effective_left = visible_left + gutter_width(); // locate mouse if (!customonly && !noinput) @@ -604,36 +605,47 @@ void menu::draw(uint32_t flags) // loop over visible lines m_hover = m_items.size() + 1; bool selected_subitem_too_big = false; - float const line_x0 = x1 + 0.5f * UI_LINE_WIDTH; - float const line_x1 = x2 - 0.5f * UI_LINE_WIDTH; + float const line_x0 = x1 + 0.5F * UI_LINE_WIDTH; + float const line_x1 = x2 - 0.5F * UI_LINE_WIDTH; if (!customonly) { for (int linenum = 0; linenum < m_visible_lines; linenum++) { auto const itemnum = top_line + linenum; menu_item const &pitem = m_items[itemnum]; - char const *const itemtext = pitem.text.c_str(); + std::string_view const itemtext = pitem.text(); rgb_t fgcolor = ui().colors().text_color(); rgb_t bgcolor = ui().colors().text_bg_color(); rgb_t fgcolor2 = ui().colors().subitem_color(); rgb_t fgcolor3 = ui().colors().clone_color(); - float const line_y0 = visible_top + (float)linenum * line_height; - float const line_y1 = line_y0 + line_height; + float const line_y0 = visible_top + (float)linenum * line_height(); + float const line_y1 = line_y0 + line_height(); + + // work out what we're dealing with + bool const uparrow = !linenum && show_top_arrow; + bool const downarrow = (linenum == (m_visible_lines - 1)) && show_bottom_arrow; // set the hover if this is our item - if (mouse_in_rect(line_x0, line_y0, line_x1, line_y1) && is_selectable(pitem)) - m_hover = itemnum; + bool const hovered = mouse_in_rect(line_x0, line_y0, line_x1, line_y1); + if (hovered) + { + if (uparrow) + m_hover = HOVER_ARROW_UP; + else if (downarrow) + m_hover = HOVER_ARROW_DOWN; + else if (is_selectable(pitem)) + m_hover = itemnum; + } - // if we're selected, draw with a different background if (is_selected(itemnum)) { + // if we're selected, draw with a different background fgcolor = fgcolor2 = fgcolor3 = ui().colors().selected_color(); bgcolor = ui().colors().selected_bg_color(); } - - // else if the mouse is over this item, draw with a different background - else if (itemnum == m_hover) + else if (hovered && (uparrow || downarrow || is_selectable(pitem))) { + // else if the mouse is over this item, draw with a different background fgcolor = fgcolor2 = fgcolor3 = ui().colors().mouseover_color(); bgcolor = ui().colors().mouseover_bg_color(); } @@ -642,77 +654,89 @@ void menu::draw(uint32_t flags) if (bgcolor != ui().colors().text_bg_color()) highlight(line_x0, line_y0, line_x1, line_y1, bgcolor); - if (linenum == 0 && show_top_arrow) + if (uparrow) { // if we're on the top line, display the up arrow draw_arrow( - 0.5f * (x1 + x2) - 0.5f * ud_arrow_width, - line_y0 + 0.25f * line_height, - 0.5f * (x1 + x2) + 0.5f * ud_arrow_width, - line_y0 + 0.75f * line_height, - fgcolor, - ROT0); - if (m_hover == itemnum) - m_hover = HOVER_ARROW_UP; + 0.5F * (x1 + x2) - 0.5F * ud_arrow_width(), + line_y0 + 0.25F * line_height(), + 0.5F * (x1 + x2) + 0.5F * ud_arrow_width(), + line_y0 + 0.75F * line_height(), + fgcolor, + ROT0); } - else if (linenum == m_visible_lines - 1 && show_bottom_arrow) + else if (downarrow) { // if we're on the bottom line, display the down arrow draw_arrow( - 0.5f * (x1 + x2) - 0.5f * ud_arrow_width, - line_y0 + 0.25f * line_height, - 0.5f * (x1 + x2) + 0.5f * ud_arrow_width, - line_y0 + 0.75f * line_height, - fgcolor, - ROT0 ^ ORIENTATION_FLIP_Y); - if (m_hover == itemnum) - m_hover = HOVER_ARROW_DOWN; + 0.5F * (x1 + x2) - 0.5F * ud_arrow_width(), + line_y0 + 0.25F * line_height(), + 0.5F * (x1 + x2) + 0.5F * ud_arrow_width(), + line_y0 + 0.75F * line_height(), + fgcolor, + ROT0 ^ ORIENTATION_FLIP_Y); } - else if (pitem.type == menu_item_type::SEPARATOR) + else if (pitem.type() == menu_item_type::SEPARATOR) { // if we're just a divider, draw a line - container().add_line(visible_left, line_y0 + 0.5f * line_height, visible_left + visible_width, line_y0 + 0.5f * line_height, UI_LINE_WIDTH, ui().colors().border_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + container().add_line(visible_left, line_y0 + 0.5F * line_height(), visible_left + visible_width, line_y0 + 0.5F * line_height(), UI_LINE_WIDTH, ui().colors().border_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } - else if (pitem.subtext.empty()) + else if (pitem.subtext().empty()) { // if we don't have a subitem, just draw the string centered - if (pitem.flags & FLAG_UI_HEADING) + if (pitem.flags() & FLAG_UI_HEADING) { - float heading_width = ui().get_string_width(itemtext); - container().add_line(visible_left, line_y0 + 0.5f * line_height, visible_left + ((visible_width - heading_width) / 2) - lr_border, line_y0 + 0.5f * line_height, UI_LINE_WIDTH, ui().colors().border_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); - container().add_line(visible_left + visible_width - ((visible_width - heading_width) / 2) + lr_border, line_y0 + 0.5f * line_height, visible_left + visible_width, line_y0 + 0.5f * line_height, UI_LINE_WIDTH, ui().colors().border_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + float heading_width = get_string_width(itemtext); + container().add_line(visible_left, line_y0 + 0.5F * line_height(), visible_left + ((visible_width - heading_width) / 2) - lr_border(), line_y0 + 0.5F * line_height(), UI_LINE_WIDTH, ui().colors().border_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + container().add_line(visible_left + visible_width - ((visible_width - heading_width) / 2) + lr_border(), line_y0 + 0.5F * line_height(), visible_left + visible_width, line_y0 + 0.5F * line_height(), UI_LINE_WIDTH, ui().colors().border_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } - ui().draw_text_full(container(), itemtext, effective_left, line_y0, effective_width, - ui::text_layout::CENTER, ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, fgcolor, bgcolor, nullptr, nullptr); + ui().draw_text_full( + container(), + itemtext, + effective_left, line_y0, effective_width, + text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, + mame_ui_manager::NORMAL, fgcolor, bgcolor, + nullptr, nullptr, + line_height()); } else { // otherwise, draw the item on the left and the subitem text on the right - bool const subitem_invert(pitem.flags & FLAG_INVERT); - char const *subitem_text(pitem.subtext.c_str()); + bool const subitem_invert(pitem.flags() & FLAG_INVERT); float item_width, subitem_width; // draw the left-side text - ui().draw_text_full(container(), itemtext, effective_left, line_y0, effective_width, - ui::text_layout::LEFT, ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, fgcolor, bgcolor, &item_width, nullptr); - - if (pitem.flags & FLAG_COLOR_BOX) + ui().draw_text_full( + container(), + itemtext, + effective_left, line_y0, effective_width, + text_layout::text_justify::LEFT, text_layout::word_wrapping::TRUNCATE, + mame_ui_manager::NORMAL, fgcolor, bgcolor, + &item_width, nullptr, + line_height()); + + if (pitem.flags() & FLAG_COLOR_BOX) { - rgb_t color = rgb_t((uint32_t)strtoul(subitem_text, nullptr, 16)); + rgb_t color = rgb_t((uint32_t)strtoul(pitem.subtext().c_str(), nullptr, 16)); // give 2 spaces worth of padding - subitem_width = ui().get_string_width("FF00FF00"); + subitem_width = get_string_width("FF00FF00"); - ui().draw_outlined_box(container(), effective_left + effective_width - subitem_width, line_y0, - effective_left + effective_width, line_y1, color); + ui().draw_outlined_box( + container(), + effective_left + effective_width - subitem_width, line_y0 + (UI_LINE_WIDTH * 2.0F), + effective_left + effective_width, line_y1 - (UI_LINE_WIDTH * 2.0F), + color); } else { + std::string_view subitem_text(pitem.subtext()); + // give 2 spaces worth of padding - item_width += 2.0f * gutter_width; + item_width += 2.0F * gutter_width(); // if the subitem doesn't fit here, display dots - if (ui().get_string_width(subitem_text) > effective_width - item_width) + if (get_string_width(subitem_text) > effective_width - item_width) { subitem_text = "..."; if (is_selected(itemnum)) @@ -720,40 +744,48 @@ void menu::draw(uint32_t flags) } // customize subitem text color - if (!core_stricmp(subitem_text, _("On"))) + if (!core_stricmp(pitem.subtext(), _("On"))) fgcolor2 = rgb_t(0x00,0xff,0x00); - if (!core_stricmp(subitem_text, _("Off"))) + if (!core_stricmp(pitem.subtext(), _("Off"))) fgcolor2 = rgb_t(0xff,0x00,0x00); - if (!core_stricmp(subitem_text, _("Auto"))) + if (!core_stricmp(pitem.subtext(), _("Auto"))) fgcolor2 = rgb_t(0xff,0xff,0x00); // draw the subitem right-justified - ui().draw_text_full(container(), subitem_text, effective_left + item_width, line_y0, effective_width - item_width, - ui::text_layout::RIGHT, ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, subitem_invert ? fgcolor3 : fgcolor2, bgcolor, &subitem_width, nullptr); + ui().draw_text_full( + container(), + subitem_text, + effective_left + item_width, line_y0, effective_width - item_width, + text_layout::text_justify::RIGHT, text_layout::word_wrapping::TRUNCATE, + mame_ui_manager::NORMAL, subitem_invert ? fgcolor3 : fgcolor2, bgcolor, + &subitem_width, nullptr, + line_height()); } // apply arrows - if (is_selected(itemnum) && (pitem.flags & FLAG_LEFT_ARROW)) + if (is_selected(itemnum) && (pitem.flags() & FLAG_LEFT_ARROW)) { + float const l = effective_left + effective_width - subitem_width - gutter_width(); + float const r = l + lr_arrow_width(); draw_arrow( - effective_left + effective_width - subitem_width - gutter_width, - line_y0 + 0.1f * line_height, - effective_left + effective_width - subitem_width - gutter_width + lr_arrow_width, - line_y0 + 0.9f * line_height, + l, line_y0 + 0.1F * line_height(), r, line_y0 + 0.9F * line_height(), fgcolor, ROT90 ^ ORIENTATION_FLIP_X); + if (mouse_in_rect(l, line_y0 + 0.1F * line_height(), r, line_y0 + 0.9F * line_height())) + m_hover = HOVER_UI_LEFT; } - if (is_selected(itemnum) && (pitem.flags & FLAG_RIGHT_ARROW)) + if (is_selected(itemnum) && (pitem.flags() & FLAG_RIGHT_ARROW)) { + float const r = effective_left + effective_width + gutter_width(); + float const l = r - lr_arrow_width(); draw_arrow( - effective_left + effective_width + gutter_width - lr_arrow_width, - line_y0 + 0.1f * line_height, - effective_left + effective_width + gutter_width, - line_y0 + 0.9f * line_height, - fgcolor, - ROT90); + l, line_y0 + 0.1F * line_height(), r, line_y0 + 0.9F * line_height(), + fgcolor, + ROT90); + if (mouse_in_rect(l, line_y0 + 0.1F * line_height(), r, line_y0 + 0.9F * line_height())) + m_hover = HOVER_UI_RIGHT; } } } @@ -763,104 +795,58 @@ void menu::draw(uint32_t flags) if (selected_subitem_too_big) { menu_item const &pitem = selected_item(); - bool const subitem_invert(pitem.flags & FLAG_INVERT); + bool const subitem_invert(pitem.flags() & FLAG_INVERT); auto const linenum = m_selected - top_line; - float const line_y = visible_top + (float)linenum * line_height; - float target_width, target_height; + float const line_y = visible_top + float(linenum) * line_height(); // compute the multi-line target width/height - ui().draw_text_full(container(), pitem.subtext.c_str(), 0, 0, visible_width * 0.75f, - ui::text_layout::RIGHT, ui::text_layout::WORD, mame_ui_manager::NONE, rgb_t::white(), rgb_t::black(), &target_width, &target_height); + auto const [target_width, target_height] = get_text_dimensions( + pitem.subtext(), + 0, 0, visible_width * 0.75F, + text_layout::text_justify::RIGHT, text_layout::word_wrapping::WORD); // determine the target location - float const target_x = visible_left + visible_width - target_width - lr_border; - float target_y = line_y + line_height + ui().box_tb_border(); - if (target_y + target_height + ui().box_tb_border() > visible_main_menu_height) - target_y = line_y - target_height - ui().box_tb_border(); + float const target_x = visible_left + visible_width - target_width - lr_border(); + float target_y = line_y + line_height() + tb_border(); + if (target_y + target_height + tb_border() > visible_main_menu_height) + target_y = line_y - target_height - tb_border(); // add a box around that - ui().draw_outlined_box(container(), target_x - lr_border, - target_y - ui().box_tb_border(), - target_x + target_width + lr_border, - target_y + target_height + ui().box_tb_border(), + ui().draw_outlined_box( + container(), + target_x - lr_border(), target_y - tb_border(), + target_x + target_width + lr_border(), target_y + target_height + tb_border(), subitem_invert ? ui().colors().selected_bg_color() : ui().colors().background_color()); - ui().draw_text_full(container(), pitem.subtext.c_str(), target_x, target_y, target_width, - ui::text_layout::RIGHT, ui::text_layout::WORD, mame_ui_manager::NORMAL, ui().colors().selected_color(), ui().colors().selected_bg_color(), nullptr, nullptr); + ui().draw_text_full( + container(), + pitem.subtext(), + target_x, target_y, target_width, + text_layout::text_justify::RIGHT, text_layout::word_wrapping::WORD, + mame_ui_manager::NORMAL, ui().colors().selected_color(), ui().colors().selected_bg_color(), + nullptr, nullptr); } // if there is something special to add, do it by calling the virtual method custom_render(get_selection_ref(), m_customtop, m_custombottom, x1, y1, x2, y2); } -void menu::custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) +void menu::recompute_metrics(uint32_t width, uint32_t height, float aspect) { -} + float const ui_line_height = ui().get_line_height(); -//------------------------------------------------- -// draw_text_box - draw a multiline -// word-wrapped text box with a menu item at the -// bottom -//------------------------------------------------- + // force whole pixels for line height, gutters and borders + m_line_height = std::floor(ui_line_height * float(height)) / float(height); + m_gutter_width = std::floor(0.5F * ui_line_height * aspect * float(width)) / float(width); + m_tb_border = std::floor(ui().box_tb_border() * float(height)) / float(height); + m_lr_border = std::floor(ui().box_lr_border() * aspect * float(width)) / float(width); -void menu::draw_text_box() + m_lr_arrow_width = 0.4F * m_line_height * aspect; + m_ud_arrow_width = m_line_height * aspect; +} + +void menu::custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) { - const char *text = m_items[0].text.c_str(); - const char *backtext = m_items[1].text.c_str(); - float const aspect = machine().render().ui_aspect(&container()); - float line_height = ui().get_line_height(); - float lr_arrow_width = 0.4f * line_height * aspect; - float gutter_width = lr_arrow_width; - float const lr_border = ui().box_lr_border() * aspect; - float target_width, target_height, prior_width; - float target_x, target_y; - - // compute the multi-line target width/height - ui().draw_text_full(container(), text, 0, 0, 1.0f - 2.0f * lr_border - 2.0f * gutter_width, - ui::text_layout::LEFT, ui::text_layout::WORD, mame_ui_manager::NONE, rgb_t::white(), rgb_t::black(), &target_width, &target_height); - target_height += 2.0f * line_height; - if (target_height > 1.0f - 2.0f * ui().box_tb_border()) - target_height = floorf((1.0f - 2.0f * ui().box_tb_border()) / line_height) * line_height; - - // maximum against "return to prior menu" text - prior_width = ui().get_string_width(backtext) + 2.0f * gutter_width; - target_width = std::max(target_width, prior_width); - - // determine the target location - target_x = 0.5f - 0.5f * target_width; - target_y = 0.5f - 0.5f * target_height; - - // make sure we stay on-screen - if (target_x < lr_border + gutter_width) - target_x = lr_border + gutter_width; - if (target_x + target_width + gutter_width + lr_border > 1.0f) - target_x = 1.0f - lr_border - gutter_width - target_width; - if (target_y < ui().box_tb_border()) - target_y = ui().box_tb_border(); - if (target_y + target_height + ui().box_tb_border() > 1.0f) - target_y = 1.0f - ui().box_tb_border() - target_height; - - // add a box around that - ui().draw_outlined_box(container(), target_x - lr_border - gutter_width, - target_y - ui().box_tb_border(), - target_x + target_width + gutter_width + lr_border, - target_y + target_height + ui().box_tb_border(), - (m_items[0].flags & FLAG_REDTEXT) ? UI_RED_COLOR : ui().colors().background_color()); - ui().draw_text_full(container(), text, target_x, target_y, target_width, - ui::text_layout::LEFT, ui::text_layout::WORD, mame_ui_manager::NORMAL, ui().colors().text_color(), ui().colors().text_bg_color(), nullptr, nullptr); - - // draw the "return to prior menu" text with a hilight behind it - highlight( - target_x + 0.5f * UI_LINE_WIDTH, - target_y + target_height - line_height, - target_x + target_width - 0.5f * UI_LINE_WIDTH, - target_y + target_height, - ui().colors().selected_bg_color()); - ui().draw_text_full(container(), backtext, target_x, target_y + target_height - line_height, target_width, - ui::text_layout::CENTER, ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, ui().colors().selected_color(), ui().colors().selected_bg_color(), nullptr, nullptr); - - // artificially set the hover to the last item so a double-click exits - m_hover = m_items.size() - 1; } @@ -891,8 +877,8 @@ void menu::ignore_mouse() { m_mouse_hit = false; m_mouse_button = false; - m_mouse_x = -1.0f; - m_mouse_y = -1.0f; + m_mouse_x = -1.0F; + m_mouse_y = -1.0F; } @@ -916,33 +902,51 @@ void menu::handle_events(uint32_t flags, event &ev) if (custom_mouse_down()) return; - if ((flags & PROCESS_ONLYCHAR) == 0) + if (!(flags & PROCESS_ONLYCHAR)) { if (m_hover >= 0 && m_hover < m_items.size()) + { m_selected = m_hover; + } else if (m_hover == HOVER_ARROW_UP) { - if ((flags & FLAG_UI_DATS) != 0) + if (flags & PROCESS_CUSTOM_NAV) + { + ev.iptkey = IPT_UI_PAGE_UP; + stop = true; + } + else { + m_selected -= m_visible_items; + if (m_selected < 0) + m_selected = 0; top_line -= m_visible_items - (last_item_visible() ? 1 : 0); - return; } - m_selected -= m_visible_items; - if (m_selected < 0) - m_selected = 0; - top_line -= m_visible_items - (last_item_visible() ? 1 : 0); } else if (m_hover == HOVER_ARROW_DOWN) { - if ((flags & FLAG_UI_DATS) != 0) + if (flags & PROCESS_CUSTOM_NAV) { + ev.iptkey = IPT_UI_PAGE_DOWN; + stop = true; + } + else + { + m_selected += m_visible_lines - 2 + is_first_selected(); + if (m_selected > m_items.size() - 1) + m_selected = m_items.size() - 1; top_line += m_visible_lines - 2; - return; } - m_selected += m_visible_lines - 2 + is_first_selected(); - if (m_selected > m_items.size() - 1) - m_selected = m_items.size() - 1; - top_line += m_visible_lines - 2; + } + else if (m_hover == HOVER_UI_LEFT) + { + ev.iptkey = IPT_UI_LEFT; + stop = true; + } + else if (m_hover == HOVER_UI_RIGHT) + { + ev.iptkey = IPT_UI_RIGHT; + stop = true; } } break; @@ -953,10 +957,12 @@ void menu::handle_events(uint32_t flags, event &ev) { m_selected = m_hover; ev.iptkey = IPT_UI_SELECT; - if (is_last_selected()) + if (is_last_selected() && m_needs_prev_menu_item) { - ev.iptkey = IPT_UI_CANCEL; + ev.iptkey = IPT_UI_BACK; stack_pop(); + if (is_special_main_menu()) + machine().schedule_exit(); } stop = true; } @@ -964,17 +970,14 @@ void menu::handle_events(uint32_t flags, event &ev) // caught scroll event case ui_event::type::MOUSE_WHEEL: - if (!(flags & PROCESS_ONLYCHAR)) + if (!custom_mouse_scroll((0 < local_menu_event.zdelta) ? -local_menu_event.num_lines : local_menu_event.num_lines) && !(flags & (PROCESS_ONLYCHAR | PROCESS_CUSTOM_NAV))) { if (local_menu_event.zdelta > 0) { - if ((flags & FLAG_UI_DATS) != 0) - { - top_line -= local_menu_event.num_lines; - return; - } if (is_first_selected()) + { select_last_item(); + } else { m_selected -= local_menu_event.num_lines; @@ -986,13 +989,10 @@ void menu::handle_events(uint32_t flags, event &ev) } else { - if ((flags & FLAG_UI_DATS)) - { - top_line += local_menu_event.num_lines; - return; - } if (is_last_selected()) + { select_first_item(); + } else { m_selected += local_menu_event.num_lines; @@ -1027,8 +1027,7 @@ void menu::handle_events(uint32_t flags, event &ev) void menu::handle_keys(uint32_t flags, int &iptkey) { - bool ignorepause = stack_has_special_main_menu(); - int code; + bool const ignorepause = (flags & PROCESS_IGNOREPAUSE) || stack_has_special_main_menu(); // bail if no items if (m_items.empty()) @@ -1037,23 +1036,40 @@ void menu::handle_keys(uint32_t flags, int &iptkey) // if we hit select, return true or pop the stack, depending on the item if (exclusive_input_pressed(iptkey, IPT_UI_SELECT, 0)) { - if (is_last_selected()) + if (is_last_selected() && m_needs_prev_menu_item) { - iptkey = IPT_UI_CANCEL; + iptkey = IPT_INVALID; stack_pop(); + if (is_special_main_menu()) + machine().schedule_exit(); } return; } + // UI configure hides the menus + if (!(flags & PROCESS_NOKEYS) && exclusive_input_pressed(iptkey, IPT_UI_MENU, 0) && !m_global_state.stack_has_special_main_menu()) + { + if (is_one_shot()) + stack_pop(); + else + m_global_state.hide_menu(); + return; + } + // bail out - if ((flags & PROCESS_ONLYCHAR)) + if (flags & PROCESS_ONLYCHAR) return; - // hitting cancel also pops the stack - if (exclusive_input_pressed(iptkey, IPT_UI_CANCEL, 0)) + // hitting back also pops the stack + if (exclusive_input_pressed(iptkey, IPT_UI_BACK, 0)) { - if (!menu_has_search_active()) + if (!custom_ui_back()) + { + iptkey = IPT_INVALID; stack_pop(); + if (is_special_main_menu()) + machine().schedule_exit(); + } return; } @@ -1061,13 +1077,10 @@ void menu::handle_keys(uint32_t flags, int &iptkey) validate_selection(1); // swallow left/right keys if they are not appropriate - bool ignoreleft = ((selected_item().flags & FLAG_LEFT_ARROW) == 0); - bool ignoreright = ((selected_item().flags & FLAG_RIGHT_ARROW) == 0); + bool const ignoreleft = !(flags & PROCESS_LR_ALWAYS) && !(selected_item().flags() & FLAG_LEFT_ARROW); + bool const ignoreright = !(flags & PROCESS_LR_ALWAYS) && !(selected_item().flags() & FLAG_RIGHT_ARROW); - if ((m_items[0].flags & FLAG_UI_DATS)) - ignoreleft = ignoreright = false; - - // accept left/right keys as-is with repeat + // accept left/right/prev/next keys as-is with repeat if appropriate if (!ignoreleft && exclusive_input_pressed(iptkey, IPT_UI_LEFT, (flags & PROCESS_LR_REPEAT) ? 6 : 0)) return; if (!ignoreright && exclusive_input_pressed(iptkey, IPT_UI_RIGHT, (flags & PROCESS_LR_REPEAT) ? 6 : 0)) @@ -1076,13 +1089,14 @@ void menu::handle_keys(uint32_t flags, int &iptkey) // up backs up by one item if (exclusive_input_pressed(iptkey, IPT_UI_UP, 6)) { - if ((m_items[0].flags & FLAG_UI_DATS)) + if (flags & PROCESS_CUSTOM_NAV) { - top_line--; return; } - if (is_first_selected()) + else if (is_first_selected()) + { select_last_item(); + } else { --m_selected; @@ -1096,13 +1110,14 @@ void menu::handle_keys(uint32_t flags, int &iptkey) // down advances by one item if (exclusive_input_pressed(iptkey, IPT_UI_DOWN, 6)) { - if ((m_items[0].flags & FLAG_UI_DATS)) + if (flags & PROCESS_CUSTOM_NAV) { - top_line++; return; } - if (is_last_selected()) + else if (is_last_selected()) + { select_first_item(); + } else { ++m_selected; @@ -1116,6 +1131,8 @@ void menu::handle_keys(uint32_t flags, int &iptkey) // page up backs up by m_visible_items if (exclusive_input_pressed(iptkey, IPT_UI_PAGE_UP, 6)) { + if (flags & PROCESS_CUSTOM_NAV) + return; m_selected -= m_visible_items; top_line -= m_visible_items - (last_item_visible() ? 1 : 0); if (m_selected < 0) @@ -1126,6 +1143,8 @@ void menu::handle_keys(uint32_t flags, int &iptkey) // page down advances by m_visible_items if (exclusive_input_pressed(iptkey, IPT_UI_PAGE_DOWN, 6)) { + if (flags & PROCESS_CUSTOM_NAV) + return; m_selected += m_visible_lines - 2 + is_first_selected(); top_line += m_visible_lines - 2; @@ -1136,11 +1155,19 @@ void menu::handle_keys(uint32_t flags, int &iptkey) // home goes to the start if (exclusive_input_pressed(iptkey, IPT_UI_HOME, 0)) + { + if (flags & PROCESS_CUSTOM_NAV) + return; select_first_item(); + } // end goes to the last if (exclusive_input_pressed(iptkey, IPT_UI_END, 0)) + { + if (flags & PROCESS_CUSTOM_NAV) + return; select_last_item(); + } // pause enables/disables pause if (!ignorepause && exclusive_input_pressed(iptkey, IPT_UI_PAUSE, 0)) @@ -1151,17 +1178,26 @@ void menu::handle_keys(uint32_t flags, int &iptkey) machine().pause(); } - // handle a toggle cheats request - if (machine().ui_input().pressed_repeat(IPT_UI_TOGGLE_CHEAT, 0)) - mame_machine_manager::instance()->cheat().set_enable(!mame_machine_manager::instance()->cheat().enabled()); - // see if any other UI keys are pressed if (iptkey == IPT_INVALID) { - for (code = IPT_UI_FIRST + 1; code < IPT_UI_LAST; code++) + for (int code = IPT_UI_FIRST + 1; code < IPT_UI_LAST; code++) { - if (code == IPT_UI_CONFIGURE || (code == IPT_UI_LEFT && ignoreleft) || (code == IPT_UI_RIGHT && ignoreright) || (code == IPT_UI_PAUSE && ignorepause)) - continue; + switch (code) + { + case IPT_UI_LEFT: + if (ignoreleft) + continue; + break; + case IPT_UI_RIGHT: + if (ignoreright) + continue; + break; + case IPT_UI_PAUSE: + if (ignorepause) + continue; + break; + } if (exclusive_input_pressed(iptkey, code, 0)) break; } @@ -1218,11 +1254,52 @@ void menu::validate_selection(int scandir) MENU STACK MANAGEMENT ***************************************************************************/ -void menu::do_handle() +bool menu::do_handle() { - if (m_items.size() < 2) - populate(m_customtop, m_custombottom); - handle(); + bool need_update = false; + + // let OSD do its thing + machine().osd().check_osd_inputs(); + + // recompute metrics if necessary + render_manager &render(machine().render()); + render_target &target(render.ui_target()); + std::pair<uint32_t, uint32_t> const uisize(target.width(), target.height()); + float const aspect = render.ui_aspect(&container()); + if ((uisize != m_last_size) || (aspect != m_last_aspect)) + { + m_last_size = uisize; + m_last_aspect = aspect; + recompute_metrics(uisize.first, uisize.second, aspect); + need_update = true; + } + + if (m_items.empty()) + { + m_rebuilding = true; + try + { + // add an item to return - this is a really hacky way of doing this + if (m_needs_prev_menu_item) + item_append(_("Return to Previous Menu"), 0, nullptr); + + // let implementation add other items + populate(); + need_update = true; + } + catch (...) + { + m_items.clear(); + m_rebuilding = false; + throw; + } + m_rebuilding = false; + } + + auto [eventptr, changed] = process(); + need_update = need_update || changed; + need_update = handle(eventptr) || need_update; + return need_update; } @@ -1235,26 +1312,10 @@ void menu::do_handle() // and calls the menu handler //------------------------------------------------- -uint32_t menu::ui_handler(render_container &container, mame_ui_manager &mui) +delegate<uint32_t (render_container &)> menu::get_ui_handler(mame_ui_manager &mui) { - global_state_ptr const state(get_global_state(mui.machine())); - - // if we have no menus stacked up, start with the main menu - if (!state->topmost_menu<menu>()) - state->stack_push(std::unique_ptr<menu>(make_unique_clear<menu_main>(mui, container))); - - // update the menu state - if (state->topmost_menu<menu>()) - state->topmost_menu<menu>()->do_handle(); - - // clear up anything pending to be released - state->clear_free_list(); - - // if the menus are to be hidden, return a cancel here - if (mui.is_menu_active() && ((mui.machine().ui_input().pressed(IPT_UI_CONFIGURE) && !state->stack_has_special_main_menu()) || !state->topmost_menu<menu>())) - return UI_HANDLER_CANCEL; - - return 0; + global_state &state(get_global_state(mui)); + return delegate<uint32_t (render_container &)>(&global_state::ui_handler, &state); } /*************************************************************************** @@ -1262,12 +1323,22 @@ uint32_t menu::ui_handler(render_container &container, mame_ui_manager &mui) ***************************************************************************/ //------------------------------------------------- +// create_layout +//------------------------------------------------- + +text_layout menu::create_layout(float width, text_layout::text_justify justify, text_layout::word_wrapping wrap) +{ + return text_layout(*ui().get_font(), line_height() * m_last_aspect, line_height(), width, justify, wrap); +} + + +//------------------------------------------------- // highlight //------------------------------------------------- void menu::highlight(float x0, float y0, float x1, float y1, rgb_t bgcolor) { - container().add_quad(x0, y0, x1, y1, bgcolor, m_global_state->hilight_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1) | PRIMFLAG_PACKABLE); + container().add_quad(x0, y0, x1, y1, bgcolor, m_global_state.hilight_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(1) | PRIMFLAG_PACKABLE); } @@ -1277,7 +1348,7 @@ void menu::highlight(float x0, float y0, float x1, float y1, rgb_t bgcolor) void menu::draw_arrow(float x0, float y0, float x1, float y1, rgb_t fgcolor, uint32_t orientation) { - container().add_quad(x0, y0, x1, y1, fgcolor, m_global_state->arrow_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXORIENT(orientation) | PRIMFLAG_PACKABLE); + container().add_quad(x0, y0, x1, y1, fgcolor, m_global_state.arrow_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXORIENT(orientation) | PRIMFLAG_PACKABLE); } @@ -1286,10 +1357,10 @@ void menu::draw_arrow(float x0, float y0, float x1, float y1, rgb_t fgcolor, uin // or footer text //------------------------------------------------- -void menu::extra_text_draw_box(float origx1, float origx2, float origy, float yspan, const char *text, int direction) +void menu::extra_text_draw_box(float origx1, float origx2, float origy, float yspan, std::string_view text, int direction) { // get the size of the text - auto layout = ui().create_layout(container()); + auto layout = create_layout(); layout.add_text(text); // position this extra text @@ -1300,8 +1371,8 @@ void menu::extra_text_draw_box(float origx1, float origx2, float origy, float ys ui().draw_outlined_box(container(), x1, y1, x2, y2, ui().colors().background_color()); // take off the borders - x1 += ui().box_lr_border() * machine().render().ui_aspect(&container()); - y1 += ui().box_tb_border(); + x1 += lr_border(); + y1 += tb_border(); // draw the text within it layout.emit(container(), x1, y1); @@ -1311,8 +1382,8 @@ void menu::extra_text_draw_box(float origx1, float origx2, float origy, float ys void menu::draw_background() { // draw background image if available - if (ui().options().use_background_image() && m_global_state->bgrnd_bitmap() && m_global_state->bgrnd_bitmap()->valid()) - container().add_quad(0.0f, 0.0f, 1.0f, 1.0f, rgb_t::white(), m_global_state->bgrnd_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + if (ui().options().use_background_image() && m_global_state.bgrnd_bitmap() && m_global_state.bgrnd_bitmap()->valid()) + container().add_quad(0.0F, 0.0F, 1.0F, 1.0F, rgb_t::white(), m_global_state.bgrnd_texture(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } @@ -1324,14 +1395,14 @@ void menu::draw_background() void menu::extra_text_position(float origx1, float origx2, float origy, float yspan, text_layout &layout, int direction, float &x1, float &y1, float &x2, float &y2) { - float width = layout.actual_width() + (2 * ui().box_lr_border() * machine().render().ui_aspect(&container())); + float width = layout.actual_width() + (2 * lr_border()); float maxwidth = std::max(width, origx2 - origx1); // compute our bounds - x1 = 0.5f - 0.5f * maxwidth; + x1 = 0.5F - (0.5F * maxwidth); x2 = x1 + maxwidth; y1 = origy + (yspan * direction); - y2 = origy + (ui().box_tb_border() * direction); + y2 = origy + (tb_border() * direction); if (y1 > y2) std::swap(y1, y2); @@ -1343,14 +1414,11 @@ void menu::extra_text_position(float origx1, float origx2, float origy, float ys // and footer text //------------------------------------------------- -void menu::extra_text_render(float top, float bottom, float origx1, float origy1, float origx2, float origy2, const char *header, const char *footer) +void menu::extra_text_render(float top, float bottom, float origx1, float origy1, float origx2, float origy2, std::string_view header, std::string_view footer) { - header = (header && *header) ? header : nullptr; - footer = (footer && *footer) ? footer : nullptr; - - if (header != nullptr) + if (!header.empty()) extra_text_draw_box(origx1, origx2, origy1, top, header, -1); - if (footer != nullptr) + if (!footer.empty()) extra_text_draw_box(origx1, origx2, origy2, bottom, footer, +1); } |