Контекстное меню и буфер обмена [InputField]

Простое контекстное меню с такими возможностями как: вырезать, копировать, вставить, удалить и заменить всё. Меню работает с выделенным текстом внутри Input Field. Прежде всего для нас главное научится находить в тексте тот, который был выделен с помощью мышки. По сути нужно найти начальный и конечный индекс в string, а также длину выделенного текста. Таким образом мы получаем точное место, с которым можно уже делать всё что нужно. Проще говоря, работа ведется с конкретным местом в тексте, а не с ним самим. Чтобы, допустим, выделив одно слово где-то, можно было изменить только его, не трогая другие, точно такие же слова. Кроме того, разберемся как копировать текст в буфер обмена системы и обратно.


Для начала в новом Canvas создаем группу кнопок, важный момент тут в том, чтобы родительский трансформ группы, был сделан по общему размеры, так как именно родительский объект будет получать данные о позиции.

Контекстное меню и буфер обмена [InputField]

Настраиваем размеры и цвет группы.

Затем на Canvas вешаем:

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;

public class ContextMenuUI : MonoBehaviour {

	[SerializeField] private RectTransform contextMenu; // группа кнопок
	private static ContextMenuUI _internal;
	private InputField inputField;
	private int startIndex, endIndex, length;
	private string current, currentSelection;

	public static ContextMenuUI use
	{
		get{ return _internal; }
	}

	public void SetInputField(InputField iField)
	{
		inputField = iField;
	}

	public void ShowContextMenu(Vector2 position)
	{
		if(CurrentIndex())
		{
			current = inputField.text;
			currentSelection = SelectionText();
			Vector2 size = contextMenu.sizeDelta;
			if(position.x + size.x > Screen.width) contextMenu.position = new Vector2(position.x - size.x/2, contextMenu.position.y);
			else contextMenu.position = new Vector2(position.x + size.x/2, contextMenu.position.y);
			if(position.y - size.y < 0) contextMenu.position = new Vector2(contextMenu.position.x, position.y + size.y/2);
			else contextMenu.position = new Vector2(contextMenu.position.x, position.y - size.y/2);
			contextMenu.gameObject.SetActive(true);
		}
	}

	void Awake()
	{
		_internal = this;
		contextMenu.gameObject.SetActive(false);
	}

	bool CurrentIndex() // находим место, выделенного текста, и его длину
	{
		if(inputField == null) return false;
		startIndex = Mathf.Min(inputField.selectionFocusPosition, inputField.selectionAnchorPosition);
		endIndex = Mathf.Max(inputField.selectionFocusPosition, inputField.selectionAnchorPosition);
		length = endIndex - startIndex;
		return true;
	}

	string SelectionText() // копируем выделенный текст
	{
		return inputField.text.Substring(startIndex, length);
	}

	void Replace(string newValue)
	{
		if(current.Length == startIndex) // добавление текста
		{
			current += newValue;
		}
		else // или добавление в выделенное место
		{
			string part1 = current.Remove(startIndex);
			string part2 = current.Remove(0, endIndex);
			current = part1 + newValue + part2;
		}
	}

	void Close()
	{
		inputField.text = current; // возвращаем результат
		contextMenu.gameObject.SetActive(false);
	}

	void ReplaceAllText(string oldValue, string newValue)
	{
		current = current.Replace(oldValue, newValue); // замена всех похожих слов
	}

	//  функции для UI кнопок

	public void ReplaceAll()
	{
		ReplaceAllText(currentSelection, GUIUtility.systemCopyBuffer);
		Close();
	}

	public void Remove()
	{
		Replace(null);
		Close();
	}

	public void Paste()
	{
		Replace(GUIUtility.systemCopyBuffer);
		Close();
	}

	public void Cut()
	{
		Copy();
		Remove();
		Close();
	}

	public void Copy()
	{
		GUIUtility.systemCopyBuffer = currentSelection; // копирование в буфер обмена
		Close();
	}
}

Указываем нашу группу и для каждой кнопки, добавляем соответствующее событие:


После настройки кнопок, меню будет готово.

Теперь, чтобы взаимодействовать с нужным Input Field.
На него цепляем небольшой скрипт:

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

[RequireComponent(typeof(InputField))]

public class ContextMenuUIComponent : MonoBehaviour, IPointerClickHandler {

	private InputField inputField;

	void Awake()
	{
		inputField = GetComponent<InputField>();
	}
	
	public void OnPointerClick(PointerEventData eventData)
	{
		if(eventData.button == PointerEventData.InputButton.Left)
		{
			ContextMenuUI.use.SetInputField(inputField);
		}
		else if(eventData.button == PointerEventData.InputButton.Right && inputField.isFocused)
		{
			ContextMenuUI.use.ShowContextMenu(eventData.position);
		}
	}
}

Фильтруем нажатие ЛКМ или ПКМ, чтобы определять текущий InputField, так как их может быть несколько.

Скачать демо:

Внимание! Посетители, находящиеся в группе Гости, не могут скачивать файлы.
Тестировалось на: Unity 5.4.0

Комментариев 6

Офлайн
JEnitro 2 декабря 2017
Спасибо огромное за урок! Интересно было бы посмотреть урок по созданию текстового редактора типа NotePad, с разными "фишками" и сохранением.
Офлайн
Alex2D 25 января 2017
Light,
Спасибо Огромное! :) Заработало копирование! :)
И ведь вроде бы всё, что более-менее к теме относилось там просмотрел, но... -работающего варианта не заметил...
Спасибо еще раз! :)
С Уважением, Алексей!
Офлайн
Light 24 января 2017
Alex2D, пока еще не разбирался с андройдом, но что-то вроде этого надо юзать http://answers.unity3d.com/questions/1232900/copy-to-cilpboard-function.html
Офлайн
Alex2D 24 января 2017
Здравствуйте! Потрясающие уроки у Вас! :) Масса полезного в кратком изложении и что особенно важно - в виде статьи, а не этих новомодных видеотуториалов заполонивших интернет! :) -Читать-то получается обычно гораздо быстрее, чем рассказывают на микрофон, ну и "произвольный доступ к данным" - многого стоит :) -Не надо просматривать часы видео, чтобы найти нужный момент... Очень удобно.

-Отдельное Спасибо Вам за Ваше Время потраченное на это удобное изложение материала! :)

И вот еще вопрос - подскажите, а что можно использовать для работы с буфером обмена под Android вместо GUIUtility.systemCopyBuffer? Есть-ли там простое и красивое решение? Искал и на англоязычных форумах, но, пока ничего рабочего так и не нашел...
С Уважением, Алексей!
Офлайн
Light 30 августа 2016
Цитата: DropDeadRed
Доброго времени. Не знаю, учитываете ли Вы пожелания пользователей, но я попытаюсь. Возможно ли, что мы в будущем увидим статейку о системе внутриигровых ачивментов?Было бы здорово.

Пожелания лучше присылать в ЛС. Они учитываются. А что конкретно будет опубликовано или не будет, покажет время. Поэтому, следите за новыми публикациями.
Офлайн
DropDeadRed 29 августа 2016
Доброго времени. Не знаю, учитываете ли Вы пожелания пользователей, но я попытаюсь. Возможно ли, что мы в будущем увидим статейку о системе внутриигровых ачивментов?Было бы здорово.
Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.
  • Яндекс.Метрика