Частина 15 - Обробка подій. §2 - обробка мишки

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

Давайте почнемо з повного коду прикладу:

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_MOUSEMOTION: begin
                           writeln( 'X: ', sdlEvent^.motion.x, '   Y: ', sdlEvent^.motion.y,
                                    '   dX: ', sdlEvent^.motion.xrel, '   dY: ', sdlEvent^.motion.yrel );
                         end;

Є три різних структури для подій переміщення мишки, копок мишки і олеса прокрутки мишки: SDL_MouseMotionEvent, SDL_MouseButtonEvent і SDL_MouseWheelEvent.

Обробка руху мишки в SDL 2.0

Якщо Ви рухаєте мишкою, спрацьовує подія SDL_MOUSEMOTION. Структура запису SDL_MouseMotionEvent показана нижче:


TSDL_MouseMotionEvent = record
    type_: UInt32;       // SDL_MOUSEMOTION
    timestamp: UInt32;
    windowID: UInt32;    // вікно з фокусом мишки якщо є
    which: UInt32;       // id екземпляра мишки, або SDL_TOUCH_MOUSEID
    state: UInt8;        // поточний стан кнопки
    padding1: UInt8;
    padding2: UInt8;
    padding3: UInt8;
    x: SInt32;           // Координата X, відносно вікна
    y: SInt32;           // Координата Y, відносно вікна
    xrel: SInt32;        // Відносне переміщення по напрямку X 
    yrel: SInt32;        // Відносне переміщення по напрямку Y
  end;

Знову є поля type_, timestamp і windowID. Тут нічого нового. Поле which містить id мишки. Це важливо якщо Ви маєте підключено до Вашого комп'ютера більше одного пристрою. Подумайте, наприклад, про ноутбук з точпадомдля переміщення курсору мишки, і в той же час USB мишку підключену до цього ж ноутбука. Щоб розрізнити їх, ви можете отримати їх ідентифікатори.

Поле state відомо зі структури SDL_KeyBoardEvent. Може бути різниця в тому, що якщо ви натискаєте кнопку миші та рухаєте мишу, або якщо у вас немає натиснутої кнопки. Найвідоміший приклад - це якщо ви хочете вибрати купу файлів на робочому столі або в папці. Між іншим, поле state кодує число, яке відрізняється в залежності від того, які кнопки насправді натиснуті. Це працює подібно до модифікаторів клавіш, якщо Ви тримаєте натиснутою дві кнопки миші під час руху, стан - це сума кожного значення стану окремої кнопки миші. Як приклад для моєї миші: немає кнопки миші 0, лівої кнопки миші 1, правої кнопки миші 4, середньої кнопки миші 2, кнопки великого пальця 8. Якщо я продовжую натискати ліву та праву кнопки миші 5 (сума 1 + 4).

Поля x і y містять координати курсору мишки в пікселях. Ці координати відносяться до відна програми на SDL 2.0. Пам'ятайте, що координата (0/0) відповідає лівому верхньому кутку. Позитивні значення x підраховуються зліва направо, а позитивні значення y - зверху вниз.

Поля xrel і yrel використовуються щоб визначити як швидко переміщується мишка з однієї точки в іншу. Припустимо, Ви пересуваєте курсор мишки зліва на право в вікні Вашої програми. Перший раз, коли Ви робите це повільно, xrel може бути 1, що значить, Ви просто перемістились на піксель зліва на право між двома подіями руху мишки. Якщо Ви рухаєте швидко, xrel може бути 50, що значить що цього разу Ви перемістили на 50 пікселів між двома подіями руху мишки. Для ігрового програмування це може бути особливо важливо. Напр., подумайте про шутер від першої особи, якби швидкість руху вигляду від першої особи була незалежною від дійсного переміщення мишки, ця гра не мала б особливого сенсу.

Щоб отримати доступ до цих полів, слід прочитати поле руху події. У прикладі коду зчитуються координати (x / y) та відносні позиції xrel та yrel

sdlEvent^.motion.x

sdlEvent^.motion.y

sdlEvent^.motion.xrel

sdlEvent^.motion.yrel

і просто виводяться на екран. Давайте перейдемо до наступного фрагменту коду.


        SDL_MOUSEBUTTONDOWN: writeln( 'Натиснуто кнопку мишки: Індекс кнопки: ', sdlEvent^.button.button );
        SDL_MOUSEBUTTONUP: writeln( 'Відпущено кнопку мишки' );
        SDL_MOUSEWHEEL: begin
                          write( 'Прокручено колесо прокрутки: ' );
                          if sdlEvent^.wheel.y > 0 then writeln( 'Scroll forward, Y: ', sdlEvent^.wheel.y )
                          else writeln( 'Обернена прокрутка, Y: ', sdlEvent^.wheel.y );
                        end;

Натискання кнопки миші в SDL 2.0

Як і для SDL_KeyBoardEvent, Ви хотіли б знати, натиснута або відпущена кнопка миші. Якщо натиснута кнопка мишки, спрацьовує подія SDL_MOUSEBUTTONDOWN. При відпущенні кнопки мишки спрацьовує подія SDL_MOUSEBUTTONUP. Вони мають структуру SDL_MouseButtonEvent . Давайте подивимось на цю структуру:


TSDL_MouseButtonEvent = record
    type_: UInt32;       // SDL_MOUSEBUTTONDOWN або SDL_MOUSEBUTTONUP 
    timestamp: UInt32;
    windowID: UInt32;    // вікно з фокусом мишки, якщо є
    which: UInt32;       // id екземпляра мишки, або SDL_TOUCH_MOUSEID 
    button: UInt8;       // Індекс кнопки мишки
    state: UInt8;        // SDL_PRESSED або SDL_RELEASED
    padding1: UInt8;
    padding2: UInt8;
    x: SInt32;           // X координата, відносно вікна
    y: SInt32;           // Y координата, відносно вікна
  end;

Якщо Ви порівняєте цю структуру зі структурою SDL_MouseMotionEvent, Ви помітите, що зникло лише два поля xrel і yrel і нове поле, важливе для ясності, нове, це button 8 бітного беззнакового цілого типу.

Я не буду обговорювати всі поля, які ми обговорювали для структури SDL_MouseMotionEvent structure. Увага, якщо порівняти поле стану SDL_MouseButtonEvent, воно працює іншим способом. Це дозволяє лише два значення, SDL_PRESSED або SDL_RELEASED, як відомо з SDL_KeyBoardEvent. Як нагадування: для SDL_MouseMotionEvent, це представлено повним станом всіх кнопок що натискаються під час руху мишки.

Поле button дозволяє розпізнати яка кнопка спрацювала в події SDL_MouseButtonEvent. Кожна кнопка мишки має власний індекс. Як приклад, для моєї мишки вони наступні:: ліва кнопка мишки 1, права кнопка мишки 3, середня кнопка мишки 2, кнопка під великим пальцем 4. Не путайте ці номери індексів зі станами кнопок мишки структури SDL_MouseMotionEvent. Ця подія може обробити лише одну кнопку! Поєднання подій руху неможливі. В коді прикладу це значення просто виводиться на екран використовуючи

sdlEvent^.button.button.

Як бачите, щоб отримати доступ до цього запису Вам потрібна адреса до поля button події. Це не слід плутати з обговореним вище полем button події SDL_MouseButtonEvent.

Поля x і y містять координати (x/y) коли кнопка мишки (індекс якої зберігається в полі button) була натиснута (або відпущена). Це важливо знати. Чого б коштувала програма, якби Ви могли знати, що натиснуто певну кнопку, але ви не знаєте, де саме? Не показано в коді, але ви можете отримати доступ до цих полів за допомогою

sdlEvent.button.x
sdlEvent.button.y

Колесо мишки в SDL 2.0

Якщо використовується колисо мишки, спрацьовує подія SDL_MOUSEWHEEL. Давайте розглянемо відповідну структуру SDL_MouseWheelEvent.

TSDL_MouseWheelEvent = record
    type_: UInt32;        // SDL_MOUSEWHEEL
    timestamp: UInt32;
    windowID: UInt32;    // вікно з фокусом мишки, якщо є
    which: UInt32;       // id мишки, або SDL_TOUCH_MOUSEID
    x: SInt32;           // Рахунок горизонтальної прокрутки 
    y: SInt32;           // Рахунок вертикальної прокрутки
  end;

Тут нові поля - це x і y які цього разу не відповідають позиції курсору мишки. Натомість цього вони посилаються на напрямок в якому відбулась прокрутка. Якщо Ви прокручували колесо мишки вгору або вперід, тут поверне 1, і якщо Ви прокручували назад це поверне -1. Якщо Ви маєте колесо мишки для горизонтальної прогрутки, це буде аналогічно. Між іншим, прокрутка мишкою може бути вирішена як дуже швидке натискання кнопки для потрібного напрямку.

В коді, якщо спрацьовує подія SDL_MOUSEWHEEL, значення y перевіряється на позитивне чи негативне. Це вирішує, яка прокретка відбулась, вгору чи вниз, і буде повернуто та виведено на екран відповідне значення. В будь-якому випадку, чи здогадаєтесь Ви, що трапиться якщо хтось скористається горизонтальною прокруткою? Виконається цей же самий блок коду, оскільки спрацює подія SDL_MOUSEWHEEL. Замість значення y між 1 чи -1, там буде 0, але значення x буде 1 чи -1. Тим не менше, блок else буде виконаний, оскільки y не перевищує 0. Отже, для прикладу програми він буде неправильно виводити, що сталася зворотна прокрутка. У будь-якому випадку, Ви отримали ідею.

Обробка вікон у SDL 2.0

Сучасні програми завжди працюють у вікнах. Знаменита операційна система “Windows” від Microsoft навіть вивела з цього свою назву. Першим завданням для більшості програм SDL 2.0 є створення вікна. Приклад коду створює вікно шириною 500 пікселів і висотою 500 пікселів. Можливо, важливо знати, чи взаємодіє користувач із вікном програми. Щоразу, коли це трапляється, спрацьовує SDL_WINDOWEVENT.

        //події вікна
        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;

Якщо спрацьовує подія типу SDL_WINDOWEVENT, виводиться текстове повідомлення «Window event:».

Щоб отримати доступ до полів події вікна, Вам потрібно звернутись до поля window структури події. Поле event містить інформацію типу події вікна, щоб розрізнити яка саме подія вікна спрацювала.

sdlEvent^.window.event

На відміну від подій клавіатури і мишки, які ми обговорювали раніше, різні типи подій відрізняються не полем type_ field, а додатковим полем event.

У прикладі коду перевіряється шість різних типів подій вікна: SDL_WINDOWEVENT_SHOWN, SDL_WINDOWEVENT_MOVED, SDL_WINDOWEVENT_MINIMIZED, SDL_WINDOWEVENT_MAXIMIZED, SDL_WINDOWEVENT_ENTER і SDL_WINDOWEVENT_LEAVE. З воведеного в вікні тексту Ви можете здогадатися, коли вони спрацьовують. Думаю, тут не потрібно додаткових пояснень.

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

Давайте розглянемо структуру події SDL_WindowEvent.

TSDL_WindowEvent = record
    type_: UInt32;       // SDL_WINDOWEVENT
    timestamp: UInt32;
    windowID: UInt32;    //Асоційоване вікно
    event: UInt8;        // SDL_WindowEventID
    padding1: UInt8;
    padding2: UInt8;
    padding3: UInt8;
    data1: SInt32;       // дані залежні від події
    data2: SInt32;       // дані залежні від події
  end;

Поля type_, timestamp і windowID відомі і мають те ж саме значення,як обговорювалось раніше.

Поле event зберігає ідентифікатор (SDL_WindowEventID) для розрізнення різних подій вікна. Тут вони перераховані, і в дужках ви знайдете дію, пов’язану з вікном, яка викликала їх:

  1. SDL_WINDOWEVENT_SHOWN (вікно було показане)
  2. SDL_WINDOWEVENT_HIDDEN (вікно було сховане)
  3. SDL_WINDOWEVENT_EXPOSED (вікно було виставлене і його слід перемалювати)
  4. SDL_WINDOWEVENT_MOVED (вікно було переміщено до data1, data2)
  5. SDL_WINDOWEVENT_RESIZED (розмір вікна було змінено до data1xdata2; цій події завжди передує SDL_WINDOWEVENT_SIZE_CHANGED)
  6. SDL_WINDOWEVENT_SIZE_CHANGED (розмір вікна змінився або в результаті виклику API, або через зміну розміру вікна через систему або користувача; ця подія супроводжується SDL_WINDOWEVENT_RESIZED, якщо розмір змінився зовнішньою подією, тобто користувачем або менеджером вікон)
  7. SDL_WINDOWEVENT_MINIMIZED (вікно згорнуто)
  8. SDL_WINDOWEVENT_MAXIMIZED (вікно розгорнуто)
  9. SDL_WINDOWEVENT_RESTORED (вікно було відновлено до нормальних розмірів та положення)
  10. SDL_WINDOWEVENT_ENTER вікно отримало фокус миші)
  11. SDL_WINDOWEVENT_LEAVE (вікно втратило фокус миші)
  12. SDL_WINDOWEVENT_FOCUS_GAINED (вікно отримало фокус клавіатури)
  13. SDL_WINDOWEVENT_FOCUS_LOST (вікно втратило фокус клавіатури)
  14. SDL_WINDOWEVENT_CLOSE (менеджер вікон просить закрити вікно)

Цей список базується на інформації, розміщеній на SDL 2.0 wiki.

Якщо Ви уважно прочитаєте список, то помітите згадування data1 і data2, що скоріше пояснює їх наявність у структурі подій :-)! Їх потрібно прочитати для SDL_WINDOWEVENT_MOVED і SDL_WINDOWEVENT_RESIZED, щоб отримати нову позицію вікна або розміри.

На даний момент я не впевнений, чому для подій вікна різниця між окремими подіями вікна (наприклад, SDL_WINDOWEVENT_MOVED, SDL_WINDOWEVENT_RESIZED тощо) робиться не за полем type_, як для клавіатури, миші та інших подій, а за додатковим полем event.

                         end;
      end;
    end;
    SDL_Delay( 20 );
  end;

  dispose( sdlEvent );
  SDL_DestroyWindow ( sdlWindow1 );

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

end.

У заключній частині можна дізнатись не так багато. Цикл затримується на 20 мілісекунд для кращої сприйняття виводу тексту.

Якщо цикл залишено, звільняється вказівник події, знищується вікно SDL 2.0 і відбувається вихід з SDL 2.0. Це все :-)!

Події сенсорного екрану, події джойстика та багато іншого!

У цьому розділі докладно висвітлено події клавіатури, миші та вікна. Майте на увазі, що SDL 2.0 може показати набагато більше! Є ще багато подій, які можна використовувати для розробки додатків. Вони в основному охоплюють будь-який сучасний тип взаємодії, який Ви можете побажати. Сюди входять події з сенсорним екраном (важливі для розробки під смартфони), події джойстика (розробка ігрової консолі) і навіть подія dropfile (перетягування файлів) тощо.

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

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

Опублікувати коментар