Графический интерфейс пользователя. Курсор и нажатие кнопки
Электроника
“
Классическим примером использования графического интерфейса является движение с помощью мышки или кнопок графического объекта — курсора. При этом, если курсор попадает на кнопку, и пользователь нажимает на манипулятор, кнопка меняет свое состояние.
Давайте создадим курсор и с помощью кнопок на плате подвигаем его на графическом дисплее. Также мы сможем с помощью курсора нажать на кнопку
Давайте создадим курсор и с помощью кнопок на плате подвигаем его на графическом дисплее. Также мы сможем с помощью курсора нажать на кнопку
Глоссарий
Для успешного освоения материала рекомендуем вам изучить следующие понятия:
Кнопка
один из элементов интерфейса пользователя компьютерной программы, «нажатие» на который приводит к некоторому действию, заложенному в программе
Курсор
в интерфейсе пользователя — элемент графического интерфейса, указывающий на объект, с которым будет производиться взаимодействие с помощью клавиатуры, мыши или другого устройства управления. Различают текстовый курсор, обозначающий место ввода с клавиатуры; курсор мыши (или указатель мыши) и других указывающих устройств; курсор меню. Кроме указания на объект курсор может также отображать его состояние, например — невозможность взаимодействия
Графический индикатор
наиболее сложный тип индикаторов, позволяющий передавать как символьную информацию, так и рисунки
Видеолекция
Конспект
Задача
- Построить абстракцию курсора в программе
- Сформировать движение курсора по экрану
- Поменять состояние кнопки на «нажата», когда на нее попадает курсор, и пользователь «кликает мышью», то есть в нашем случае нажимает кнопку на плате
Информация для курсора
1. Координаты левого верхнего угла в пикселях экрана
2. Ширина-высота в пикселях экрана
3. Идентификатор объекта (опционально, т. е. курсор обычно один)
4. Метод отрисовки
2. Ширина-высота в пикселях экрана
3. Идентификатор объекта (опционально, т. е. курсор обычно один)
4. Метод отрисовки
Работа на компьютере
1. Копируем предыдущий проект, переименовываем и открываем
2. В main.c добавляем новый тип для курсора typedef struct Cursor_Data Cursor_Data
3. Добавляем описание курсора в виде структуры
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. Добавляем функцию для отрисовки курсора
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
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. Поправляем заголовки соответствующих функций и добавляем обработку курсора
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. Сохраняем, компилируем и прошиваем устройство
20. Сохраняем, компилируем и прошиваем устройство
Тестирование
После перезагрузки на экране появляется «ОК» и курсор. С помощью кнопок на плате мы можем двигать курсор
Использовав кнопку 3 (клик) на плате, мы нажимаем «ОК» на экране, и индикатор считает время нажатия
“
Мы создали полноценный функционал взаимодействия пользователя со встраиваемой системой с помощью графического интерфейса, а также написали программу для движения курсора и нажатия им графической кнопки. Давайте приступим к выполнению заданий.
Интерактивное задание
Тест
Для закрепления полученных знаний пройдите тест
Стартуем! |
Курсор, двигаясь, стирает свое предыдущее положение. Как это исправить?
Дальше |
Проверить |
Узнать результат |
Зачем при создании переменной для нажатия кнопок использовать префикс volatile?
Дальше |
Проверить |
Узнать результат |
Откуда необходимо брать размеры экрана?
Дальше |
Проверить |
Узнать результат |
К сожалению, вы ответили неправильно
Прочитайте лекцию и посмотрите видео еще раз
Пройти еще раз |
Неплохо!
Но можно лучше. Прочитайте лекцию и посмотрите видео еще раз
Пройти еще раз |
Отлично!
Вы отлично справились. Теперь можете ознакомиться с другими компетенциями
Пройти еще раз |