diff options
Diffstat (limited to 'src/frontend/mame/ui/confswitch.cpp')
-rw-r--r-- | src/frontend/mame/ui/confswitch.cpp | 541 |
1 files changed, 541 insertions, 0 deletions
diff --git a/src/frontend/mame/ui/confswitch.cpp b/src/frontend/mame/ui/confswitch.cpp new file mode 100644 index 00000000000..cf69bb7cfbb --- /dev/null +++ b/src/frontend/mame/ui/confswitch.cpp @@ -0,0 +1,541 @@ +// license:BSD-3-Clause +// copyright-holders:Nicola Salmoria, Aaron Giles, Nathan Woods, Vas Crabb +/********************************************************************* + + ui/confswitch.cpp + + Configuration/DIP switches menu. + +*********************************************************************/ + +#include "emu.h" +#include "ui/confswitch.h" + +#include "uiinput.h" + +#include <algorithm> +#include <cstring> + + +namespace ui { + +namespace { + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +// DIP switch rendering parameters in terms of line height +constexpr float DIP_SWITCH_HEIGHT = 1.6f; +constexpr float DIP_SWITCH_SPACING = DIP_SWITCH_HEIGHT / 5.0f; +constexpr float SINGLE_TOGGLE_SWITCH_FIELD_WIDTH = DIP_SWITCH_HEIGHT / 2.0f; +constexpr float SINGLE_TOGGLE_SWITCH_WIDTH = SINGLE_TOGGLE_SWITCH_FIELD_WIDTH * 0.8f; +// make the switch nub 80% of the width space and 1/2 of the switch height +constexpr float SINGLE_TOGGLE_SWITCH_HEIGHT = (DIP_SWITCH_HEIGHT / 2.0f) * 0.8f; + +} // anonymous namespace + + + +/*------------------------------------------------- + menu_confswitch +-------------------------------------------------*/ + +menu_confswitch::field_descriptor::field_descriptor(ioport_field &f) noexcept + : field(f) +{ +} + + +menu_confswitch::switch_group_descriptor::switch_group_descriptor(ioport_field const &f, ioport_diplocation const &loc) noexcept + : name(loc.name()) + , owner(f.device()) + , mask(0U) + , state(0U) +{ + std::fill(std::begin(toggles), std::end(toggles), toggle{ nullptr, 0U }); +} + + +inline bool menu_confswitch::switch_group_descriptor::matches(ioport_field const &f, ioport_diplocation const &loc) const noexcept +{ + return (&owner.get() == &f.device()) && !strcmp(loc.name(), name); +} + + +inline unsigned menu_confswitch::switch_group_descriptor::switch_count() const noexcept +{ + return (sizeof(mask) * 8) - count_leading_zeros_32(mask); +} + + +menu_confswitch::menu_confswitch(mame_ui_manager &mui, render_container &container, uint32_t type) + : menu(mui, container) + , m_fields() + , m_switch_groups() + , m_active_switch_groups(0U) + , m_type(type) + , m_changed(false) +{ +} + + +menu_confswitch::~menu_confswitch() +{ +} + + +void menu_confswitch::menu_activated() +{ + // switches can have input assignments, and scripts are a thing + reset(reset_options::REMEMBER_REF); +} + + +void menu_confswitch::populate() +{ + // locate relevant fields if necessary + if (m_fields.empty()) + find_fields(); + + // reset switch group masks + m_active_switch_groups = 0U; + for (switch_group_descriptor &group : m_switch_groups) + group.mask = group.state = 0U; + + // loop over input ports and set up the current values + device_t *prev_owner(nullptr); + for (field_descriptor &desc : m_fields) + { + ioport_field &field(desc.field); + if (field.enabled()) + { + if (!field.settings().empty()) + { + // add a device heading if necessary + if (&field.device() != prev_owner) + { + prev_owner = &field.device(); + if (prev_owner->owner()) + item_append(string_format(_("%1$s [root%2$s]"), prev_owner->type().fullname(), prev_owner->tag()), FLAG_UI_HEADING | FLAG_DISABLE, nullptr); + else + item_append(string_format(_("[root%1$s]"), prev_owner->tag()), FLAG_UI_HEADING | FLAG_DISABLE, nullptr); + } + + // set the left/right flags appropriately + uint32_t flags(0U); + if (field.has_previous_setting()) + flags |= FLAG_LEFT_ARROW; + if (field.has_next_setting()) + flags |= FLAG_RIGHT_ARROW; + + // add the menu item + item_append(field.name(), field.setting_name(), flags, &field); + } + + // track switch groups + if (!field.diplocations().empty()) + { + // get current settings + ioport_field::user_settings settings; + field.get_user_settings(settings); + + // iterate over each bit in the field + ioport_value accummask(field.mask()); + for (ioport_diplocation const &loc : field.diplocations()) + { + // find the matching switch group + switch_group_descriptor &group( + *std::find_if( + m_switch_groups.begin(), + m_switch_groups.end(), [&field, &loc] (switch_group_descriptor const &sw) { return sw.matches(field, loc); })); + + // count if this is the first switch in the group + if (!group.mask) + ++m_active_switch_groups; + + // apply the bits + ioport_value const mask(accummask & ~(accummask - 1)); + group.toggles[loc.number() - 1].field = &field; + group.toggles[loc.number() - 1].mask = mask; + group.mask |= uint32_t(1) << (loc.number() - 1); + if (((settings.value & mask) && !loc.inverted()) || (!(settings.value & mask) && loc.inverted())) + group.state |= uint32_t(1) << (loc.number() - 1); + + // clear the relevant bit in the accumulated mask + accummask &= ~mask; + } + } + } + } + + item_append(menu_item_type::SEPARATOR); + item_append(_("Reset System"), 0, (void *)1); +} + + +bool menu_confswitch::handle(event const *ev) +{ + bool const was_changed(std::exchange(m_changed, false)); + bool need_update(false); + if (ev && (IPT_CUSTOM == ev->iptkey)) + { + // clicked a switch + m_changed = true; + } + else if (!ev || !ev->itemref) + { + // no user input + } + else if (uintptr_t(ev->itemref) == 1U) + { + // reset + if (ev->iptkey == IPT_UI_SELECT) + machine().schedule_hard_reset(); + } + else + { + // actual settings + ioport_field &field(*reinterpret_cast<ioport_field *>(ev->itemref)); + + switch (ev->iptkey) + { + // left goes to previous setting + case IPT_UI_LEFT: + field.select_previous_setting(); + m_changed = true; + break; + + // right goes to next setting + case IPT_UI_SELECT: + case IPT_UI_RIGHT: + field.select_next_setting(); + m_changed = true; + break; + + // if cleared, reset to default value + case IPT_UI_CLEAR: + { + ioport_field::user_settings settings; + field.get_user_settings(settings); + if (field.defvalue() != settings.value) + { + settings.value = field.defvalue(); + field.set_user_settings(settings); + m_changed = true; + } + } + break; + + // trick to get previous group - depend on headings having null reference + case IPT_UI_PREV_GROUP: + { + auto current = selected_index(); + bool found_break = false; + while (0 < current) + { + if (!found_break) + { + if (!item(--current).ref()) + found_break = true; + } + else if (!item(current - 1).ref()) + { + set_selected_index(current); + set_top_line(current - 1); + need_update = true; + break; + } + else + { + --current; + } + } + } + break; + + // trick to get next group - depend on special item references + case IPT_UI_NEXT_GROUP: + { + auto current = selected_index(); + while (item_count() > ++current) + { + if (!item(current).ref()) + { + if ((item_count() > (current + 1)) && (uintptr_t(item(current + 1).ref()) != 1)) + { + set_selected_index(current + 1); + set_top_line(current); + need_update = true; + } + break; + } + } + } + break; + } + } + + // changing settings triggers an item rebuild because it can affect whether things are enabled + if (m_changed || was_changed) + reset(reset_options::REMEMBER_REF); + return need_update; +} + + +void menu_confswitch::find_fields() +{ + assert(m_fields.empty()); + assert(m_switch_groups.empty()); + + // find relevant input ports + for (auto &port : machine().ioport().ports()) + { + for (ioport_field &field : port.second->fields()) + { + if (field.type() == m_type) + { + m_fields.emplace_back(field); + + // iterate over locations + for (ioport_diplocation const &loc : field.diplocations()) + { + auto const found( + std::find_if( + m_switch_groups.begin(), + m_switch_groups.end(), [&field, &loc] (switch_group_descriptor const &sw) { return sw.matches(field, loc); })); + if (m_switch_groups.end() == found) + m_switch_groups.emplace_back(field, loc); + } + } + } + } +} + + + +/*------------------------------------------------- + menu_settings_dip_switches +-------------------------------------------------*/ + +menu_settings_dip_switches::menu_settings_dip_switches(mame_ui_manager &mui, render_container &container) + : menu_confswitch(mui, container, IPT_DIPSWITCH) + , m_switch_group_y() + , m_visible_switch_groups(0U) + , m_single_width(0.0f) + , m_nub_width(0.0f) + , m_first_nub(0.0f) + , m_clickable_height(0.0f) +{ + set_heading(_("DIP Switches")); +} + + +menu_settings_dip_switches::~menu_settings_dip_switches() +{ +} + + +void menu_settings_dip_switches::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + menu_confswitch::recompute_metrics(width, height, aspect); + + set_custom_space( + 0.0f, + m_visible_switch_groups + ? ((m_visible_switch_groups * (DIP_SWITCH_HEIGHT * line_height())) + ((m_visible_switch_groups - 1) * (DIP_SWITCH_SPACING * line_height())) + (tb_border() * 3.0f)) + : 0.0f); +} + + +void menu_settings_dip_switches::custom_render(uint32_t flags, void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2) +{ + // catch if no DIP locations have to be drawn + if (!m_visible_switch_groups) + return; + + // calculate optimal width + float const maxwidth(1.0f - (lr_border() * 2.0f)); + m_single_width = (line_height() * SINGLE_TOGGLE_SWITCH_FIELD_WIDTH * x_aspect()); + float width(0.0f); + unsigned maxswitches(0U); + for (switch_group_descriptor const &group : switch_groups()) + { + if (group.mask) + { + maxswitches = (std::max)(group.switch_count(), maxswitches); + float const namewidth(get_string_width(group.name)); + float const switchwidth(m_single_width * maxswitches); + width = (std::min)((std::max)(namewidth + switchwidth + (line_height() * x_aspect()), width), maxwidth); + } + } + + // draw extra menu area + float const boxwidth((std::max)(width + (lr_border() * 2.0f), origx2 - origx1)); + float const boxleft((1.0f - boxwidth) * 0.5f); + ui().draw_outlined_box(container(), boxleft, origy2 + tb_border(), boxleft + boxwidth, origy2 + bottom, ui().colors().background_color()); + + // calculate centred layout + float const nameleft((1.0f - width) * 0.5f); + float const switchleft(nameleft + width - (m_single_width * maxswitches)); + float const namewidth(width - (m_single_width * maxswitches) - (line_height() * x_aspect())); + + // iterate over switch groups + ioport_field *const field((uintptr_t(selectedref) != 1U) ? reinterpret_cast<ioport_field *>(selectedref) : nullptr); + float const nubheight(line_height() * SINGLE_TOGGLE_SWITCH_HEIGHT); + m_nub_width = line_height() * SINGLE_TOGGLE_SWITCH_WIDTH * x_aspect(); + float const ygap(line_height() * ((DIP_SWITCH_HEIGHT * 0.5f) - SINGLE_TOGGLE_SWITCH_HEIGHT) * 0.5f); + float const xgap((m_single_width + (UI_LINE_WIDTH * 0.5f) - m_nub_width) * 0.5f); + m_first_nub = switchleft + xgap; + m_clickable_height = (line_height() * DIP_SWITCH_HEIGHT) - (ygap * 2.0f); + unsigned line(0U); + for (unsigned n = 0; switch_groups().size() > n; ++n) + { + switch_group_descriptor const &group(switch_groups()[n]); + if (group.mask) + { + // determine the mask of selected bits + uint32_t selectedmask(0U); + if (field) + { + for (ioport_diplocation const &loc : field->diplocations()) + if (group.matches(*field, loc)) + selectedmask |= uint32_t(1) << (loc.number() - 1); + } + + // draw the name + float const liney(origy2 + (tb_border() * 2.0f) + (line_height() * (DIP_SWITCH_HEIGHT + DIP_SWITCH_SPACING) * line)); + draw_text_normal( + group.name, + nameleft, liney + (line_height() * (DIP_SWITCH_HEIGHT - 1.0f) / 2.0f), namewidth, + text_layout::text_justify::RIGHT, text_layout::word_wrapping::NEVER, + ui().colors().text_color()); + + // draw the group outline + float const switchbottom(liney + (DIP_SWITCH_HEIGHT * line_height())); + unsigned const cnt(group.switch_count()); + ui().draw_outlined_box( + container(), + switchleft, liney, switchleft + (m_single_width * cnt), switchbottom, + ui().colors().background_color()); + for (unsigned i = 1; cnt > i; ++i) + { + container().add_line( + switchleft + (m_single_width * i), liney, switchleft + (m_single_width * i), switchbottom, + UI_LINE_WIDTH, ui().colors().text_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + } + + // compute top and bottom for on and off positions + float const yoff(liney + UI_LINE_WIDTH + ygap); + float const yon(switchbottom - UI_LINE_WIDTH - ygap - nubheight); + m_switch_group_y[n] = yoff; + + // draw the switch nubs + for (unsigned toggle = 0; cnt > toggle; ++toggle) + { + float const nubleft(switchleft + (m_single_width * toggle) + xgap); + if (BIT(group.mask, toggle)) + { + float const nubtop(BIT(group.state, toggle) ? yon : yoff); + container().add_rect( + nubleft, nubtop, nubleft + m_nub_width, nubtop + nubheight, + BIT(selectedmask, toggle) ? ui().colors().dipsw_color() : ui().colors().text_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + } + else + { + container().add_rect( + nubleft, yoff, nubleft + m_nub_width, yon + nubheight, + ui().colors().unavailable_color(), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + } + } + + // limit the number of visible switch groups + if (++line >= m_visible_switch_groups) + break; + } + } +} + + +std::tuple<int, bool, bool> menu_settings_dip_switches::custom_pointer_updated(bool changed, ui_event const &uievt) +{ + if (!m_visible_switch_groups || !(uievt.pointer_pressed & 0x01) || (uievt.pointer_buttons & ~u32(0x01))) + return std::make_tuple(IPT_INVALID, false, false); + + auto const [cx, y] = pointer_location(); + if (cx < m_first_nub) + return std::make_tuple(IPT_INVALID, false, false); + + float const x(cx - m_first_nub); + for (unsigned n = 0U, line = 0U; (switch_groups().size() > n) && (m_visible_switch_groups > line); ++n) + { + switch_group_descriptor const &group(switch_groups()[n]); + if (group.mask) + { + ++line; + if ((y >= m_switch_group_y[n]) && (y < (m_switch_group_y[n] + m_clickable_height))) + { + unsigned const cnt(group.switch_count()); + for (unsigned i = 0U; cnt > i; ++i) + { + if (BIT(group.mask, i)) + { + float const xstart(float(i) * m_single_width); + if ((x >= xstart) && (x < (xstart + m_nub_width))) + { + ioport_field::user_settings settings; + group.toggles[i].field->get_user_settings(settings); + settings.value ^= group.toggles[i].mask; + group.toggles[i].field->set_user_settings(settings); + return std::make_tuple(IPT_CUSTOM, true, true); + } + } + } + } + } + } + + return std::make_tuple(IPT_INVALID, false, false); +} + + +void menu_settings_dip_switches::populate() +{ + // let the base class add items + menu_confswitch::populate(); + + // use up to about 70% of height for DIP switch display + if (active_switch_groups()) + { + m_switch_group_y.resize(switch_groups().size()); + float const groupheight(DIP_SWITCH_HEIGHT * line_height()); + float const groupspacing(DIP_SWITCH_SPACING * line_height()); + if ((active_switch_groups() * (groupheight + groupspacing)) > 0.7f) + m_visible_switch_groups = unsigned(0.7f / (groupheight + groupspacing)); + else + m_visible_switch_groups = active_switch_groups(); + set_custom_space(0.0f, (m_visible_switch_groups * groupheight) + ((m_visible_switch_groups - 1) * groupspacing) + (tb_border() * 3.0f)); + } + else + { + m_visible_switch_groups = 0U; + set_custom_space(0.0f, 0.0f); + } +} + + + +/*------------------------------------------------- + menu_settings_machine_config +-------------------------------------------------*/ + +menu_settings_machine_config::menu_settings_machine_config(mame_ui_manager &mui, render_container &container) : menu_confswitch(mui, container, IPT_CONFIG) +{ + set_heading(_("Machine Configuration")); +} + +menu_settings_machine_config::~menu_settings_machine_config() +{ +} + +} // namespace ui |