LXF115:FLTK
|
|
|
- Программируем с FLTK Быстрый, легкий, поддерживающий OpenGL: выберите любые три!
- Метамодернизм в позднем творчестве В.Г. Сорокина
- ЛитРПГ - последняя отрыжка постмодерна
- "Ричард III и семиотика"
- 3D-визуализация обложки Ridero создаем обложку книги при работе над самиздатом.
- Архитектура метамодерна - говоря о современном искусстве, невозможно не поговорить об архитектуре. В данной статье будет отмечено несколько интересных принципов, характерных для построек "новой волны", столь притягательных и скандальных.
- Литература
- Метамодерн
- Рокер-Прометей против изначального зла в «Песне про советскую милицию» Вени Дркина, Автор: Нина Ищенко, к.ф.н, член Союза Писателей ЛНР - перепубликация из журнала "Топос".
- Как избавиться от комаров? Лучшие типы ловушек.
- Что делать если роблокс вылетает на windows
- Что делать, если ребенок смотрит порно?
- Почему собака прыгает на людей при встрече?
- Какое масло лить в Задний дифференциал (мост) Visco diff 38434AA050
- О чем может рассказать хвост вашей кошки?
- Верветки
- Отчетность бюджетных учреждений при закупках по Закону № 223-ФЗ
- Срок исковой давности как правильно рассчитать
- Дмитрий Патрушев минсельхоз будет ли преемником Путина
- Кто такой Владислав Поздняков? Что такое "Мужское Государство" и почему его признали экстремистским в России?
- Как правильно выбрать машинное масло в Димитровграде?
- Как стать богатым и знаменитым в России?
- Почему фильм "Пипец" (Kick-Ass) стал популярен по всему миру?
- Как стать мудрецом?
- Как правильно установить FreeBSD
- Как стать таким как Путин?
- Где лучше жить - в Димитровграде или в Ульяновске?
- Почему город Димитровград так называется?
- Что такое метамодерн?
- ВАЖНО! Временное ограничение движения автотранспортных средств в Димитровграде
- Тарифы на электроэнергию для майнеров предложено повысить
Сверхскоростная графика
- ЧАСТЬ 2 Пусть Qt и GTK+ лучше подходят для сложных приложений – FLTK блистает там, где интерфейс должен быть незаметным: например, в «демках» OpenGL. Андрей Боровский напишет одну такую.
Какое звено является ведущим в связке потребностей и технологий? Я думаю, что все-таки потребности. Развитие графических адаптеров с миллионами поддерживаемых цветов и пикселей стимулировалось парадигмой WYSIWYG, а не наоборот, а ускорители 3D-графики для ПК появились благодаря трехмерным играм (первые из которых обходились вовсе без ускорителей). А вот внедрение в работающую схему новых технологий по принципу «зачем добру пропадать» редко приносит хорошие результаты. Вот, например, все современные рабочие столы обзавелись трехмерными «примочками» – а часто ли мы ими пользуемся? Тем не менее, раз уж OpenGL распространяется повсюду, то и обзор библиотеки виджетов не может без него обойтись.
OpenGL в FLTK
Как уже отмечалось, поддержка OpenGL была в свое время уникальной и крайне привлекательной чертой FLTK, и даже сейчас с некоторыми проблемами вывода трехмерной графики эта библиотека справляется лучше, нежели другие наборы виджетов. Для работы с OpenGL FLTK предлагает нам два класса: GlWindow и GlutWindow. Как нетрудно догадаться, они наследуют Window и реализуют специальные типы окон, у которых рабочая поверхность подготовлена для вывода графики OpenGL. В остальном, окна GlWindow и GlutWindow подобны окну Window – они могут содержать дочерние виджеты и обрабатывать сообщения, адресованные главному окну программы. Окно GlWindow предоставляет базовую функциональность, необходимую для работы с OpenGL, а окно GlutWindow вдобавок эмулирует функции библиотеки GLUT.
Если вы интересуетесь программированием с OpenGL, то
наверняка уже знаете, что такое GLUT, и тем не менее я это поясню. Интерфейс OpenGL разрабатывался как максимально платформо-независимый. Выразилось это, например, в том, что в OpenGL не были включены функции для обработки сообщений системы и взаимодействия с окнами. Вместе с тем, на практике OpenGL-программы разрабатываются, в основном, в графических многооконных средах, а значит, всем программистам нужен некий минимум средств для взаимодействия между OpenGL и оконной системой. Конечно, разработчики последних тоже не остались в стороне. Для X Window была разработана система GLX, а для Windows GDI – WGL (Wiggle), но эти расширения были довольно сложны и несовместимы друг с другом. Свободную нишу запол- нила разработанная Марком Килгардом [Mark J. Kilgard] библио- тека GLUT, которая отличалась от GLX/WGL простотой использо- вания и кроссплатформенностью (фактически, GLUT на каждой платформе представляет собой надстройку над расширениями конкретной системы). Неудивительно, что в то время многие про- граммисты предпочитали GLUT для разработки надежным кросс- платформенных программ.
Учитывая популярность GLUT, разработчик FLTK Билл Спитцак
[Bill Spitzak] принял мудрое решение – добавить поддержку интер- фейса GLUT в свой набор виджетов. В результате авторы про- грамм, использовавшие GLUT, смогли без труда портировать свой код на FLTK (отметим в скобках, что если вы начинаете писать новую программу, нет никакого смысла использовать класс GlutWindow, так как все то хорошее, что может дать вам библиоте- ка GLUT, реализовано в классе GlWindow). Поскольку библиотека GLUT не является открытым ПО (хотя исходные тексты доступны), Спитцак создал модуль поддержки GLUT с нуля, сохранив совме- стимость на уровне интерфейса. Но так как особенности GLUT нас не интересуют, мы остановимся на работе с окном GlWindow.
Мы напишем минимальную программу, использующую OpenGL
и FLTK, в которой окно-потомок GlWindow будет главным и един- ственным окном приложения (исходный текст программы вы най- дете на диске в архиве ogldemo1).
#include <fltk/GlWindow.h> using namespace fltk; class MyGLWindow : public GlWindow { public: MyGLWindow(int X, int Y, int W,int H, const char* L=0); private: void draw(); }; В объявлении класса окна мы переопределяем конструктор и
виртуальный метод draw(). Не спрашивайте меня, что он делает, я сам скажу: draw() выполняет отрисовку сцены. Давайте посмот- рим на реализацию методов:
#include <fltk/gl.h> #include "MyGLWindow.h" MyGLWindow::MyGLWindow(int X,int Y,int W,int H,const char*L) : GlWindow(X,Y,W,H,L) { } void MyGLWindow::draw() { if (!valid()) { glLoadIdentity(); glViewport(0,0,w(),h()); glOrtho(-w(),w(),-h(),h(),-1,1); } glClear(GL_COLOR_BUFFER_BIT); glBegin(GL_TRIANGLES); glColor3f(1.0f,0.0f,0.0f); glVertex2f(w() - 10, h() - 10); glColor3f(0.0f,1.0f,0.0f); glVertex2f(10 -w() , 10 -h()); glColor3f(0.0f,0.0f,1.0f); glVertex2f(10 -w() , h() - 10); glEnd(); } Как уже отмечалось, одной из проблем вывода графики OpenGL
является необходимость изменять параметры матрицы проекти- рования при изменении размеров окна. В GlWindow вы можете совместить настройку матрицы проектирования и код, формиру- ющий изображение, в одном методе draw(), благодаря свойству valid() класса GlWindow (о понятии свойства в FLTK говорилось в LXF113/114). Свойство valid() принимает значение 0, если окно только что создано, если его размеры были изменены или прои- зошло переключение графических контекстов. После завершения вызова метода draw() свойство valid() принимает ненулевое значе- ние. Таким образом, мы можем организовать проверку значения valid() в начале метода draw(). Если свойство равно 0, значит, тре- буется перенастроить матрицу проектирования, в противном слу- чае мы можем сразу приступить к выводу изображения.
У класса GlWindow есть метод resize(), объявленный в разделе
protected, который вызывается при их изменении размеров окна, так что у вас может возникнуть соблазн переопределить его и разместить в нем код перенастройки проектирования. Не делай- те этого! В результате вы получите совсем не то, чего ожидали. Переопределение метода resize() может понадобиться только в том случае, если окну GlWindow приходится иметь дело с не-OpenGL элементами, например, с дочерними виджетами. Не могу не отме- тить, что в наборе примеров FLTK Cheats (http://seriss.com/people/ erco/fltk/#OpenGlSimpleWidgets), которыми часто пользуются для изучения FLTK, допущена ошибка – код перенастройки проектиро- вания вызывается и в методе draw(), и в методе resize() (и, кроме того, добавлен в конструктор окна). Ошибка незаметна, так как «правильный» код в методе draw() перекрывает неправильный, но повторять эту небрежность не следует.
Для компиляции программы воспользуемся командой g++ MyGLWindow.cpp main.cpp -lfltk2 -lfltk2_gl -lGL -o ogldemo Обратите внимание, что кроме стандартной библиотеки
OpenGL нам требуется подключить к файлу программы библио- теку libfltk2_gl. Теперь мы можем наслаждаться зрелищем радуж- ного треугольника (рис. 1), который вы, конечно, уже видели бес- счетное количество раз.
В заключение перечислим несколько полезных функций клас-
са GlWindow. Свойство context() позволяет управлять контекста- ми OpenGL. Оно имеет тип GLContext, который на платформе X соответствует типу GLXContext, а в среде GDI – HGLRC. Благодаря context() вы можете вызывать напрямую функции оконных рас- ширений OpenGL для данной платформы. С помощью метода mode() можно указать ряд параметров OpenGL, таких как исполь- зование альфа-канала, двойной буферизации, буфера трафарета и т.п. Метод ortho() настраивает матрицу проектирования таким образом, что начало системы координат OpenGL совпадает с нижним левым углом окна, а точка в координатах OpenGL соот- ветствует одному пикселю экрана. Этот режим особенно удобен, когда OpenGL используется для работы с двумерными изображе- ниями. Метод swap_buffers () управляет переключением буферов OpenGL. Обработка событий Вы помните времена, когда программист MS-DOS, желающий добавить в свою программу такой простой элемент интерфейса, как кнопку, должен был выполнять все операции по ее отрисов- ке, используя единый цикл обработки сообщений программы? Прелесть концепции виджетов заключается в разделении обязан- ностей. Большую часть времени виджеты сами заботятся о себе (поддерживают свой внешний вид, изменяют размеры и поло- жение в зависимости от геометрии окна) и беспокоят вашу про- грамму только тогда, когда им действительно «есть, что сказать». Сообщения, которые виджеты посылают программе, можно раз- делить на две категории, или, точнее, на два уровня. Сообщения низкого уровня обычно связаны с действиями устройств ввода (нажата клавиша на клавиатуре, переместился указатель мыши); высокоуровневые же сообщения, как правило, отражают логику работы виджета. Сообщения высокого уровня часто основаны на событиях низкого уровня, но могут и не зависеть от них (виджет может сообщать о событии, связанном с внутренней работой про- граммы, а не с внешним действием).
На первый взгляд может показаться, что система виджетов
должна предоставлять программисту средства обработки исклю- чительно высокоуровневых сообщений, но, поскольку ни один, даже самый тщательно продуманный набор виджетов не может удовлетворить всех программистских запросов, следует преду- смотреть и возможность обработки сообщений низкого уровня. Примером двухуровневой системы обработки сообщений может служить система событий и сигналов в Qt. События Qt соответ- ствуют сообщениям низкого уровня, тогда как сигналы отражают функциональность виджетов. В FLTK обработка сообщений низ- кого уровня выполняется с помощью механизма событий, а обра- ботка сообщений высокого уровня, порожденных виджетами – с помощью функций обратного вызова.
Для обработки событий FLTK классы-потомки fltk::Widget
используют метод handle(), объявленный как
int Widget::handle( int event) В параметре этого метода передается численный идентифи-
катор события. Метод должен вернуть ненулевое значение, если событие было обработано корректно, и 0 в противном случае. Хотя метод handle() вызывается для обработки всех событий, связанных с виджетом, мы, как правило, хотим обрабатывать самостоятельно только некоторые события, возложив все про- чее на систему. Шаблон перегруженного метода handle() можно представить так:
int MyWidget::handle(int event) { switch(event) { ... default: return BaseWidget::handle(event); } } Интересующие нас события перехватываются в теле оператора
switch(), а для обработки остальных событий мы вызываем метод handle() базового класса. Каким образом с помощью одного чис- лового параметра метода handle() программе передается инфор- мация обо всем многообразии событий, на которые должен реа- гировать виджет? На самом деле параметр event содержит инфор- мацию только о типе события – остальные сведения обработчик получает с помощью вспомогательных функций. Объявления различных констант и функций, необходимых для обработки событий, содержатся в заголовочном файле fltk/events.h. Давайте рассмотрим механизмы обработки некоторых распространенных типов событий более подробно.
Манипуляции с мышью порождают одно из пяти событий:
ENTER – указатель мыши вошел в область виджета, LEAVE – ука- затель покинул область виджета, PUSH – нажата одна из кнопок мыши, DRAG – указатель мыши перетаскивается при нажатой кнопке (это событие генерируется периодически, до тех пор, пока кнопка не будет отпущена), RELEASE – кнопка отпущена. Код кнопки мыши, вызвавшей событие, можно получить с помощью функции event_key(): значения 1, 2, 3 обозначают левую, среднюю и правую кнопки, соответственно. Положение указателя в момент возникновения события можно выяснить с помощью функций event_x() и event_y(). Любопытно отметить, как FLTK сигнализи- рует о прокрутке колесика мыши. Прокрутка колесика порождает серию событий MOUSEWHEEL. Функция event_dy() возвращает количество единиц прокрутки (положительное значение для про- крутки вверх и отрицательное – для прокрутки вниз). Если про- крутка сопровождается удерживанием средней кнопки мыши, помимо события MOUSEWHEEL генерируется серия событий RELEASE (без парных им сообщений PUSH). Функция event_key() при этом возвращает значение 4 (прокрутка вверх) или 5 (про- крутка вниз).
Нажатие клавиши на клавиатуре порождает события KEY (кла-
виша нажата) и KEYUP (клавиша отпущена). Функция event_key() позволяет получить код клавиши (она работает для любой клави- ши клавиатуры), а функция event_text() – код символа (для сим- вольной клавиши). Значение, возвращаемое event_text(), зави- сит от настроек локали и выбранной раскладки клавиатуры. С помощью функции event_key() можно связывать специальные действия с несимвольными клавишами. Кроме того, эта функ- ция удобна, когда некоторое действие должно выполняться при нажатии на символьную клавишу независимо от выбранной рас- кладки клавиатуры (меня, например, бесят программы, в которых сочетания клавиш Ctrl+C, Ctrl+V и Ctrl+Z перестают работать при переключении на русскую раскладку). Для многих кодов клавиш, возвращаемых функцией event_key(), определены константы- мнемоники, например, EscapeKey, HomeKey, LeftKey, UpKey, RightKey, DownKey, PageUpKey, PageDownKey, EndKey, PrintKey.
Если нажать и удерживать клавишу на клавиатуре, генериру-
ется серия событий KEY без соответствующих им событий KEYUP. В ходе своих экспериментов с обработкой событий FLTK я обнару- жил одну странность: событие KEYUP генерируется не тогда, ког- да ранее нажатая клавиша отпущена, а в момент нажатия следую- щей клавиши (сразу за событием KEYUP генерируется событие KEY, соответствующее нажатию новой клавиши). Не думаю, что разработчикам следует полагаться на своевременность события KEYUP в FLTK (в некоторых ситуациях это событие вообще может не случиться).
Любопытно отметить, что функции event_key(), event_x() и им
подобные не являются методами классов виджетов. Это само- стоятельные функции, которые получают информацию о пара- метрах события из статических переменных, спрятанных в недрах FLTK. Такой подход нельзя назвать особо элегантным с точки зре- ния объектно-ориентированного программирования. Кроме того, поскольку функции «не знают», для какого события они вызваны, обработка событий возможна строго в порядке их поступления.
Хотя обычно источником событий являются устройства ввода,
их можно генерировать и программно. Для этого служит метод send() класса fltk::Widget. Единственным аргументом метода дол- жен быть численный идентификатор события. Метод send() пред- ставляет собой, по сути, обертку вокруг метода handle(), однако перед тем как вызвать обработчик событий, send() выполняет некоторые полезные действия, например, сохраняет координаты x и y для событий, связанных с мышью. А что делать, если вы хоти- те эмулировать не только событие, но и его параметры, например, указать собственные координаты мыши? Для этого придется вос- пользоваться недокументированной возможностью – напрямую обратиться к тем самым статическим переменным, в которых сохраняются параметры события. Имена переменных начинаются с префикса e_, и их можно найти в файле fltk/events.h. Например, координаты указателя мыши хранятся в переменных e_x и e_y.
Вы можете установить глобальный обработчик для всех собы-
тий, которые не смогли обработать виджеты FLTK (необработан- ными считаются события, для которых метод handle() вернул зна- чение 0). Заголовок функции обработчика должен иметь вид
int handler_name(int event, fltk::Window * window). В параметре event обработчику передается идентификатор
события, а в параметре window – указатель на объект-окно, которому оно предназначалось (поскольку речь идет о необра- ботанных событиях, система не всегда может определить окно- получателя). Установка обработчика выполняется с помощью функции add_event_handler(). Живой OpenGL Чтобы продемонстрировать обработку событий FLTK на прак- тике, мы добавим в нашу программу элемент интерактивности (новый вариант вы найдете в архиве ogdemo2). Пользователь сможет перетаскивать треугольник в окне, «ухватившись» за него мышью. Для этого добавим в класс MyGLWindow метод handle() и несколько вспомогательных полей:
class MyGLWindow : public GlWindow { public: MyGLWindow(int X, int Y, int W,int H, const char* L=0); private: int x1, y1, x2, y2, x3, y3, oldX, oldY; bool moving, isFullScreen; void draw(); int handle(int event); }; Реализация метода handle() следует описанной выше схеме: int MyGLWindow::handle(int event) { switch(event) { case PUSH: if (event_key() == 1) { unsigned int pixel[3] = {0,0,0}; glReadPixels(event_x(), h() - event_y(), 1, 1, GL_RGB, GL_ UNSIGNED_INT, &pixel); if ((pixel[0] + pixel[1] + pixel[2]) != 0) { moving = true; oldX = event_x(); oldY = event_y(); } } return 1; case DRAG: if (moving) { x1 += (event_x() - oldX)*2; x2 += (event_x() - oldX)*2; x3 += (event_x() - oldX)*2; y1 -= (event_y() - oldY)*2; y2 -= (event_y() - oldY)*2; y3 -= (event_y() - oldY)*2; oldX = event_x(); oldY = event_y(); redraw(); } return 1; case RELEASE: moving = false; return 1; case KEY: switch (event_key()) { case EscapeKey: destroy(); case 102: isFullScreen ? fullscreen_off(0, 0, 500, 300) : fullscreen(); isFullScreen = !isFullScreen; break; default: ; } return 1; default: return GlWindow::handle(event); } Я намеренно не останавливаюсь на особенностях работы
OpenGL в данной программе – на эту тему можно было бы напи- сать отдельную статью. Мы обрабатываем события мыши PUSH, DRAG и RELEASE. Кроме них, в нашем методе handle() обрабаты- ваются события клавиатуры: нажатие на клавишу Esc приводит к завершению работы программы, а кнопка F переключает ее между полноэкранным и оконным режимами, для чего используются методы fullscreen() и fullscreen_off(). Они реализованы в классе Window, а не GlWindow, но, как вы понимаете, при работе с трех- мерной графикой они особенно полезны. Обратите внимание, что для идентификации клавиши F мы пользуемся значением функ- ции event_key(), то есть эта клавиша будет работать независимо от раскладки клавиатуры и состояния CapsLock. Функции обратного вызова Рассмотрим теперь механизм обработки событий высокого уров- ня. Как было сказано выше, для передачи сообщений программе виджеты FLTK используют функции обратного вызова. Попросту говоря, вы можете указать виджету FLTK функцию, которую сле- дует вызвать тогда, когда с ним произойдет нечто, достойное внимания программы. Для каждого виджета можно зарегистри- ровать только одну такую функцию. Это связано с убеждением разработчика FLTK в том, что каждый виджет может создавать только одно «интересное» событие. Интерфейс функций обрат- ного вызова FLTK прост настолько, насколько это возможно: все функции обратного вызова имеют заголовок вида
void callback_fn(Widget *, void *)
В первом параметре функции передается указатель на объект- виджет, породивший событие, второй параметр представляет собой указатель на произвольный блок данных, определен- ный программистом. Установить его можно с помощью метода user_data(), которым обладает каждый класс-потомок fltk::Widget. Для регистрации функции обратного вызова используется метод callback(), который, опять же, есть у каждого класса, реализую- щего виджет.
Вот, собственно, и все. Как вы можете видеть, функция обрат-
ного вызова не возвращает никаких значений. Дополнительные сведения, необходимые для обработки события, можно получить с помощью свойств виджета, вызвавшего функцию, а также с помощью тех функций, которыми мы пользовались для обработ- ки событий низкого уровня. В частности, функция event(), объ- явленная в файле fltk/events.h, позволяет узнать, какое именно низкоуровневое событие заставило виджет сделать обратный вызов. В интерактивной программе OpenGL я добавил функцию обратного вызова для главного окна программы. Оно вызывает ее в одном-единственном случае – когда пользователь пытается закрыть это окно с помощью кнопки [x] в его заголовке. Сама функция обратного вызова выглядит просто:
void exit_callback(Widget* widget, void*) { if (ask("Вы действительно хотите выйти?")) ((MyGLWindow*)widget)->hide(); } Функция ask() выводит на экран модальное диалоговое окно с
кнопками Yes и No (рис. 2).
Возвращаемое функцией значение соответствует нажатой
кнопке. Если пользователь нажал Yes, мы закрываем главное окно программы с помощью его метода hide(), что приводит к завершению работы всей программы.
Последнее, что нам осталось сделать – зарегистрировать
функции обратного вызова в функции main():
MyGLWindow win(0, 0, 500, 300, "OpenGL Test App"); win.callback(exit_callback); Возможно, библиотека FLTK – не лучший выбор для соз-
дания больших и сложных приложений, но она хорошо под- ходит для создания небольших программ, например, «демок» OpenGL. Возможно также, что опыт FLTK пригодится вам, если когда-нибудь вы захотите написать собственный набор вид- жетов. LXF