summaryrefslogblamecommitdiffstatshomepage
path: root/src/frontend/mame/ui/inputmap.cpp
blob: 33cbd9223e0f1416f9808d3dcfefec5efb949f7e (plain) (tree)
1
2
3
4
5
6
7
8

                                                               

                                                                      
                   


                                      


                                                                      
                        


                    
 

                    
 
              
 
                                                   

                                               

                                                   
                                                                                                              


 
                                       
 
 
 




                                                                               
         
                                                                         
                                                                                   
         
                                                                                  

 
                                
 



                                                                                                                 


 




                                                   


                                                                                                     
 

 



                                         
                                                                        
 


                                     
 
                                                                  
                                                                                
                 

                                                                                        
                         


















                                                                                                                                                      

                         








                                                                                                             

                                                  
                                                 

 
                                                                      
 

                                                                                                                  

 
 




                                                   
                                                                                                                        


 



                                           
                                                                         
 
                         
         
                                     
 



                                                                         
                         



                                                                                                                                                                      
                                 


















                                                                                                                                   



                                 










































                                                                                                                     

                                                  
                                                 

 
                                                                       
 

                                             

                                                                                                
                                                                  
                                                                                                              

 
 


                                                   



                                                                         


                                     

                            
 

 
                         







                                                   
                                                                                                                        
 
                                                                                         
                                               
                                           


                                     



                                                                                                                  
                                                                                            






                                                                                                    






























                                                                                                                    

 
                         
 
                                                  
                                
 


                                                                                               
         
                                                              
                                                          
 
                                                                
                 
                                                         
                                              
                                                 
                         
                                                                                          





                                                                                            
                                                                                       

                                                  
                 
                                                                                         
                 
                                              












                                                                            

                 
                                                   
         
                                               
                                                                                                  

                                           
                                                                          








                                                                                                                                     



                                                                                           


                                                                              
                                            
                                               
                              

                 
                                                                         






                                                    
                                            

                                         

         

                                             
         
                                              
 
                                                         


                                  
                                                   
                       
                                                        


 




                                                   
 
                                                                       
 
                                                               
 
                                                                   




                                                            
                         


                                  
                                          
         

                                                                                             
 
                                                                                        



                                                    
                                                                       

                                                                                                

                 
                                                                       
                                     
                                                
 
                                   
                                         
                 
                                                                                             
                                        
                                                                    
                 

                    
                                                                                                            

                                                                              

                 
                               
                                                                               
         


                                                                            

 
                 
// license:BSD-3-Clause
// copyright-holders:Nicola Salmoria, Aaron Giles, Nathan Woods
/*********************************************************************

    ui/inputmap.cpp

    Internal menus for input mappings.

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

#include "emu.h"
#include "ui/inputmap.h"

#include "uiinput.h"
#include "ui/ui.h"

#include <algorithm>


namespace ui {

/*-------------------------------------------------
    menu_input_groups - handle the input groups
    menu
-------------------------------------------------*/

menu_input_groups::menu_input_groups(mame_ui_manager &mui, render_container &container) : menu(mui, container)
{
}

menu_input_groups::~menu_input_groups()
{
}

void menu_input_groups::populate(float &customtop, float &custombottom)
{
	// build up the menu
	item_append(_("User Interface"), "", 0, (void *)uintptr_t(IPG_UI + 1));
	for (int player = 0; player < MAX_PLAYERS; player++)
	{
		auto s = string_format("Player %d Controls", player + 1);
		item_append(s, "", 0, (void *)uintptr_t(IPG_PLAYER1 + player + 1));
	}
	item_append(_("Other Controls"), "", 0, (void *)uintptr_t(IPG_OTHER + 1));
}

void menu_input_groups::handle()
{
	// process the menu
	const event *const menu_event = process(0);
	if (menu_event && menu_event->iptkey == IPT_UI_SELECT)
		menu::stack_push<menu_input_general>(ui(), container(), int(uintptr_t(menu_event->itemref) - 1));
}


/*-------------------------------------------------
    menu_input_general - handle the general
    input menu
-------------------------------------------------*/

menu_input_general::menu_input_general(mame_ui_manager &mui, render_container &container, int _group)
	: menu_input(mui, container)
	, group(_group)
{
}

menu_input_general::~menu_input_general()
{
}

void menu_input_general::populate(float &customtop, float &custombottom)
{
	if (data.empty())
	{
		assert(!pollingitem);

		// iterate over the input ports and add menu items
		for (const input_type_entry &entry : machine().ioport().types())
		{
			// add if we match the group and we have a valid name
			if ((entry.group() == group) && entry.name() && entry.name()[0])
			{
				// loop over all sequence types
				for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; ++seqtype)
				{
					// build an entry for the standard sequence
					input_item_data &item(*data.emplace(data.end()));
					item.ref = &entry;
					item.seqtype = seqtype;
					item.seq = machine().ioport().type_seq(entry.type(), entry.player(), seqtype);
					item.defseq = &entry.defseq(seqtype);
					item.group = entry.group();
					item.type = ioport_manager::type_is_analog(entry.type()) ? (INPUT_TYPE_ANALOG + seqtype) : INPUT_TYPE_DIGITAL;
					item.is_optional = false;
					item.name = entry.name();
					item.owner_name = nullptr;

					// stop after one, unless we're analog
					if (item.type == INPUT_TYPE_DIGITAL)
						break;
				}
			}
		}
	}
	else
	{
		for (input_item_data &item : data)
		{
			const input_type_entry &entry(*reinterpret_cast<const input_type_entry *>(item.ref));
			item.seq = machine().ioport().type_seq(entry.type(), entry.player(), item.seqtype);
		}
	}

	// populate the menu in a standard fashion
	populate_sorted(customtop, custombottom);
}

void menu_input_general::update_input(input_item_data &seqchangeditem)
{
	const input_type_entry &entry = *reinterpret_cast<const input_type_entry *>(seqchangeditem.ref);
	machine().ioport().set_type_seq(entry.type(), entry.player(), seqchangeditem.seqtype, seqchangeditem.seq);
}


/*-------------------------------------------------
    menu_input_specific - handle the game-specific
    input menu
-------------------------------------------------*/

menu_input_specific::menu_input_specific(mame_ui_manager &mui, render_container &container) : menu_input(mui, container)
{
}

menu_input_specific::~menu_input_specific()
{
}

void menu_input_specific::populate(float &customtop, float &custombottom)
{
	if (data.empty())
	{
		assert(!pollingitem);

		// iterate over the input ports and add menu items
		for (auto &port : machine().ioport().ports())
		{
			for (ioport_field &field : port.second->fields())
			{
				const ioport_type_class type_class = field.type_class();

				// add if it's enabled and it's a system-specific class
				if (field.enabled() && (type_class == INPUT_CLASS_CONTROLLER || type_class == INPUT_CLASS_MISC || type_class == INPUT_CLASS_KEYBOARD))
				{
					// loop over all sequence types
					for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; ++seqtype)
					{
						// build an entry for the standard sequence
						input_item_data &item(*data.emplace(data.end()));
						item.ref = &field;
						item.seqtype = seqtype;
						item.seq = field.seq(seqtype);
						item.defseq = &field.defseq(seqtype);
						item.group = machine().ioport().type_group(field.type(), field.player());
						item.type = field.is_analog() ? (INPUT_TYPE_ANALOG + seqtype) : INPUT_TYPE_DIGITAL;
						item.is_optional = field.optional();
						item.name = field.name();
						item.owner_name = field.device().tag();

						// stop after one, unless we're analog
						if (item.type == INPUT_TYPE_DIGITAL)
							break;
					}
				}
			}
		}

		// sort it
		std::sort(
				data.begin(),
				data.end(),
				[] (const input_item_data &i1, const input_item_data &i2)
				{
					int cmp = strcmp(i1.owner_name, i2.owner_name);
					if (cmp < 0)
						return true;
					if (cmp > 0)
						return false;
					if (i1.group < i2.group)
						return true;
					if (i1.group > i2.group)
						return false;
					const ioport_field &field1 = *reinterpret_cast<const ioport_field *>(i1.ref);
					const ioport_field &field2 = *reinterpret_cast<const ioport_field *>(i2.ref);
					if (field1.type() < field2.type())
						return true;
					if (field1.type() > field2.type())
						return false;
					std::vector<char32_t> codes1 = field1.keyboard_codes(0);
					std::vector<char32_t> codes2 = field2.keyboard_codes(0);
					if (!codes1.empty() && (codes2.empty() || codes1[0] < codes2[0]))
						return true;
					if (!codes2.empty() && (codes1.empty() || codes1[0] > codes2[0]))
						return false;
					cmp = strcmp(i1.name, i2.name);
					if (cmp < 0)
						return true;
					if (cmp > 0)
						return false;
					return i1.type < i2.type;
				});
	}
	else
	{
		for (input_item_data &item : data)
		{
			const ioport_field &field(*reinterpret_cast<const ioport_field *>(item.ref));
			item.seq = field.seq(item.seqtype);
		}
	}

	// populate the menu in a standard fashion
	populate_sorted(customtop, custombottom);
}

void menu_input_specific::update_input(input_item_data &seqchangeditem)
{
	ioport_field::user_settings settings;

	// yeah, the const_cast is naughty, but we know we stored a non-const reference in it
	reinterpret_cast<const ioport_field *>(seqchangeditem.ref)->get_user_settings(settings);
	settings.seq[seqchangeditem.seqtype] = seqchangeditem.seq;
	reinterpret_cast<ioport_field *>(const_cast<void *>(seqchangeditem.ref))->set_user_settings(settings);
}


/*-------------------------------------------------
    menu_input - display a menu for inputs
-------------------------------------------------*/
menu_input::menu_input(mame_ui_manager &mui, render_container &container)
	: menu(mui, container)
	, data()
	, pollingitem(nullptr)
	, seq_poll(machine().input())
	, errormsg()
	, erroritem(nullptr)
	, lastitem(nullptr)
	, record_next(false)
{
}

menu_input::~menu_input()
{
}

/*-------------------------------------------------
    toggle_none_default - toggle between "NONE"
    and the default item
-------------------------------------------------*/

void menu_input::toggle_none_default(input_seq &selected_seq, input_seq &original_seq, const input_seq &selected_defseq)
{
	if (original_seq.empty()) // if we used to be "none", toggle to the default value
		selected_seq = selected_defseq;
	else // otherwise, toggle to "none"
		selected_seq.reset();
}

void menu_input::custom_render(void *selectedref, float top, float bottom, float x1, float y1, float x2, float y2)
{
	if (pollingitem)
	{
		const std::string seqname = machine().input().seq_name(seq_poll.sequence());
		char const *const text[] = { seqname.c_str() };
		draw_text_box(
				std::begin(text), std::end(text),
				x1, x2, y2 + ui().box_tb_border(), y2 + bottom,
				ui::text_layout::CENTER, ui::text_layout::NEVER, false,
				ui().colors().text_color(), ui().colors().background_color(), 1.0f);
	}
	else
	{
		if (erroritem && (selectedref != erroritem))
		{
			errormsg.clear();
			erroritem = nullptr;
		}

		if (erroritem)
		{
			char const *const text[] = { errormsg.c_str() };
			draw_text_box(
					std::begin(text), std::end(text),
					x1, x2, y2 + ui().box_tb_border(), y2 + bottom,
					ui::text_layout::CENTER, ui::text_layout::NEVER, false,
					ui().colors().text_color(), UI_RED_COLOR, 1.0f);
		}
		else if (selectedref)
		{
			const input_item_data &item = *reinterpret_cast<input_item_data *>(selectedref);
			if ((INPUT_TYPE_ANALOG != item.type) && machine().input().seq_pressed(item.seq))
			{
				char const *const text[] = { _("Pressed") };
				draw_text_box(
						std::begin(text), std::end(text),
						x1, x2, y2 + ui().box_tb_border(), y2 + bottom,
						ui::text_layout::CENTER, ui::text_layout::NEVER, false,
						ui().colors().text_color(), ui().colors().background_color(), 1.0f);
			}
		}
	}
}

void menu_input::handle()
{
	input_item_data *seqchangeditem = nullptr;
	bool invalidate = false;

	// process the menu
	const event *const menu_event = process((pollingitem != nullptr) ? PROCESS_NOKEYS : 0);
	if (pollingitem)
	{
		// if we are polling, handle as a special case
		input_item_data *const item = pollingitem;

		if (machine().ui_input().pressed(IPT_UI_CANCEL))
		{
			// if UI_CANCEL is pressed, abort
			pollingitem = nullptr;
			if (!seq_poll.modified())
			{
				// cancelled immediately - toggle between default and none
				record_next = false;
				toggle_none_default(item->seq, starting_seq, *item->defseq);
				seqchangeditem = item;
			}
			else
			{
				// entered something before cancelling - abandon change
				invalidate = true;
			}
		}
		else if (seq_poll.poll()) // poll again; if finished, update the sequence
		{
			pollingitem = nullptr;
			if (seq_poll.valid())
			{
				record_next = true;
				item->seq = seq_poll.sequence();
				seqchangeditem = item;
			}
			else
			{
				// entered invalid sequence - abandon change
				invalidate = true;
				errormsg = _("Invalid sequence entered");
				erroritem = item;
			}
		}
	}
	else if (menu_event && menu_event->itemref)
	{
		// otherwise, handle the events
		input_item_data &item = *reinterpret_cast<input_item_data *>(menu_event->itemref);
		switch (menu_event->iptkey)
		{
		case IPT_UI_SELECT: // an item was selected: begin polling
			errormsg.clear();
			erroritem = nullptr;
			pollingitem = &item;
			lastitem = &item;
			starting_seq = item.seq;
			if (record_next)
				seq_poll.start((item.type == INPUT_TYPE_ANALOG) ? ITEM_CLASS_ABSOLUTE : ITEM_CLASS_SWITCH, item.seq);
			else
				seq_poll.start((item.type == INPUT_TYPE_ANALOG) ? ITEM_CLASS_ABSOLUTE : ITEM_CLASS_SWITCH);
			invalidate = true;
			break;

		case IPT_UI_CLEAR: // if the clear key was pressed, reset the selected item
			errormsg.clear();
			erroritem = nullptr;
			toggle_none_default(item.seq, item.seq, *item.defseq);
			record_next = false;
			seqchangeditem = &item;
			break;
		}

		// if the selection changed, reset the "record next" flag
		if (&item != lastitem)
		{
			if (erroritem)
			{
				errormsg.clear();
				erroritem = nullptr;
			}
			record_next = false;
			lastitem = &item;
		}
	}

	// if the sequence changed, update it
	if (seqchangeditem)
	{
		update_input(*seqchangeditem);

		// invalidate the menu to force an update
		invalidate = true;
	}

	// if the menu is invalidated, clear it now
	if (invalidate)
		reset(reset_options::REMEMBER_POSITION);
}


//-------------------------------------------------
//  populate_sorted - take a sorted list of
//  input_item_data objects and build up the
//  menu from them
//-------------------------------------------------

void menu_input::populate_sorted(float &customtop, float &custombottom)
{
	const char *nameformat[INPUT_TYPE_TOTAL] = { nullptr };

	// create a mini lookup table for name format based on type
	nameformat[INPUT_TYPE_DIGITAL] = "%s";
	nameformat[INPUT_TYPE_ANALOG] = "%s Analog";
	nameformat[INPUT_TYPE_ANALOG_INC] = "%s Analog Inc";
	nameformat[INPUT_TYPE_ANALOG_DEC] = "%s Analog Dec";

	// build the menu
	std::string text, subtext;
	std::string prev_owner;
	bool first_entry = true;
	for (input_item_data &item : data)
	{
		// generate the name of the item itself, based off the base name and the type
		assert(nameformat[item.type] != nullptr);

		if (item.owner_name && strcmp(item.owner_name, prev_owner.c_str()) != 0)
		{
			if (first_entry)
				first_entry = false;
			else
				item_append(menu_item_type::SEPARATOR);
			item_append(string_format("[root%s]", item.owner_name), "", 0, nullptr);
			prev_owner.assign(item.owner_name);
		}

		text = string_format(nameformat[item.type], item.name);
		if (item.is_optional)
			text = "(" + text + ")";

		uint32_t flags = 0;
		if (&item == pollingitem)
		{
			// if we're polling this item, use some spaces with left/right arrows
			subtext = "   ";
			flags |= FLAG_LEFT_ARROW | FLAG_RIGHT_ARROW;
		}
		else
		{
			// otherwise, generate the sequence name and invert it if different from the default
			subtext = machine().input().seq_name(item.seq);
			flags |= (item.seq != *item.defseq) ? FLAG_INVERT : 0;
		}

		// add the item
		item_append(std::move(text), std::move(subtext), flags, &item);
	}

	// leave space for showing the input sequence below the menu
	custombottom = ui().get_line_height() + 3.0f * ui().box_tb_border();
}

} // namespace ui