Частина 14 - Обробка подій. §.1, обробка клавіатури

Що таке подія та обробка подій у програмуванні?

Обробка подій - головне поняття в програмуванні ігор та додатків. Вони дозволяють користувачеві взаємодіяти з Вашою програмою. Кожного разу, коли Ви рухаєте курсор миші, натискаєте або відпускаєте клавішу на клавіатурі або використовуєте сенсорний екран, усі ці взаємодії розпізнаються як так звані події.

Події в SDL 2.0, SDL_Event

У SDL 2.0 щоразу, коли відбувається подія, наприклад, натискання клавіші на клавіатурі, вся інформація, пов’язана з цією конкретною подією, зберігається в записі SDL_Event. Залежно від типу події (наприклад, рух миші, натискання клавіші на клавіатурі, розгортання вікна програми) є різні поля, до яких можна отримати доступ. Для руху миші Ви можете прочитати положення x та y курсора, тоді як немає сенсу мати значення x та y для натиснутої клавіші на клавіатурі, а знати, яку конкретну клавішу було натиснуто на клавіатурі.

Типи подій, доступні в SDL 2.0

Існує набагато більше типів подій, ніж рух миші та натискання клавіші на клавіатурі. Подумайте про використання джойстика, використання сенсорного екрану, мінімізації / розгортання вікна програми тощо. Існує безліч різних подій, які можуть статися.

Всі ці типи подій мають певні імена, напр. тип події який вказує на рух мишки називається SDL_MOUSEMOTION. Повний список згідно з офіційною документацією SDL 2.0:

Типи подій, структура подій та поле SDL_Event
Тип події Структура події Поле SDL_Event
SDL_AUDIODEVICEADDED
SDL_AUDIODEVICEREMOVED
SDL_AudioDeviceEvent adevice
SDL_CONTROLLERAXISMOTION SDL_ControllerAxisEvent caxis
SDL_CONTROLLERBUTTONDOWN
SDL_CONTROLLERBUTTONUP
SDL_ControllerButtonEvent cbutton
SDL_CONTROLLERDEVICEADDED
SDL_CONTROLLERDEVICEREMOVED
SDL_CONTROLLERDEVICEREMAPPED
SDL_ControllerDeviceEvent cdevice
SDL_DOLLARGESTURE
SDL_DOLLARRECORD
SDL_DollarGestureEvent dgesture
SDL_DROPFILE SDL_DropEvent drop
SDL_FINGERMOTION
SDL_FINGERDOWN
SDL_FINGERUP
SDL_TouchFingerEvent tfinger
SDL_KEYDOWN
SDL_KEYUP
SDL_KeyboardEvent key
SDL_JOYAXISMOTION SDL_JoyAxisEvent jaxis
SDL_JOYBALLMOTION SDL_JoyBallEvent jball
SDL_JOYHATMOTION SDL_JoyHatEvent jhat
SDL_JOYBUTTONDOWN
SDL_JOYBUTTONUP
SDL_JoyButtonEvent jbutton
SDL_JOYDEVICEADDED
SDL_JOYDEVICEREMOVED
SDL_JoyDeviceEvent jdevice
SDL_MOUSEMOTION SDL_MouseMotionEvent motion
SDL_MOUSEBUTTONDOWN
SDL_MOUSEBUTTONUP
SDL_MouseButtonEvent button
SDL_MOUSEWHEEL SDL_MouseWheelEvent wheel
SDL_MULTIGESTURE SDL_MultiGestureEvent mgesture
SDL_QUIT SDL_QuitEvent quit
SDL_SYSWMEVENT SDL_SysWMEvent syswm
SDL_TEXTEDITING SDL_TextEditingEvent edit
SDL_TEXTINPUT SDL_TextInputEvent text
SDL_USEREVENT SDL_UserEvent user
SDL_WINDOWEVENT SDL_WindowEvent window
Інші події SDL_CommonEvent не використовується .type
Джерело: Документація SDL 2.0: SDL_Event

Цей список надзвичайно довгий, але не хвилюйтеся, як тільки Ви зрозумієте концепцію подій, Ви легко зрозумієте, який із цих типів подій буде відігравати роль для програм, що Ви хочете розробляти. Найважливіші події в будь-якому випадку детально висвітлено в цьому підручнику. Ви зможете працювати з рештою подій, як тільки отримаєте концепцію.

На відміну від SDL 1.2, деякі типи подій зникли, а в SDL 2.0 доступно багато нових типів, які корисні для використання нових форм взаємодії між користувачем та додатком (наприклад, технологія сенсорного екрану).

У чому різниця між типом події, структурою події та полем події?

У таблиці вище Ви помітили, що перша колонка охоплює тип події. Він визначає, який тип події відбувся, напр. клавіша натискається на клавіатурі (SDL_KEYDOWN).

Структура події в другій колонці - це запис структури яка залежить від типу події. Як вже обговорювалося раніше, натиснута клавіша потребуватиме структури запису, яка зберігає ідентифікатор клавіші, а не координати x, y (що, в свою чергу, буде необхідним для руху миші). Кожен тип події має певну відповідну структуру записів (подій) для зберігання інформації про подію.

Багато типів подій можуть мати однакову структуру подій. Типи подій SDL_KEYDOWN і SDL_KEYUP які генеруються натисканням чи відпусканням клавіші мають спільну структуру події SDL_KeyboardEvent оскільки інформація однакова, наприклад ідентифікатор клавіші.

Третя колонка показує назву поля SDL_Event для доступу до полів, специфічних для події. У випадку події типу SDL_KEYDOWN структура події буде SDL_KeyboardEvent. Специфічна інформація, напр. ідентифікатор клавіші, доступна через поле key в записі SDL_Event.

Це може здатися заплутаним. Далі відношення обговорюється більш докладно. Давайте перейдемо до коду.

program Chapter8_SDL2;

uses SDL2;

var
sdlWindow1: PSDL_Window;
sdlEvent: PSDL_Event;
exitloop: boolean = false;
text1: string = '';

begin

  //ініціалізація підсистеми відео
  if SDL_Init( SDL_INIT_VIDEO ) < 0 then HALT;

  sdlWindow1 := SDL_CreateWindow( 'Window1', 50, 50, 500, 500, SDL_WINDOW_SHOWN );
  if sdlWindow1 = nil then HALT;

  new( sdlEvent );

  while exitloop = false do
  begin
    while SDL_PollEvent( sdlEvent ) = 1 do
    begin
      write( 'Виявлено подію: ' );
      case sdlEvent^.type_ of

        //події клавіатури
        SDL_KEYDOWN: begin
                       writeln( 'Натиснуто клавішу:');
                       writeln( '  Код клавіші: ', sdlEvent^.key.keysym.sym );
                       writeln( '  Назва клавіші: "', SDL_GetKeyName( sdlEvent^.key.keysym.sym ), '"' );
                       writeln( '  Сканкод: ', sdlEvent^.key.keysym.scancode );
                       writeln( '  Назва сканкоду: "', SDL_GetScancodeName( sdlEvent^.key.keysym.scancode ), '"' );
                       writeln( '  Модифікатор  клавіші: ', sdlEvent^.key.keysym._mod );
                       case sdlEvent^.key.keysym.sym of
                         SDLK_ESCAPE: exitloop := true;  // вихід при натиснені клавіші ESC

                         //увімкнення/вимкнення режиму введення тексту
                         SDLK_F1: begin
                                    if SDL_IsTextInputActive = SDL_True then SDL_StopTextInput
                                    else SDL_StartTextInput;
                                    writeln(' Режим введення тексту переключено' );
                                  end;
                       end;
                     end;
        SDL_KEYUP: writeln( 'Клавішу відпущено ' );
        SDL_TEXTINPUT: begin
                         writeln( 'Введено текст: "', sdlEvent^.text.text, '"' );
                         text1 := text1 + sdlEvent^.text.text;
                         writeln( 'Весь рядок: ' + text1 );
                       end;

        //події мишки
        SDL_MOUSEMOTION: begin
                           writeln( 'X: ', sdlEvent^.motion.x, '   Y: ', sdlEvent^.motion.y,
                                    '   dX: ', sdlEvent^.motion.xrel, '   dY: ', sdlEvent^.motion.yrel );
                         end;
        SDL_MOUSEBUTTONDOWN: writeln( 'Натиснуто клавішу мишки: Індекс кнопки: ', sdlEvent^.button.button );
        SDL_MOUSEBUTTONUP: writeln( 'Відпущено кнопку мишки' );
        SDL_MOUSEWHEEL: begin
                          write( 'Колесо мишки прокручено: ' );
                          if sdlEvent^.wheel.y > 0 then writeln( 'Прокрутка вперед, Y: ', sdlEvent^.wheel.y )
                          else writeln( 'Прокрутка назад, Y: ', sdlEvent^.wheel.y );
                        end;

        //події вікна
        SDL_WINDOWEVENT: begin
                           write( 'Window event: ' );
                           case sdlEvent^.window.event of
                             SDL_WINDOWEVENT_SHOWN: writeln( 'Вікно показане' );
                             SDL_WINDOWEVENT_MOVED: writeln( 'Вікно переміщено' );
                             SDL_WINDOWEVENT_MINIMIZED: writeln( 'Вікно згорнуто' );
                             SDL_WINDOWEVENT_MAXIMIZED: writeln( 'Вікно розгорнуто' );
                             SDL_WINDOWEVENT_ENTER: writeln( 'Вікно отримало фокус мишки' );
                             SDL_WINDOWEVENT_LEAVE: writeln( 'Вікно втратило фокус мишки' );
                           end;
                         end;
      end;
    end;
    SDL_Delay( 20 );
  end;
  
  dispose( sdlEvent );
  SDL_DestroyWindow ( sdlWindow1 );

  //вимкнення підсистеми відео
  SDL_Quit;

end.

Результат буде видно не власне у вікні SDL 2.0, а у вікні командного рядка (яке зазвичай відображається разом із вікном SDL 2.0 у середовищі Windows).

Chapter 8 - result

Початкові рядки коду:

program Chapter8_SDL2;

uses SDL2;

var
sdlWindow1: PSDL_Window;
sdlEvent: PSDL_Event;
exitloop: boolean = false;
text1: string = '';

begin

  //ініціалізація підсистеми відео
  if SDL_Init( SDL_INIT_VIDEO ) < 0 then HALT;

  sdlWindow1 := SDL_CreateWindow( 'Window1', 50, 50, 500, 500, SDL_WINDOW_SHOWN );
  if sdlWindow1 = nil then HALT;

  new( sdlEvent );

Програма називається “Chapter8_SDL2”. Оскільки обробка подій є базовою можливістю SDL 2.0, підключати додаткові модулі, окрім самого SDL2 не потрібно.

Нам потрібно три змінні. “sdlWindow1” потрібно щоб створити вікно як вже відомо з попередніх частин. Цього разу ми не будемо на ньому нічого малювати, але використаємо його для розпізнавання подій (напр. клік мишкою всередині вікна).

Змінна SDL_Event з іменем “sdlEvent” зберігає події, які генеруються користувачем додатку. Це вказівник типу PSDL_Event. І тоді проста змінна типу Boolean Pascal з назвою “exitloop”, яка встановлюється як false, оскільки ми не хочемо від початку переривати програмний цикл. Крім того, ми маємо текстову змінну “text1”, яка нам знадобиться пізніше для демонстрації введення тексту.

Для наступних рядків нічого нового немає, ініціалізується SDL 2.0 і створюється вікно SDL 2.0.

Оскільки “sdlEvent” це змінна вказівного типу, нам потрібно виділити для неї пам'ять. Як відомо, це виконується командою new.

  while exitloop = false do
  begin
    while SDL_PollEvent( sdlEvent ) = 1 do
    begin

Спочатку запускається цикл while-do, який буде виконуватися до тих пір, поки змінна “exitloop” буде false. Якщо її змінити на true, цикл буде завершений. (Це спрацьовує по натисканню клавіші ESC, пізніше це детально обговоримо.)

У зовнішньому циклі першим ділом є опитування події, яке виконується функцією SDL_PollEvent.

SDL_PollEvent(event: PSDL_Event): SInt32

Ця функція повертає ціле значення 1, якщо одна або кілька подій знаходяться в черзі. Дані події виділяються у змінну SDL_Event і видаляються з черги. Якщо подій не очікується, вона повертає 0, а змінна події приймає значення nil замість конкретних даних про подію.

Якщо очікується подія, її інформація надходить у “sdlEvent” і повертається 1. Внутрішній цикл while працює, доки не будуть оброблені всі події в черзі. Це виведе в консолі текст із повідомленням «Виявлено подію» і, що ще важливіше, перевіримо тип події!

Не використовуйте оператор if (замість внутрішнього циклу while) для перевірки подій, оскільки це означає, що ми перевіряємо лише один раз подію кожного проходу зовнішнього циклу. Поєднуючи два цикли while та перевіряючи всі події у внутрішньому циклі, ми можемо обробити кожну подію, що відбулася для цього проходу циклу програми.

Поле type_

Тип події можна прочитати з поля type_, отже ми перевіряємо тип у sdlEvent^.type_ за допомогою виразу case.

      write( 'Виявлено подію: ' );
      case sdlEvent^.type_ of

        //події клавіатури
        SDL_KEYDOWN: begin
                       writeln( 'Натиснуто клавішу:');
                       writeln( '  Код клавіші: ', sdlEvent^.key.keysym.sym );
                       writeln( '  Назва клавіші: "', SDL_GetKeyName( sdlEvent^.key.keysym.sym ), '"' );
                       writeln( '  Сканкод: ', sdlEvent^.key.keysym.scancode );
                       writeln( '  Назва сканкоду: "', SDL_GetScancodeName( sdlEvent^.key.keysym.scancode ), '"' );
                       writeln( '  Модифікатор  клавіші: ', sdlEvent^.key.keysym._mod );
                       case sdlEvent^.key.keysym.sym of
                         SDLK_ESCAPE: exitloop := true;  // вихід при натиснені клавіші ESC

Якщо _typeє подією SDL_KEYDOWN відбувається вхід в блок begin-end block. Існує кілька рядків виводу оператором writeln. Давайте обговоримо їх один за одним.

В першому рядку повертається (віртуальний) код клавіші SDL, який представлений цілим числом. Для спеціальних клавіш (напр. клавіш, Insert, …) ці значення часто виходять за межі змінних типу SmallInt (-32768 до 32767) або Word (0 до 65535), про що слід пам’ятати, якщо Ви збираєтесь повернути ці значення до змінних. Доступ довіртуальних коді клавіш SDL можна отримати за допомогою

sdlEvent^.key.keysym.sym.

Спробуймо трохи це розібрати. Подія зберігається в sdlEvent, який має тип вказівника, тому для доступу до вмісту нам потрібен sdlEvent^. У події клавіатури є поле keysymКод віртуальної клавіші SDL зберігається в полі sym запису keysym. Не хвилюйтесь, якщо це здається заплутаним, у наступній частині ми детально розглянемо ці два записи (запис SDL_KeyboardEvent та запис keysym).

Більшість кодів клавіш мають назву, напр. “Escape” для клавіші Esc. У наступному рядку коду функція SDL_GetKeyName використовується для отримання імені клавіші, код якої ми знайшли:

function SDL_GetKeyName(key: TSDL_ScanCode): PAnsiChar

Іншим способом, це також можливо за допомогою цієї функції:

function SDL_GetKeyFromName(const name: PAnsiChar): TSDL_KeyCode

Однак це не показано в коді.

Іншим поданням є скан код клавіші. Детальна інформація про різницю між кодами клавіш та скан кодами обговориться трохи пізніше. У будь-якому випадку, скан код зберігається в полі scancode запису keysym. Отже, до нього можна отримати доступ:

sdlEvent^.key.keysym.scancode

і його назву можна прочитати функцією

function SDL_GetScancodeName(scancode: TSDL_ScanCode): PAnsiChar.

Також це можна зробити обхідним шляхом:

function SDL_GetScancodeFromName(const name: PAnsiChar): TSDL_ScanCode.

Для завершення роботи та отримання вашої інформації можна отримати код клавіші зі скан коду та навпаки. Використовуються такі функції:

function SDL_GetKeyFromScancode(scancode: TSDL_ScanCode): TSDL_KeyCode

та

function SDL_GetScancodeFromKey(key: TSDL_KeyCode): TSDL_ScanCode.

Розумієте, між ними існує сильний зв’язок, але вони не однакові. Давайте наведемо короткий приклад того, що відбувається, якщо натиснути клавішу:

Keycodes and Scancodes
Коди клавіш та скан коди

Код підручника повертає ці коди клавіш та скан коди для клавіші “Q”, клавіші Esc та клавіші Return. Як бачите, коди не тільки відрізняються між різними клавішами, але й для одної і тої ж клавіші, наприклад код клавіші Esc - 27, а скан код - 41. У будь-якому випадку, імена клавіш, схоже, узгоджені. Пізніше ми побачимо приклад, коли навіть назви відрізняються.

Останній рядок повертає значення коду модифікаторів клавіш. Модифікатори клавіш - це клавіші, які буквально їх модифікують. Наприклад, якщо Ви натискаєте клавішу букви, утримуючи клавішу Shift, зазвичай Ви змінюєте букву на велику. Типовими модифікаторами клавіш є shift, ctrl та alt. Поле, в якому зберігається значення модифікатора ключа, називається _mod. Пізніше ми обговоримо це поле більш докладно.

                       case sdlEvent^.key.keysym.sym of
                         SDLK_ESCAPE: exitloop := true;  // вихід при натиснені клавіші ESC

                         //увімкнення/вимкнення режиму введення тексту
                         SDLK_F1: begin
                                    if SDL_IsTextInputActive = SDL_True then SDL_StopTextInput
                                    else SDL_StartTextInput;
                                    writeln(' Режим введення тексту переключено' );
                                  end;
                       end;
                     end;
        SDL_KEYUP: writeln( 'Клавішу відпущено ' );
        SDL_TEXTINPUT: begin
                         writeln( 'Введено текст: "', sdlEvent^.text.text, '"' );
                         text1 := text1 + sdlEvent^.text.text;
                         writeln( 'Весь рядок: ' + text1 );
                       end;

Ми перевіряємо значення коду клавіші за допомогою оператора case з sdlEvent^.key.keysym.sym. Якщо ми знаємо код клавіші певної клавіші, ми можемо перевірити цю клавішу і відповідно відреагувати. Спочатку ми перевіряємо, чи код клавіші події є SDLK_ESCAPE, оскільки це код клавіші Escape (ESC). Якщо так, для змінної exitloop встановлено значення true, щоб зупинити зовнішній цикл while та вийти з програми.

Кожен код клавіші представлений константою коду клавіші (наприклад, SDLK_ESCAPE), десятковим значенням (тут 27) та шістнадцятковим значенням (тут ($001B). Ви можете перевірити коди клавіш у наступній (офіційній) таблиці пошуку кодів клавіш SDL 2.0:

https://wiki.libsdl.org/SDLKeycodeLookup or Таблиця пошуку коду SDL 2.0 (резервна копія розміщена тут)

У п’ятому рядку знайдено код клавіші Escape.

У цій таблиці ви знайдете додатково до десяткового значення шістнадцяткове значення. Ви можете спробувати використати $001B замість 27. Це також зробить фокус :-)! Також, якщо можливо, показано зображення символів.

Якщо натиснута клавіша F1, ми хотіли б увімкнути або вимкнути режим введення тексту. Цього разу ми не використовуємо десятковий код клавіші для розпізнавання клавіші, а скоріше константу (SDLK_F1). Ви можете здогадатися, що у вас є вибір, і можете використовувати або десяткове значення, і константу (як щодо SDLK_ESCAPE у попередньому випадку?), або шістнадцяткове представлення. - Навіть існує SDLK_ESCAPE(!), Але вгадайте що, що це не працює. Для деяких спеціальних клавіш це працює, як показано для клавіші F1, для більшості інших клавіш це не працює (Ви виявите, що більшість клавіш зберігаються як константи рядків, тому компілятор скаржиться, що вони неправильного типу). Отже, для більшості клавіш потрібно шукати десяткове представлення і робити так, як показано.

Таким же чином ми перевіряємо, чи натиснута клавіша F1, перевіряючи SDLK_F1. Якщо F1 натиснуто, перевіряється, чи активний так званий режим введення тексту функцією

function SDL_IsTextInputActive: TSDL_Bool.

Якщо він активний, то він деактивовується функцією

procedure SDL_StopTextInput

і якщо він не активний, він активовується функцією

procedure SDL_StartTextInput.

Нарешті повертається короткий текст, в якому зазначено, що режим введення тексту змінено. Детальніше про режим введення пізніше.

І останнє, але не менш важливе, якщо _type - це подія SDL_KEYUP event, ми знаємо, що клавішу відпущено, отже, вона фізично рухається на клавіатурі. Ми просто роздруковуємо “Клавішу відпущено”, нам байдуже, яку саме клавішу тут відпущено.

Події клавіатури SDL_KEYDOWN і SDL_KEYUP

Давайте подивимось на структуру запису події клавіатури та обговоримо поля зверху вниз:

TSDL_KeyboardEvent = record
    type_: UInt32;        // SDL_KEYDOWN або SDL_KEYUP
    timestamp: UInt32;
    windowID: UInt32;     // Вікно з фокусом на клавіатурі, якщо воно є
    state: UInt8;         // SDL_PRESSED або SDL_RELEASED 
    _repeat: UInt8;       // Не нуль якщо клавіша повторюється
    padding2: UInt8;
    padding3: UInt8;
    keysym: TSDL_KeySym;  // Клавіша, яку натиснули або відпустили
  end;

type_ - це беззнакове 32-бітове ціле число (отже, UInt32), яке визначає, який саме тип події у вас є. Як зазвичай, цілі значення представлені константами, тут SDL_KEYDOWN (якщо натиснуто) і SDL_KEYUP (якщо клавішу відпущено). Вони мають однакову загальну структуру записів подій (SDL_KeyboardEvent). Для довідки, всі події SDL 2.0 мають поле type_ з очевидної причини.

Наступне поле timestamp очевидно містить часову мітку цілочисельного типу, яка використовується всередині SDL 2.0 для визначення послідовності, в якій запускались події. Усі події SDL 2.0 мають поле timestamp.

Поле windowID необхідно для розрізнення подій, викликаних різними вікнами SDL 2.0, якщо їх більше одного. Припустимо, Ваша програма має два вікна SDL 2.0, window1 та window2. Ці два вікна мають певні постійні ідентифікатори, виділені SDL 2.0. Тепер, якщо Ви маєте фокус на window1 (тобто активним є window1) і натиснули клавішу, windowID події містить конкретний ідентифікатор window1. Якщо активним вікном SDL 2.0 є window2 і Ви натискаєте клавішу, конкретний ідентифікатор window2 буде присутній у windowID. Таким чином Ви можете легко розрізнити, для якого вікна програма повинна реагувати на подію клавіатури, наприклад натискання клавіші. У будь-якому випадку, в SDL 2.0 була введена можливість обробляти більше одного вікна, тому можете собі уявити, що для багатьох програм, і якщо у Вас немає більше одного вікна у Вашій програмі, Ви можете ігнорувати це поле. Поле windowID не присутнє (і не необхідне) для всіх подій, які надає SDL 2.0.

Поле state 8-бітового цілочисельного типу може бути зчитане, щоб отримати стан клавіші (натиснуто або відпущено), кодованого SDL_PRESSED та SDL_RELEASED. Вас може трохи заплутати, яка різниця між парою SDL_PRESSED та SDL_RELEASED та парою SDL_KEYDOWN і SDL_KEYUP. По суті, вони мають однакове значення для клавіші клавіатури. Формально різниця полягає в тому, що SDL_KEYDOWN і SDL_KEYUP - це два різні типи подій, тоді як SDL_PRESSED та SDL_RELEASED - це два різні стани клавіш. У випадку натискання клавіші на клавіатурі стан начебто зайвий, оскільки якщо Ви отримуєте подію SDL_KEYDOWN, Ви вже знаєте, що була натиснута клавіша, і зчитування стану (який буде SDL_PRESSED) зайве. У будь-якому випадку, поле state здається введеним для повноти, оскільки для інших типів подій (наприклад, подій мишки) існує величезна різниця, якщо Ви рухаєте мишкою і натискаєте або відпускаєте кнопки мишки.

_repeat дозволяє дізнатись, чи відповідна клавіша перебуває в режимі повтору. Для більшості операційних систем режим повторення вмикається через невелику затримку, коли Ви натискаєте клавішу. Ви можете спробувати відкрити простий текстовий редактор. Якщо натиснути будь-яку клавішу, яка має букву (наприклад, клавішу "a"), у текстовому редакторі ви побачите "a". Якщо натиснути клавішу після короткої затримки, увімкнеться режим повторення, і швидко з’явиться ще кілька а. Якщо ви перебуваєте в режимі повторення для певної клавіші, repeat_ має значення, яке відрізняється від 0 (швидше за все, 1), інакше воно буде 0. Особливо для ігор, можливо, Ви захочете відключити початкову затримку, якщо утримувати натиснутою клавішу та нехай режим постійного повторення вмикається без затримки. У SDL 1.2 я описав тут, як це зробити просто за допомогою функції SDL_EnableKeyRepeat, ця функція застаріла і вже не існує в SDL 2.0!

Просте рішення проблеми “повторної затримки”: Замість того, щоб шукати, чи фактична подія неодноразово запускається подією клавіші, використовуйте перемикач, який включається, якщо відбувається подія натиснення клавіші, і який вимикається, якщо відбувається подія відпускання клавіші. Приклад: Припустимо, у вас є космічний корабель, який повинен рухатись ліворуч, натискаючи клавішу "a". Замість того, щоб змінювати його координати лише один раз, коли спрацьовує подія натиснення клавіші, Ви запускаєте перемикач (наприклад, MoveSpaceshipLeft: = true). Тригер обробляється незалежно від обробки подій десь у головному ігровому циклі. Як тільки спрацьовує подія клавіші для клавіші “a”, перемикач вимикається (наприклад, MoveSpaceshipLeft: = false).

Я не знаю призначення полів padding2 and padding3. Можливо, вони резервують місце для майбутніх розробок або використовуються внутрішньо.

І останнє, але не по значенню, дуже важливе поле. Поле keysym типу SDL_KeySym type містить кілька відомостей про ідентифікацію натиснутої клавіші. У більшості випадків нам потрібно знати, яку саме клавішу було натиснуто. Давайте розглянемо запис SDL_KeySym:

TSDL_Keysym = record
    scancode: TSDL_ScanCode;      // код фізичної клавіші SDL
    sym: TSDL_KeyCode;            // віртуальний код клавіші SDL
    _mod: UInt16;                 // поточні модифікатори клавіші
    unicode: UInt32;              // (застаріло) 
  end;

Як бачите, перші два поля містять подання коду клавіші для ідентифікації натиснутої або відпущеної клавіші. Хоча в обох полях, схоже, знову є спеціальні записи, а саме SDL_ScanCode і SDL_KeyCode, вони насправді складаються лише з одного поля, DWord (цілочисельний тип) для SDL_ScanCode та SInt32 (цілий тип) для SDL_KeyCode.

Ви можете ознайомитися з таблицею пошуку сканкоду SDL 2.0: https://wiki.libsdl.org/SDLScancodeLookup або Таблиця пошуку сканкодів SDL 2.0 (резервна копія)

Різниця між сканкодом та кодом клавіші

Різниця полягає в тому, що сканкод стосується конкретного фізичного розташування клавіші на клавіатурі. Сканкод посилається на типову розкладку клавіатури в США (розкладка QWERTY). Термін "QWERTY" просто стосується перших шести літер зліва направо в першому рядку з літерами на типовій американській клавіатурі. Наприклад: німецька розкладка клавіатури (розкладка QWERTZ) подібна до американської (для більшості літерних клавіш), хоча клавіші "Y" та "Z" мають абсолютно протилежні позиції (звідси QWERTZ для німецької розкладки на відміну від QWERTY для американської розкладки). Якщо я натисну клавішу "Z" на німецькій клавіатурі, повернутий код сканування буде представляти клавішу "Y", оскільки положення клавіші (незалежно від розкладки) дорівнює положенню клавіші "Y" на американській клавіатурі . Сканкоди не залежать від розкладки.

Код клавіші відноситься до віртуального представлення клавіші відповідно до розкладки клавіатури. Тут Ви розглядаєте розкладку, отже, коди клавіш залежать від розкладки. Як обговорювалося раніше, сканкод для клавіші "Z" на німецькій клавіатурі поверне, що клавіша "Y" була натиснута, оскільки клавіша має місце розташування клавіші "Y" на американській клавіатурі. Але код клавіші цього не повертає, це клавіша “Y”, але він правильно поверне, що було натиснуто клавішу “Z”. Вивід на наступнову вікні, що позначено червоним на наступному зображенні ілюструє результат, якщо на німецькій клавіатурі натиснуто клавішу "Z".

Difference Key code and Scancode

Ви можете сказати, що я тоді завжди повинен використовувати коди клавіш для читання введення тексту, так? - Неправильно :-). Насправді, починаючи з SDL 2.0, це сильно залежить від того, що Ви насправді хочете зробити. Якщо Ви хочете отримати текст або один символ від користувача, вам не слід ні використовувати сканкоди, ні використовувати коди клавіш (більше ніколи). (У колишніх кодах клавіш SDL 1.2 або представлення Unicode насправді були кращим вибором.) Обробка реального введення тексту більше не робиться таким чином. Цю справу ми обговоримо пізніше. Як би там не було, ця цитата з Посібника з міграції SDL 1.2 на SDL 2.0 чудово підсумовує:

Використовуйте SDL_KEYDOWN щоб поводитись з клавіатурою як із 101-кнопочним джойстиком. Введення тексту відбувається десь в іншому місці.

Подумайте про відоме розташування клавіш WASD у формі літери "Т" (розташування чотирьох клавіш "W", "A", "S" та "D") у американській розкладці, навіть якщо Ви використовуєте клавіатуру без будь-яких латинських літер, можливо, вам захочеться використовуйте ці чотири клавіші для переміщення ігрового персонажа вперед (“W”), вліво (“A”), назад (“S”) або вправо (“D”). Позначення клавіш у цьому випадку не має значення, і клавіші не використовуються для введення тексту.

Знову ж таки, пам'ятайте:

Ніколи не використовуйте коди клавіш або сканкоди для читання введення тексту в SDL 2.0 (в жодному разі!)

Залишилось поле _mod - це 16-бітове ціле число без знака (відповідає типу Word в Pascal) і представляє модифікатори клавіш (ctrl, alt, shift, num lock, caps lock,…). Якщо натиснуто один або кілька модифікаторів клавіш, значення _mod має унікальний номер для кожної клавіші або комбінації клавіш. Наприклад, ліва клавіша Shift має десяткове значення 1, права клавіша Shift має значення 2, ліва клавіша Control (ctrl) має значення 64, права клавіша ctrl має значення 128. Якщо ліва клавіша shift і ctrl одночасно натискаються значення _mod буде 65 (1 + 64). Припустимо, Ви хочете, щоб користувач закрив програму, натиснувши клавішу ctrl та “Q”. Отже, ви зачитуєте код клавіші для “Q” і перевіряєте, чи є _mod 64 або 128. Оскільки, здається, не існує таблиці для модифікаторів ключів, тут найважливіші з них:

Десяткові значення клавіш-модифікаторів
Клавіша-модифікатор Значення UInt16
Лівий shift 1
Правий shift 2
Лівий ctrl 64
Правий ctrl 128
Лівий alt 256
Правий alt 576 (64 + 512?)
Caps lock 8192
Num lock 4096

Поле unicode застаріле і не буде тут обговорюватись. Між іншим, також пропала процедура SDL_EnableUnicode, яка включала режим юнікоду.

Введення таксту в SDL 2.0

Давайте розглянемо наступну частину коду та дізнаємось про правильне введення тексту в SDL 2.0.

                         //увімкнення/вимкнення режиму введення тексту
                         SDLK_F1: begin
                                    if SDL_IsTextInputActive = SDL_True then SDL_StopTextInput
                                    else SDL_StartTextInput;
                                    writeln(' Режим введення тексту переключено' );
                                  end;
                       end;
                     end;
        SDL_KEYUP: writeln( 'Клавішу відпущено ' );
        SDL_TEXTINPUT: begin
                         writeln( 'Введено текст: "', sdlEvent^.text.text, '"' );
                         text1 := text1 + sdlEvent^.text.text;
                         writeln( 'Весь рядок: ' + text1 );
                       end;
У SDL 2.0 є новий тип події з назвою SDL_TEXTINPUT. Це було явно введено в SDL 2.0, щоб зробити введення тексту більш гнучким і простим.

Давайте подивимось на структуру запису SDL_TextInputEvent.

TSDL_TextInputEvent = record
    type_: UInt32;           // SDL_TEXTINPUT 
    timestamp: UInt32;
    windowID: UInt32;        // вікно з фокусом клавіатури, якщо є
    text: array[0..SDL_TEXTINPUTEVENT_TEXT_SIZE] of Char;   // введений текст 
  end;

На відміну від структури події SDL_KeyBoardEvent, де були доступні два типи подій (SDL_KEYDOWN і SDL_KEYUP), на даний момент для структури події SDL_TextInputEvent можливий лише один тип події, SDL_TEXTINPUT.

Поля timestamp і windowID field дуже детально обговорювались раніше. Вони містять загальну інформацію про те, коли ця подія була ініційована та яке вікно програми було в фокусі, коли подія була запущена. Ви можете прокрутити вище, щоб отримати більше інформації про це.

Унікальною у цій структурі подій є поле text, який є масивом елементів символів. Цей масив містить від 0 до SDL_TEXTINPUTEVENT_TEXT_SIZE елементів символів. SDL_TEXTINPUTEVENT_TEXT_SIZE за замовчуванням має розмір 32. Увага, це не означає, що у Вас може бути лише 32 символи в тексті або щось подібне! Це означає, що на момент запуску цієї події до події можна подати не більше 32 символів. Майте на увазі, однак, якщо Ви використовуєте західну мову з латинськими символами, Ви завжди подаєте лише один символ за кожним натисканням клавіші.

То чому тоді все-таки 32 можливі символи? Щоб зрозуміти це, потрібно було б глибше заглибитися у побудову незахідних мов та як будуються слова та речення. Величезна кількість та складність способу побудови слів та речень робить так, що вони будуються заздалегідь перед тим, як подати їх у поле text SDL_TextInputEvent. Ці конструкції не просто створюються лише одним натисканням клавіші, яке можна прочитати. Для цих конструкцій зарезервовано 32 символи. (Я був би радий дати тут більш детальне пояснення, будь ласка, зв'яжіться зі мною для вдосконалення цього пункту.)

Давайте повернемось до коду. Якщо знаходиться подія SDL_TEXTINPUT, до її запису (SDL_TextInputEvent) можна отриматти доступ через text. Це інформація текстового вводу, що зберігається в полі text яке ми нещодавно розглядали. Ось чому ми можемо використовувати

sdlEvent^.text.text

щоб отримати доступ до введеної текстової інформації. В першому рядку кода ми просто видруковуємо вміст поля text. Для збереження знайдених символів і додавання до символів які були знайдені раніше використовується змінна типу String з назвою “text1”. Цей рядок також видруковується.

Зверніть увагу на те, як правильно розпізнаються спеціальні символи (наприклад, символи валют, французькі символи з наголосом, німецький символ "ß", ...) та великі літери. Спробуйте це за допомогою кодів клавіш або сканкодів (це біль).

Також зауважте, що функціональні клавіші (F1, Backspace,…) не розпізнаються як символи. Ви несете відповідальність за те, щоб вони працювали бажаним чином :-).

← попередня частина | наступна частина →

Немає коментарів:

Дописати коментар