Tutorials - TonSharp/WAPITIS GitHub Wiki
Быстрое перемещение:
Quick Start
- Установите фреймворк согласно инструкции в README.MD на главной странице репозитория;
- Откройте файл
main.hpp
.
Минимальный набор функций для запуска программы:
int _main_(MainArgs args)
{
return 0;
}
Подключаем библиотеку для использования всех возможностей фреймворка:
#include "libs.hpp"
Создаём две глобальные переменные: одну для хранения названия класса, вторую для хранения заголовка окна. Будем использовать переменные типа wstring для корректного отображения русских символов:
wstring szMainClass = L"MainClass"; //Модификатор L перед строкой показывает, что её тип - wstring
wstring szTitle = L"Title";
Создаём глобальную переменную окна и инициализируем экземпляр:
Window* wnd; // Объявляем до _main_
int _main_(MainArgs args)
{
wnd = new Window(szTitle, &szMainClass, args);
return 0;
}
Создадим функцию обработки сообщений, которая будет обрабатывать наше главное окно. Функция должна быть объявлена до _main_
(а также должна иметь возвращаемый тип int
и принимать единственный аргумент CallbackArgs
):
int MainCallback(CallbackArgs args)
{
return 1; // "1" обозначает завершение необработанного сообщения
}
Создаём и сразу отображаем окно по умолчанию. В качестве аргументов указываем родительское окно (у главного окна его нет, поэтому пишем NULL
) и только что созданный класс обработки сообщений:
wnd->CreateDefaultWindow(NULL, MainCallback);
Готовый код
#pragma once
#include "libs.hpp"
wstring szMainClass = L"MainClass";
wstring szTitle = L"Title";
Window* wnd;
int MainCallback(CallbackArgs);
int _main_(MainArgs args)
{
wnd = new Window(szTitle, &szMainClass, args);
wnd->CreateDefaultWindow(NULL, MainCallback);
return 0;
}
int MainCallback(CallbackArgs args)
{
return 1;
}
UI Tutorial
В данном примере мы разберем создание пользовательского интерфейса (в качестве основы возьмем лабораторную работу из университета).
Инициализация окна
Для начала напишем код создания окна из примера в начале и получим следующий каркас:
#pragma once
#include "libs.hpp"
wstring szMainClass = L"MainClass";
wstring szTitle = L"Title";
Window* wnd;
int MainCallback(CallbackArgs);
int _main_(MainArgs args)
{
wnd = new Window(szTitle, &szMainClass, args);
wnd->CreateDefaultWindow(NULL, MainCallback);
return 0;
}
int MainCallback(CallbackArgs args)
{
return 1;
}
Создание UI элементов
Добавим переменные необходимых нам элементов интерфейса:
Button* AddButton;
ListBox* list;
ComboBox* FirstList;
ComboBox* SecondList;
RadioButton* FirstRadio;
RadioButton* SecondRadio;
Проинициализируем все элементы в _main_
(обязательно после создания главного окна):
FirstList = new ComboBox(L"1", wnd, args.hInstance);
SecondList = new ComboBox(L"1", wnd, args.hInstance);
FirstRadio = new RadioButton(L"Список 1", wnd, args.hInstance);
SecondRadio = new RadioButton(L"Список 2", wnd, args.hInstance);
AddButton = new Button(L"Добавить", wnd, args.hInstance);
list = new ListBox(L"", wnd, args.hInstance, false);
Создадим все элементы:
//Позиции и размеры элементов просчитаны заранее
FirstList->Create(DROPDOWN, { 10, 10 }, { 270, 100 }); //DROPDOWN обозначает, что список будет "выпадать"
SecondList->Create(DROPDOWN, { 10, 40 }, { 270, 100 });
FirstRadio->Create(BORDER, {10, 70}, {100, 20}); //BORDER обозначает, что элемент будет в тонкой рамке
SecondRadio->Create(BORDER, {180, 70}, {100, 20});
AddButton->Create(NULL, { 10, 100 }, { 270, 30 });
list->Create(BORDER, { 10, 140 }, { 270, 120 });
list->AddVScroll(); //VSCROLL обозначает, что элемент будет иметь вертикальную полосу прокрутки
Для примера добавим по одной строке в каждый список:
FirstList->AddItem(L"FIRST");
SecondList->AddItem(L"SECOND");
И сразу выберем их при запуске программы (по умолчанию в выпадающем списке элементы не выбраны):
FirstList->SelectItem(0);
SecondList->SelectItem(0);
Сделаем первую радиокнопку активной по умолчанию:
FirstRadio->SetCheck(TRUE);
Обработка сообщений
Опишем базовый случай выхода из приложения:
int MainCallback(CallbackArgs args)
{
if(Closing(args)) //Если мы закрываем окно
Quit(); //То выходим полностью из программы
return 1;
}
Приступим к обработке нажатия кнопки в функции обработки сообщений `MainCallback`:
```c++
int MainCallback(CallbackArgs args)
{
if (AddButton && AddButton->IsClicked(args)) //Если кнопка существует и была нажата
{
if (FirstRadio->IsChecked()) //Если выбрана первая радиокнопка:
list->AddItem(FirstList->GetSelectedText()); //Добавляем в нижний список текущий выбранный элемент из первого списка
else if(SecondRadio->IsChecked()) //Иначе, если выбрана вторая радиокнопка:
list->AddItem(SecondList->GetSelectedText()); //Добавляем в нижний список текущий выбранный элемент из второго списка
}
return 1;
}
Осталось добавить возможность удаления элементов из нижнего списка по двойному щелчку левой кнопки мыши:
if (list && list->IsDoubleClick(args)) //Если нижний список существует и был выполнен двойной щелчок левой кнопкой мыши:
list->RemoveSelectedItems(); //Удаляем текущий выбранный элемент
Готовый код
#pragma once
#include "libs.hpp"
wstring szMainClass = L"MainClass";
wstring szTitle = L"Title";
Window* wnd;
Button* AddButton;
ListBox* list;
ComboBox* FirstList;
ComboBox* SecondList;
RadioButton* FirstRadio;
RadioButton* SecondRadio;
int MainCallback(CallbackArgs);
int _main_(MainArgs args)
{
wnd = new Window(szTitle, &szMainClass, args);
wnd->CreateCustomWindow(NULL, WS_OVERLAPPEDWINDOW, { 10, 10 }, { 300, 300 }, NULL, NULL, NULL, MainCallback);
FirstList = new ComboBox(L"1", wnd, args.hInstance);
SecondList = new ComboBox(L"1", wnd, args.hInstance);
FirstRadio = new RadioButton(L"Список 1", wnd, args.hInstance);
SecondRadio = new RadioButton(L"Список 2", wnd, args.hInstance);
AddButton = new Button(L"Добавить", wnd, args.hInstance);
list = new ListBox(L"", wnd, args.hInstance, false);
FirstList->Create(DROPDOWN, { 10, 10 }, { 270, 100 });
SecondList->Create(DROPDOWN, { 10, 40 }, { 270, 100 });
FirstRadio->Create(BORDER, {10, 70}, {100, 20});
SecondRadio->Create(BORDER, {180, 70}, {100, 20});
AddButton->Create(NULL, { 10, 100 }, { 270, 30 });
list->Create(BORDER, { 10, 140 }, { 270, 120 }, false);
list->AddVScroll();
FirstList->AddItem(L"FIRST");
SecondList->AddItem(L"SECOND");
FirstList->SelectItem(0);
SecondList->SelectItem(0);
FirstRadio->SetCheck(TRUE);
return 0;
}
int MainCallback(CallbackArgs args)
{
if(Closing(args))
Quit();
if (AddButton && AddButton->IsClicked(args))
{
if (FirstRadio->IsChecked())
list->AddItem(FirstList->GetSelectedText());
else if(SecondRadio->IsChecked())
list->AddItem(SecondList->GetSelectedText());
}
if (list && list->IsDoubleClick(args))
list->RemoveSelectedItems();
return 1;
}
Menu Tutorial
В данном примере мы создадим верхнее и контекстное меню (основываясь на лабораторной работе из университета).
Инициализация элементов
Нам необходимо два экземпляра меню: одно будет верхним меню, которое располагается под окном, а второе - контекстным, которое отображается при щелчке правой кнопкой мыши по списку.
Объявим нужные глобальные переменные:
wstring szMainClass = L"MainClass";
wstring szTitle = L"Laba 5";
Window* wnd;
ListBox* list;
Menu* mainMenu;
Menu* listContext;
int MainCallback(CallbackArgs);
Проинициализируем эти переменные в _main_
:
int _main_(MainArgs args)
{
wnd = new Window(szTitle, &szMainClass, args);
wnd->CreateDefaultWindow(NULL, MainCallback);
wnd->SetBackgroundColor(RGB(100, 100, 100));
list = new ListBox(L"", wnd, args.hInstance, false);
list->Create(BORDER, { 10, 10 }, { 500, 500 });
}
Создадим основное меню и заполним его:
mainMenu = new Menu(L"Main", false, 500); //Создаем меню. L"Main" является просто отличительным словом. False говорит нам о том, что меню постоянное, а
//не всплывающее
mainMenu->AddSubMenu( //Добавляем первый элемент меню, который будет выпадающим
{
{L"Открыть", MF_STRING }, //Он содержит пункты "Открыть", "Сохранить", разделительную полосу и "Выход"
{L"Сохранить", MF_STRING },
{L"", MF_SEPARATOR },
{L"Выход", MF_STRING }
},
1000, //ID должен быть уникальным у каждого меню и подменю, а также быть не меньше 1000
L"Файл" //А называться главный элемент будет "Файл"
);
mainMenu->AddSubMenu( //Добавим еще одно меню с выпадающим списком Сортировка
{
{L"По производителю 🠕", MF_STRING },
{L"По производителю 🠗", MF_STRING },
{L"По частоте 🠕", MF_STRING },
{L"По частоте 🠗", MF_STRING }
},
2000,
L"Сортировка"
);
Привяжем меню к окну:
mainMenu->Register(*wnd);
Повторим операцию с контекстным меню:
listContext = new Menu(L"List context menu", true, 0); //True говорит нам о том, что это всплывающее меню, а не постоянное
listContext->AddItems(
{
{L"Добавить", MF_STRING},
{L"Изменить", MF_STRING},
{L"Удалить", MF_STRING},
},
3000
);
//Это меню привязывать не нужно, потому что оно является контекстным
Обработка сообщений
Опишем базовый случай выхода из приложения:
int MainCallback(CallbackArgs args)
{
if(Closing(args)) //Если мы закрываем окно:
Quit(); //Полностью выходим из программы
return 1;
}
Проверим, было ли вызвано контекстное меню у списка (щелчок правой кнопкой мыши по области списка):
if (list->IsContextMenu(args))
{
//Do something
}
Если было вызвано, то отображаем контекстное меню и сохраняем ID выбранного пункта:
auto val = listContext->Track(list->Get(), args); //Показываем меню в области list и сохраняем ID выбранной опции в val
Далее мы можем проверить это значение в if:
if (val == listContext->GetIDByMenuIndex(0, 0)) //Проверит, выбран ли первый элемент меню (так как в нашем меню есть только одно подменю с несколькими
{ //элементами, то первый 0 указывает на первое подменю, а второй 0 указывает на первый элемент подменю
MessageBox(wnd->Get(), L"Option A", L"Notify", MB_OK);
}
if (val == listContext->GetIDByMenuIndex(0, 1)) //Проверка на второй элемент
{
MessageBox(wnd->Get(), L"Option B", L"Notify", MB_OK);
}
if (val == listContext->GetIDByMenuIndex(0, 2)) //Проверка на третий элемент
{
MessageBox(wnd->Get(), L"Option С", L"Notify", MB_OK);
}
Теперь за проверкой на контекстное меню, проверим был ли нажат пункт "Выход" основного меню:
if (mainMenu->IsClicked(args, 0, 3)) //Если был нажат четвертый пункт первого подменю (третий пункт - это разделительная полоса):
Quit(); //Выходим из программы
Готовый код
#pragma once
#include "libs.hpp"
wstring szMainClass = L"MainClass";
wstring szTitle = L"Laba 5";
Window* wnd;
ListBox* list;
Menu* mainMenu;
Menu* listContext;
int MainCallback(CallbackArgs);
int _main_(MainArgs args)
{
wnd = new Window(szTitle, &szMainClass, args);
wnd->CreateDefaultWindow(NULL, MainCallback);
wnd->SetBackgroundColor(RGB(100, 100, 100));
list = new ListBox(L"", wnd, args.hInstance, false);
list->Create(BORDER, { 10, 10 }, { 500, 500 });
mainMenu = new Menu(L"Main", false, 500);
mainMenu->AddSubMenu(
{
{L"Открыть", MF_STRING },
{L"Сохранить", MF_STRING },
{L"", MF_SEPARATOR },
{L"Выход", MF_STRING }
},
1000,
L"Файл"
);
mainMenu->AddSubMenu(
{
{L"По производителю 🠕", MF_STRING },
{L"По производителю 🠗", MF_STRING },
{L"По частоте 🠕", MF_STRING },
{L"По частоте 🠗", MF_STRING }
},
2000,
L"Сортировка"
);
mainMenu->Register(*wnd);
listContext = new Menu(L"List context menu", true, 0);
listContext->AddItems(
{
{L"Добавить", MF_STRING},
{L"Изменить", MF_STRING},
{L"Удалить", MF_STRING},
},
3000
);
return 0;
}
int MainCallback(CallbackArgs args)
{
if (Closing(args))
Quit();
if (list->IsContextMenu(args))
{
auto val = listContext->Track(list->Get(), args);
if (val == listContext->GetIDByMenuIndex(0, 0))
MessageBox(wnd->Get(), L"Option B", L"Notify", MB_OK);
if (val == listContext->GetIDByMenuIndex(0, 1))
MessageBox(wnd->Get(), L"Option B", L"Notify", MB_OK);
if (val == listContext->GetIDByMenuIndex(0, 2))
MessageBox(wnd->Get(), L"Option C", L"Notify", MB_OK);
return 0;
}
if (mainMenu->IsClicked(args, 0, 3))
Quit();
return 1;
}
OpenGL Initialization
Первоначальная настройка
Создадим окно приложения, используя базовый код:
#pragma once
#include "libs.hpp"
wstring szMainClass = L"MainClass";
wstring szTitle = L"Title";
Window* wnd;
int MainCallback(CallbackArgs);
int _main_(MainArgs args)
{
wnd = new Window(szTitle, &szMainClass, args);
wnd->CreateCustomWindow(0, GL_WINDOW, { 10, 10 }, { 800, 600 }, NULL, NULL, NULL, MainCallback);
return 0;
}
int MainCallback(CallbackArgs args)
{
if (Closing(args))
{
Quit();
}
return 1;
}
За инициализацию и настройку OpenGL отвечает класс GLContext, создадим указатель на данный класс в глобальных переменных:
GLContext* context;
Создадим функцию-отрисовщик. Данная функция будет осуществлять всю необходимую отрисовку объектов, которые мы создадим:
void MainRenderer()
{
context->ClearBuffers(0); //обязательно очищаем все буферы перед каждой последующей отрисовкой
glLoadIdentity(); //сбрасываем также матрицу обзора.
//вот тут мы будем рисовать
if(context) //эти две строчки будут упрощены в будущем (Если существует контекст)
SwapBuffers(context->GetHDC()); //(Мы меняем второй буфер на котором рисовали на главный (отображаем нарисованное))
}
Создадим экземпляр контекста в _main_
, после создания окна:
wnd->CreateCustomWindow(0, GL_WINDOW, { 10, 10 }, { 800, 600 }, NULL, NULL, NULL, MainCallback); // строчка с нашим созданием окна
context = new GLContext(wnd, NULL, 16, 16, MainRenderer); //наше главное окно, флаги (NULL), число битов цвета (16), число битов глубины (16)
Для того, что бы иметь возможность обрабатывать каждый кадр при отрисовке, добавим функцию Update()
:
void Update()
{
context->Render() //будем вызывать отрисовку нашей графики каждый кадр
//здесь будет наша логика, данная функция будет вызываться каждый кадр.
}
Осталось подключить `Update` к центральному обработчику сообщений. Для этого, в `_main_` добавим следующую строку:
```c++
UpdateCallback.push_back(Update); //где Update - название нашей функции
Отрисовка базовых фигур
Отобразим квадрат в центре экрана, для этого воспользуемся функцией главного отрисовщика. Для рисования многоугольников предусмотрена функция DrawPolygon
, для рисования квадратов по 4 точки каждый - DrawQuads
, для рисования треугольников по 3 точки каждый - DrawTriangles
, у всех них одинаковые параметры.
Что бы правильно выставить координаты при рисовании объекта, важно понимать, как они отсчитываются. Начало координат располагается в центре экрана, вверх уходит ось y в положительном направлении до 1, вниз до -1. Вправо уходит ось x до 1, влево до -1, то же самое и с осью z.
Для рисования квадрата воспользуемся функцией главного отрисовщика, а так же методом DrawQuads:
void MainRenderer()
{
context->ClearBuffers(0); //очищаем экран перед отрисовкой
glLoadIdentity(); //сбрасывает вид
glTranslatef(0, 0, -3.5); // немного отодвинем квадрат вглубь экрана (-3.5 по z координате)
DrawQuads( //первым аргументом является вектор всех точек
{
{-0.5, 0.5, 0}, //первая точка верхняя левая
{0.5, 0.5, 0}, //верхняя правая
{0.5, -0.5, 0}, //нижняя правая
{-0.5, -0.5, 0} //левая нижняя
},
{ 100, 100, 100, 255 } //цвет в формате RGBA (альфа канал в настоящий момент не поддерживается)
);
if(context) //если контекст существует
SwapBuffers(context->HDC()); //меняем буферы
}
Базовое вращение
Добавим постоянное вращение, для этого создадим глобальную переменную Angle:
int Angle;
Теперь будем увеличивать ее каждый кадр:
void Update()
{
context->Render();
Angle+=2;
}
В функции отрисовщике будем изменять вращение:
void MainRenderer()
{
//тут какой-то код
glTranslatef(0, 0, zPos - 3.5); //отодвигаем объекты вглубь
glRotatef(Angle, 1, 1, 1); //вращаем на угол Angle по всем осям одинаково (x = 1, y = 1, z = 1)
//тут какой-то код
}
Готовый код
#pragma once
#include "libs.hpp"
//Сюда добавляйте свои библиотеки:
wstring szMainClass = L"MainClass";
wstring szTitle = L"Title";
Window* wnd;
GLContext* context;
int Angle, zPos;
int MainCallback(CallbackArgs);
void Update();
void MainRenderer();
int _main_(MainArgs args)
{
wnd = new Window(szTitle, &szMainClass, args);
wnd->CreateCustomWindow(0, GL_WINDOW, { 10, 10 }, { 800, 600 }, NULL, NULL, NULL, MainCallback);
context = new GLContext(wnd, 0, 16, 16, MainRenderer); //инициализация графики opengl
context->AddAmbienLight({ 255, 255, 255, 255 }); ///пока не работает
glEnable(GL_LIGHT0);
UpdateCallback.push_back(Update); //чтобы функция вызывалась каждую перерисовку кадра
return 0;
}
int MainCallback(CallbackArgs args)
{
if (Closing(args))
{
Quit();
}
return 1;
}
void Update()
{
context->Render();
Angle += 2;
}
void MainRenderer()
{
context->ClearBuffers(0);
glLoadIdentity();
glTranslatef(0, 0, zPos - 3.5);
glRotatef(Angle, 1, 1, 1);
DrawQuads(
{
{-0.5, 0.5, 0},
{0.5, 0.5, 0},
{0.5, -0.5, 0},
{-0.5, -0.5, 0}
},
{ 100, 100, 100, 255 }
);
if(context)
SwapBuffers(context->HDC());
}
GameObject Tutorial
Класс GameObject
предназначен для работы с каждым объектом отдельно. Вы загружаете или рисуете модель, а потом взаимодействуете с ней.
Загрузка модели
Для начала загрузим obj модель используя встроенные возможности (модель должна быть триангулирована, иначе корректное отображение не гарантируется). Путь поиска моделей осуществляется относительно корня, в полной версии фреймворка вы можете видеть как располагается файл с моделью.
Создадим глобальную переменную объекта:
GameObject* dObj;
Теперь в _main_
инициализируем объект, загрузим модель и наложим текстуры из файла "D.tga":
dObj = new GameObject();
dObj->LoadMesh("D.obj");
dObj->LoadTexture("Shrek.tga");
Немного переместим, повернем модель, а так же уменьшим модель:
dObj->Rotation.X = 90;
dObj->Transform.Z = -2;
dObj->Transform.Y = -0.5;
dObj->ScaleObject(0.5);
Отрисовка модели
В главном отрисовщике нам необходимо вызвать единственную функцию объекта:
dObj->Draw();
Поворот объекта мышью
Привяжем курсор к окну и заблокируем его в _main_
.
Mouse::Link(wnd);
Mouse::LockCursor();
Mouse::HideCursor();
Добавим выход из приложения при нажатии Esc
в обработчике сообщений:
if (Closing(args) || Keyboard::GetKeyDown(args, VK_ESCAPE))
{
Quit();
}
Теперь перед отрисовкой объекта будем менять его угол, в зависимости от разницы в позиции курсора:
dObj->Rotation.Z += Mouse::GetDX();
dObj->Rotation.X += Mouse::GetDY();
Готовый Код
#pragma once
#include "libs.hpp"
wstring szMainClass = L"MainClass";
wstring szTitle = L"Title";
Window* wnd;
GLContext* context;
GameObject *dObj;
int Angle;
float zPos;
int MainCallback(CallbackArgs);
void Update();
void MainRenderer();
int _main_(MainArgs args)
{
dObj = new GameObject();
wnd = new Window(szTitle, &szMainClass, args);
wnd->CreateCustomWindow(0, GL_WINDOW, { 10, 10 }, { 800, 600 }, NULL, NULL, NULL, MainCallback);
context = new GLContext(wnd, 0, 16, 16, MainRenderer);
context->AddSmooth();
context->AddAmbienLight({ 255, 255, 255, 255 });
UpdateCallback.push_back(Update);
Mouse::Link(wnd);
Mouse::LockCursor();
Mouse::HideCursor();
dObj->LoadMesh("D.obj");
dObj->LoadTexture("D.tga");
dObj->Rotation.X = 90;
dObj->Transform.Z = -2;
dObj->Transform.Y = -0.5;
dObj->ScaleObject(0.5);
return 0;
}
int MainCallback(CallbackArgs args)
{
if (Closing(args) || Keyboard::GetKeyDown(args, VK_ESCAPE))
{
Quit();
}
return 1;
}
void Update()
{
context->Render();
}
void MainRenderer()
{
context->ClearBuffers(0);
dObj->Rotation.Z += Mouse::GetDX();
dObj->Rotation.X += Mouse::GetDY();
dObj->Draw();
if(context)
SwapBuffers(context->HDC());
}