Графический интерфейс пользователя. Курсор и нажатие кнопки

Электроника

Классическим примером использования графического интерфейса является движение с помощью мышки или кнопок графического объекта — курсора. При этом, если курсор попадает на кнопку, и пользователь нажимает на манипулятор, кнопка меняет свое состояние.

Давайте создадим курсор и с помощью кнопок на плате подвигаем его на графическом дисплее. Также мы сможем с помощью курсора нажать на кнопку
Глоссарий
Для успешного освоения материала рекомендуем вам изучить следующие понятия:
Кнопка
один из элементов интерфейса пользователя компьютерной программы, «нажатие» на который приводит к некоторому действию, заложенному в программе
Курсор
в интерфейсе пользователя — элемент графического интерфейса, указывающий на объект, с которым будет производиться взаимодействие с помощью клавиатуры, мыши или другого устройства управления. Различают текстовый курсор, обозначающий место ввода с клавиатуры; курсор мыши (или указатель мыши) и других указывающих устройств; курсор меню. Кроме указания на объект курсор может также отображать его состояние, например — невозможность взаимодействия
Графический индикатор
наиболее сложный тип индикаторов, позволяющий передавать как символьную информацию, так и рисунки
Видеолекция
Конспект

Задача
  1. Построить абстракцию курсора в программе
  2. Сформировать движение курсора по экрану
  3. Поменять состояние кнопки на «нажата», когда на нее попадает курсор, и пользователь «кликает мышью», то есть в нашем случае нажимает кнопку на плате
Информация для курсора
1. Координаты левого верхнего угла в пикселях экрана

2. Ширина-высота в пикселях экрана

3.
Идентификатор объекта (опционально, т. е. курсор обычно один)

4. Метод отрисовки
Работа на компьютере
1. Копируем предыдущий проект, переименовываем и открываем

2.
В main.c добавляем новый тип для курсора typedef struct Cursor_Data Cursor_Data

3.
Добавляем описание курсора в виде структуры
//описание курсора
struct Cursor_Data
{
	enum Object id; //идентификатор объекта
	uint8_t y;		   	//координаты верхнего левого угла кнопки
	uint8_t x;			//координаты верхнего левого угла кнопки
	uint8_t	height;		//высота кнопки
	uint8_t width;		//ширина кнопки
	uint8_t to_redraw;	//флаг "отрисовать"
    void (*Draw_Cursor)(Cursor_Data *curs_struct, uint8_t clear);
};
4. Задаем объект — курсор
//описание курсора
struct Cursor_Data
{
	enum Object id; //идентификатор объекта
	uint8_t y;		   	//координаты верхнего левого угла кнопки
	uint8_t x;			//координаты верхнего левого угла кнопки
	uint8_t	height;		//высота кнопки
	uint8_t width;		//ширина кнопки
	uint8_t to_redraw;	//флаг "отрисовать"
    void (*Draw_Cursor)(Cursor_Data *curs_struct, uint8_t clear);
};
5. В список идентификации объектов добавляем курсор

6. Добавляем функцию для отрисовки курсора
//отрисовка курсора примитивами
//clear: 0 - рисуем нормально, 1 - рисуем цветом фона (BLACK)
void draw_cursor(Cursor_Data *cur_struct, uint8_t clear)
{
	uint16_t Color, ColorFr;
	Color = WHITE;
	ColorFr = PURPLE;

	if(clear!=0)
	{
		Color= BLACK; //очистка примитивами
		ColorFr = BLACK;
	}
7. Отрисовываем курсор с помощью графических примитивов
	//центр курсора
	uint8_t x_c = cur_struct->x + cur_struct->width/2;
	uint8_t y_c = cur_struct->y + cur_struct->height/2;

	//ветки
	ssd1331_draw_line( cur_struct->x , y_c ,  x_c-5, y_c , Color); //левый горизонт
	//обрамление
	ssd1331_draw_line( cur_struct->x , y_c+1 ,  x_c-5, y_c+1 , ColorFr); //левый горизонт
	ssd1331_draw_line( cur_struct->x , y_c-1 ,  x_c-5, y_c-1 , ColorFr); //левый горизонт

	ssd1331_draw_line( cur_struct->x+cur_struct->width, y_c , x_c+5, y_c , Color); //правый горизонт
	//обрамление
	ssd1331_draw_line( cur_struct->x+cur_struct->width, y_c+1 , x_c+5, y_c+1 , ColorFr); //правый горизонт
	ssd1331_draw_line( cur_struct->x+cur_struct->width, y_c-1 , x_c+5, y_c-1 , ColorFr); //правый горизонт

	ssd1331_draw_line( x_c ,  cur_struct->y,  x_c,  y_c-5 , Color); //верхняя вертикаль
	//обрамление
	ssd1331_draw_line( x_c+1 ,  cur_struct->y,  x_c+1,  y_c-5 , ColorFr); //верхняя вертикаль
	ssd1331_draw_line( x_c-1 ,  cur_struct->y,  x_c-1,  y_c-5 , ColorFr); //верхняя вертикаль


	ssd1331_draw_line( x_c ,  cur_struct->y + cur_struct->height,  x_c,  y_c+5 , Color); //нижняя вертикаль
	//обрамление
	ssd1331_draw_line( x_c+1 ,  cur_struct->y + cur_struct->height,  x_c+1,  y_c+5 , ColorFr); //нижняя вертикаль
	ssd1331_draw_line( x_c-1 ,  cur_struct->y + cur_struct->height,  x_c-1,  y_c+5 , ColorFr); //нижняя вертикаль

	//центральная окружность
	ssd1331_draw_circle(x_c, y_c, 1, Color);
	//обрамление
	ssd1331_draw_circle(x_c, y_c, 2, ColorFr);
8. Событие «раз в 100 мс проверка объектов» изменяем на событие «раз в 10 мс», т. е. будем проверять объекты чуть чаще

9. Добавляем событие «раз в 100 мс проверка кнопок на плате», т. е. каждые 100 мс будем проверять состояния портов, к которым подключены кнопки на монтажной плате

10. Считываем состояние порта, соответствующего кнопке, и создаем событие нажатия кнопки. Значение сохраняется в специальной переменной TSW_Event
Важно
Так как нажатых кнопок может быть несколько, существует приоритет обработки. Первой будет обработана нижняя кнопка, т. е. если она нажата, остальные состояния перед ней будут игнорироваться
//генерируем событие "раз в 100 мс проверка кнопок на плате"
		htim1_event_100ms_count_tsw--; //уменьшаем счетчик для события
		if(htim1_event_100ms_count_tsw==0)
		{//счетчик достиг конца - генерим событие
			htim1_event_100ms_count_tsw = HTIM1_EVENT_100ms_tsw; //установим счетчик в изначальное состояние
			event_100ms_flag_tsw = 1; //установим флаг события

			//обработка нажатий тактовых кнопок, приоритет вниз нарастает
			TSW_Event = None; //до проверки считаем не нажатой ни одну кнопку на плате
			if(HAL_GPIO_ReadPin(BTN5_GPIO_Port, BTN5_Pin) == GPIO_PIN_RESET)
			{//нажали кнопку на плате "влево"
				TSW_Event=Left; //флаг нажатия тактовой кнопки на плате
			}

			if(HAL_GPIO_ReadPin(BTN1_GPIO_Port, BTN1_Pin) == GPIO_PIN_RESET)
			{//нажали кнопку на плате "вправо"
				TSW_Event=Right; //флаг нажатия тактовой кнопки на плате
			}

			if(HAL_GPIO_ReadPin(BTN2_GPIO_Port, BTN2_Pin) == GPIO_PIN_RESET)
			{//нажали кнопку на плате "вверх"
				TSW_Event=Up; //флаг нажатия тактовой кнопки на плате
			}

			if(HAL_GPIO_ReadPin(BTN4_GPIO_Port, BTN4_Pin) == GPIO_PIN_RESET)
			{//нажали кнопку на плате "вниз"
				TSW_Event=Down; //флаг нажатия тактовой кнопки на плате
			}

			if(HAL_GPIO_ReadPin(BTN3_GPIO_Port, BTN3_Pin) == GPIO_PIN_RESET)
			{//нажали кнопку на плате "клик"
				TSW_Event=Click; //флаг нажатия тактовой кнопки на плате
			}

			event_100ms_flag_tsw = 0; //сброс флага события - обработали

		}
11. Создаем значения события в виде перечислений: left, up и т. д.

12. Обрабатываем событие «проверка объектов». Изменяем имена переменных и убираем событие «раз в 3 с нажатие кнопки», так как теперь переключение кнопки происходит физическим нажатием

13. К соответствующим функциям добавляем дополнительные параметры — курсор и события по аппаратным кнопкам

14. Поправляем заголовки соответствующих функций и добавляем обработку курсора
//перерисовка всех объектов
void redraw_objects(struct Button_Data Object[], uint32_t num_objs, struct Cursor_Data *Cursor_)
{
	for(uint32_t i=0; i< num_objs; i++) //перебор всех объектов
	{
		if(Object[i].to_redraw!=0)
		{
			Object[i].Draw_Button(&Object[i], 0); //рисуем с новым состоянием
			Object[i].to_redraw=0;//сброс флага отрисовки объекта
		}
	}

	if(Cursor_->to_redraw!=0)
	{
		Cursor_->Draw_Cursor(Cursor_, 0);//рисуем с новым состоянием
		Cursor_->to_redraw=0;//сброс флага отрисовки
	}
}

//все объекты на перерисовку
void to_redraw_all_objects(struct Button_Data Object[], uint32_t num_objs, struct Cursor_Data *Cursor_)
{
	for(uint32_t i=0; i< num_objs; i++) //перебор всех объектов
	{
		Object[i].to_redraw=1;//устанвока флага отрисовки объекта
	}

	Cursor_->to_redraw =1; //курсор тоже перерисовываем
}
15. Основное изменение по обработке всех событий будет в check_objects:
  • поправляем заголовок
  • определяем центр курсора
  • проверяем события, поступающие от нажатия кнопок. Покажем пример для left
//проверка всех объектов
void check_objects(struct Button_Data Object[], uint32_t num_objs, struct Cursor_Data *Cursor_, enum TactSwitch_Event *TSW)
{



	//центр курсора
	uint8_t x_c = Cursor_->x + Cursor_->width/2;
	uint8_t y_c = Cursor_->y + Cursor_->height/2;


	//проверяем событие нажатия кнопок на плате
	switch(*TSW)
	{
		case Left:
				//нажата кнопка влево
			if(x_c-1 >=0 )
			{ //можем сдвинуть влево - двигаем
				Cursor_->Draw_Cursor(Cursor_, 1); //очистка старого места курсора
				check_draw_obj_by_cursor(Object, num_objs, Cursor_); //для объектов, которые перекрывались курсором ставим флаг безусловной перерисовки
				Cursor_->x--;//новое положение курсора
				Cursor_->to_redraw = 1; // нужно потом перерисовать курсор в новом месте
			}
		break;
Важно
Курсор, двигаясь, стирает свое предыдущее положение. Для того чтобы перерисовать объект, над которым он находился, будет создана специальная функция
16. Отдельно обрабатываем аппаратное нажатие клика, формально — это левая кнопка мыши
case Click:
			//клик мыши
			check_push_obj_by_cursor(Object, num_objs, Cursor_); //для объектов, на которых был центр курсора ставим "нажатыми"
			Cursor_->to_redraw=1; //перерисуем потом курсор, т.к. он затрется объектами на которых он был кликнут, которые перерисуются
			break;
		case None:
			//кнопку на плате отжали
			check_unpush_obj_by_cursor(Object, num_objs, Cursor_); //для всех объектов ставим "отжата"
			break;
		default:
		break;
	}

17. Реализуем три необходимые функции:

  • первая функция — для объектов, которые закрывались курсором, ставит флаг «перерисовать»
//для объектов, которые перекрывались курсором ставим флаг безусловной перерисовки
void check_draw_obj_by_cursor(struct Button_Data Object[], uint32_t num_objs, struct Cursor_Data *Cursor_)
{
	for(uint32_t i=0; i< num_objs; i++)
	{
		if((Object[i].x <= Cursor_->x && Cursor_->x <= Object[i].x + Object[i].width && Object[i].y <= Cursor_->y && Cursor_->y <= Object[i].y + Object[i].height /* левый верхний угол курсора*/) ||
		(Object[i].x <= Cursor_->x+Cursor_->width && Cursor_->x+Cursor_->width <= Object[i].x + Object[i].width && Object[i].y <= Cursor_->y && Cursor_->y <= Object[i].y + Object[i].height /* правый верхний угол курсора*/) ||
		(Object[i].x <= Cursor_->x && Cursor_->x <= Object[i].x + Object[i].width && Object[i].y <= Cursor_->y + Cursor_->height && Cursor_->y + Cursor_->height <= Object[i].y + Object[i].height /* левый нижний угол курсора*/) ||
		   (Object[i].x <= Cursor_->x+Cursor_->width && Cursor_->x+Cursor_->width <= Object[i].x + Object[i].width && Object[i].y <= Cursor_->y + Cursor_->height && Cursor_->y + Cursor_->height <= Object[i].y + Object[i].height /*правый нижний угол курсора*/)
		)
		{
			Object[i].to_redraw=1;
		}

	}
}
  • вторая функция устанавливает «нажатыми» объекты, над которыми находился курсор при нажатой аппаратной кнопке «клик»
//для объектов, на которых был центр курсора ставим "нажатыми"
void check_push_obj_by_cursor(struct Button_Data Object[], uint32_t num_objs, struct Cursor_Data *Cursor_)
{
	//центр курсора
	uint8_t x_c = Cursor_->x + Cursor_->width/2;
	uint8_t y_c = Cursor_->y + Cursor_->height/2;

	for(uint32_t i=0; i< num_objs; i++)
	{
		if(Object[i].x <= x_c && x_c <= Object[i].x + Object[i].width
				&& Object[i].y <= y_c && y_c <= Object[i].y + Object[i].height)
		{
			//центр курсора в границах объекта
			Object[i].current_state = PRESS;
		}

	}
}
  • третья функция отжимает все объекты на экране, если ни одна из аппаратных кнопок не нажата
//для всех объектов ставим "отжата"
void check_unpush_obj_by_cursor(struct Button_Data Object[], uint32_t num_objs, struct Cursor_Data *Cursor_)
{
	for(uint32_t i=0; i< num_objs; i++)
			Object[i].current_state = NO_PRESS;
}
18. Создаем переменную для нажатия кнопок и располагаем ее после перечисления типов нажатия
Важно
Обязательно использовать префикс volatile, чтобы компилятор не удалил эту функцию
19. Переносим из библиотеки SSD1331.c размеры экрана

20. Сохраняем, компилируем и прошиваем устройство
Тестирование
После перезагрузки на экране появляется «ОК» и курсор. С помощью кнопок на плате мы можем двигать курсор
Использовав кнопку 3 (клик) на плате, мы нажимаем «ОК» на экране, и индикатор считает время нажатия
Мы создали полноценный функционал взаимодействия пользователя со встраиваемой системой с помощью графического интерфейса, а также написали программу для движения курсора и нажатия им графической кнопки. Давайте приступим к выполнению заданий.
Интерактивное задание
Тест
Для закрепления полученных знаний пройдите тест
Стартуем!
Курсор, двигаясь, стирает свое предыдущее положение. Как это исправить?
Дальше
Проверить
Узнать результат
Зачем при создании переменной для нажатия кнопок использовать префикс volatile?
Дальше
Проверить
Узнать результат
Откуда необходимо брать размеры экрана?
Дальше
Проверить
Узнать результат
К сожалению, вы ответили неправильно
Прочитайте лекцию и посмотрите видео еще раз
Пройти еще раз
Неплохо!
Но можно лучше. Прочитайте лекцию и посмотрите видео еще раз
Пройти еще раз
Отлично!
Вы отлично справились. Теперь можете ознакомиться с другими компетенциями
Пройти еще раз