diff --git a/CMakeLists.txt b/CMakeLists.txt index 05a84b6..c52ae66 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,17 +3,24 @@ project(cg1) set(CMAKE_CXX_STANDARD 20) +find_package (Qt5Widgets REQUIRED) +set(CMAKE_AUTOMOC ON) add_executable( cg1 WIN32 + src/main.cpp src/frame.hpp src/drawing_assets.hpp src/drawing_assets.cpp src/painter.cpp src/painter.hpp - src/main.cpp + src/old_main.cpp -) \ No newline at end of file + src/ui.hpp + src/ui.cpp +) + +qt5_use_modules(cg1 Widgets) \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index b76397b..b86cf29 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,248 +1,13 @@ -// lab_1_basics.cpp : Определяет точку входа для приложения. +#include +#include +#include "ui.hpp" -#include -#include -#include "stdio.h" -#include "frame.hpp" -#include "painter.hpp" +int main(int argc, char **argv) { + QApplication qapp(argc, argv); -// Windows-приложение для создания буфера кадра - -LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM); - -HWND hWndStatusBar; // Дескриптор компонента StatusBar - -int WINAPI WinMain(HINSTANCE hThisInstance, HINSTANCE hPrevInst, LPSTR lpszArgs, int nWinMode) { - char szWinName[] = "Graphics Window Class"; // Имя класса окна - - HWND hWnd; // Дескриптор главного окна - MSG msg; - WNDCLASSA wcl; // Определитель класса окна - wcl.hInstance = hThisInstance; // Дескриптор приложения - wcl.lpszClassName = szWinName;// Имя класса окна - wcl.lpfnWndProc = WindowProc; // Функция обработки сообщений - wcl.style = 0; // Стиль по умолчанию - wcl.hIcon = LoadIcon(nullptr, IDI_APPLICATION);// Иконка - wcl.hCursor = LoadCursor(nullptr, IDC_ARROW); // Курсор - wcl.lpszMenuName = nullptr; // Без меню - wcl.cbClsExtra = 0; // Без дополнительной информации - wcl.cbWndExtra = sizeof(PainterState *) + sizeof(Painter **); - static_assert(sizeof(PainterState *) == sizeof(LONG_PTR)); - static_assert(sizeof(Painter **) == sizeof(LONG_PTR)); - - - wcl.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); //Белый фон - - if (!RegisterClassA(&wcl)) // Регистрируем класс окна - return 0; - - hWnd = CreateWindowA(szWinName, // Создать окно - "Лабораторная работа №1. Буфер кадра. Алгоритмы Брезенхейма", - WS_OVERLAPPEDWINDOW, // Стиль окна - CW_USEDEFAULT, // x-координата - CW_USEDEFAULT, // y-координата - CW_USEDEFAULT, // Ширина - CW_USEDEFAULT, // Высота - HWND_DESKTOP, // Без родительского окна - nullptr, // Без меню - hThisInstance, // Дескриптор приложения - nullptr); // Без дополнительных аргументов - - // Создаём компонент типа StatusBar - hWndStatusBar = CreateWindowExA( - 0, STATUSCLASSNAMEA, nullptr, - WS_CHILD | WS_VISIBLE | SBARS_SIZEGRIP, - 0, 0, 0, 0, - hWnd, (HMENU) 10001, - hThisInstance, nullptr - ); - - // Настройка частей StatusBar'а - int statwidths[] = {150, 300, 450, -1}; - SendMessageA(hWndStatusBar, SB_SETPARTS, sizeof(statwidths) / sizeof(int), (LPARAM) statwidths); - - ShowWindow(hWnd, nWinMode); // Показать окно - UpdateWindow(hWnd); // Перерисовать окно - - while (GetMessage(&msg, nullptr, 0, 0)) // Запустить цикл обработки сообщений - { - TranslateMessage(&msg); // Разрешить использование клавиатуры - DispatchMessage(&msg); // Вернуть управление операционной системе Windows - } - - return msg.wParam; - -} - -// Следующая функция вызывается операционной системой Windows и получает в качестве -// параметров сообщения из очереди сообщений данного приложения -LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { - static int pixelSize = 8; // Размер "большого" пикселя - - switch (message) { - case WM_CREATE: - SetTimer(hWnd, 1, 1000 / 30, nullptr); - SetWindowLongPtr(hWnd, 0, (LONG_PTR) new PainterState()); - SetWindowLongPtr(hWnd, sizeof(PainterState *), (LONG_PTR) &predefined_painters[0]); - break; - - case WM_PAINT: { - PAINTSTRUCT ps; - - HDC hdc = BeginPaint(hWnd, &ps); - - // Определяем ширину и высоту окна - RECT rect = ps.rcPaint; - GetClientRect(hWnd, &rect); - - int width = rect.right - rect.left; - int height = rect.bottom - rect.top; - - // Рисование в буфер кадра - - int ratio = pixelSize; // Размер "большого" пикселя - - int W = width / ratio; - int H = (height - 22) / ratio; // Отнимем высоту StatusBar'а - - Frame frame(W, H); - auto *state = reinterpret_cast(GetWindowLongPtr(hWnd, 0)); - auto *painter = *reinterpret_cast(GetWindowLongPtr(hWnd, sizeof(PainterState *))); - PixelGridPainter{COLOR{245, 245, 245}, COLOR{240, 240, 240}}.draw(state, &frame); - painter->draw(state, &frame); - - // Системная структура для хранения цвета пикселя - // Буфер кадра, который будет передаваться операционной системе, должен состоять из массива этих структур - // Она не совпадает с порядком следования цветов в формате RBG - typedef struct tagRGBPIXEL { - unsigned char BLUE; // Компонента синего цвета - unsigned char GREEN; // Компонента зелёного цвета - unsigned char RED; // Компонента красного цвета - unsigned char ALPHA; // Прозрачность - } RGBPIXEL; - - // Выделение памяти для второго буфера, который будет передаваться функции CreateBitmap для создания картинки - RGBPIXEL *bitmap = (RGBPIXEL *) HeapAlloc(GetProcessHeap(), 0, width * height * sizeof(RGBPIXEL)); - - // Копирование массива пикселей в соответствии с системным форматом пикселя и масштабирование картинки - // W и H - ширина и высота изображения в буфере кадра - // ratio - коэффициент масштабирования пикселей - for (int y = 0; y < H * ratio; y++) - for (int x = 0; x < W * ratio; x++) { - RGBPIXEL *pixel = bitmap + y * width + x; - COLOR color = frame.get_pixel(x / ratio, y / ratio); - pixel->RED = color.red; - pixel->GREEN = color.green; - pixel->BLUE = color.blue; - pixel->ALPHA = color.alpha; - } - - - // Получить дескриптор на новое растровое изображение - HBITMAP hBitMap = CreateBitmap(width, height, 1, sizeof(RGBPIXEL) * 8, bitmap); - - // Освободить память, которую занимает буфер цвета - HeapFree(GetProcessHeap(), 0, bitmap); - - // Создать в оперативной памяти контекст, совместимый с экранным контекстом, который мы используем, чтобы рисовать - HDC srcHdc = CreateCompatibleDC(hdc); - - // Связать картинку с новым контекстом - SelectObject(srcHdc, hBitMap); - - // Копировать содержимое из временного контекста srcHdc в основной контекст окна hdc - BitBlt( - hdc, // Основной контекст - 0, 0, // Координаты левого верхнего угла, от которого будет выполняться вставка - width, // Ширина вставляемого изображения - height, // Высота вставляемого изображения - srcHdc, // Дескриптор временного контекста - 0, 0, // Координаты считываемого изображения - SRCCOPY); // Параметры операции - копирование - - EndPaint(hWnd, &ps); - - // Удаление картинки из памяти - DeleteObject(hBitMap); - - // Удаление временного контекста - DeleteDC(srcHdc); - } - break; - - case WM_MOUSEMOVE: { - char str[256]; - - SendMessageA(hWndStatusBar, SB_SETTEXTA, 3, (LPARAM) "F4: Сменить рисунок"); - - // Устанавливаем текст в разных частях StatusBar'а - // Экранные координаты курсора мыши - sprintf_s(str, "X = %d, Y = %d", LOWORD(lParam), HIWORD(lParam)); - SendMessageA(hWndStatusBar, SB_SETTEXTA, 2, (LPARAM) str); - - // Координаты пикселя в буфере кадра - sprintf_s(str, "BX = %d, BY = %d", LOWORD(lParam) / pixelSize, HIWORD(lParam) / pixelSize); - SendMessageA(hWndStatusBar, SB_SETTEXTA, 1, (LPARAM) str); - - sprintf_s(str, "Масштаб (F2/F3): %d", pixelSize); - SendMessageA(hWndStatusBar, SB_SETTEXTA, 0, (LPARAM) str); - } - break; - - case WM_LBUTTONDOWN: - reinterpret_cast(GetWindowLongPtr(hWnd, 0))->set_clicked_pixel(LOWORD(lParam) / pixelSize, HIWORD(lParam) / pixelSize); - InvalidateRect(hWnd, nullptr, false); - break; - - case WM_KEYDOWN: - if (wParam == VK_F2 || wParam == VK_F3) { - if (pixelSize > 1 && wParam == VK_F2) pixelSize--; - if (pixelSize < 64 && wParam == VK_F3) pixelSize++; - - InvalidateRect(hWnd, nullptr, false); - - char str[256]; - sprintf_s(str, "Масштаб (F2/F3): %d", pixelSize); - SendMessageA(hWndStatusBar, SB_SETTEXTA, 0, (LPARAM) str); - } - if (wParam == VK_F1) { - MessageBoxA(hWnd, "Работу выполнил студент группы ПВ-221 Колесников А.И.", "О программе", MB_ICONINFORMATION); - } - if (wParam == VK_F4) { - auto newPtr = reinterpret_cast(GetWindowLongPtr(hWnd, sizeof(PainterState *))); - newPtr++; - if (*newPtr == nullptr) - newPtr = &predefined_painters[0]; - - SetWindowLongPtr(hWnd, sizeof(PainterState *), (LONG_PTR) newPtr); - } - break; - - // Обработка сообщения на изменение размера окна - case WM_SIZE: - SendMessageA(hWndStatusBar, WM_SIZE, 0, 0); - InvalidateRect(hWnd, nullptr, false); - break; - - case WM_TIMER: - reinterpret_cast(GetWindowLongPtr(hWnd, 0))->angle += 0.05; - InvalidateRect(hWnd, nullptr, false); - break; - - - - case WM_DESTROY: - delete reinterpret_cast(GetWindowLongPtr(hWnd, 0)); - SetWindowLongPtr(hWnd, 0, reinterpret_cast(nullptr)); - PostQuitMessage(0); - break; - - default: - // Все сообщения, не обрабатываемые в данной функции, направляются на обработку по умолчанию - return DefWindowProcA(hWnd, message, wParam, lParam); - } - - return 0; -} + auto w = new MainWindow(); + w->show(); + QApplication::exec(); +} \ No newline at end of file diff --git a/src/ui.cpp b/src/ui.cpp new file mode 100644 index 0000000..ca618e9 --- /dev/null +++ b/src/ui.cpp @@ -0,0 +1,129 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "ui.hpp" + +const QString StatusBar::_scale_label_template{"[F2]/[F3] Масштаб: %1 пикселей"}; +const QString StatusBar::_painter_label_template{"[Q] Отображаемая фигура: %1/%2"}; +const QString StatusBar::_x_template{"X=%1"}; +const QString StatusBar::_y_template{"Y=%1"}; + +StatusBar::StatusBar(QWidget *parent) : QWidget(parent) { + this->_layout = new QGridLayout(parent); + this->setLayout(this->_layout); + + auto add_label = [&](QLabel **dst, QString text, int row, int column, QFlags alignment) { + *dst = new QLabel(text, this); + this->_layout->addWidget(*dst, row, column, alignment); + }; + + add_label(&(this->_scale_label), StatusBar::_scale_label_template.arg("??"), 0, 0, Qt::AlignRight); + add_label(&(this->_painter_label), StatusBar::_painter_label_template.arg("??", "??"), 1, 0, Qt::AlignRight); + + add_label(&(this->_screen_coordinates.title), "Экранные координаты:", 0, 1, Qt::AlignRight); + add_label(&(this->_screen_coordinates.x), StatusBar::_x_template.arg("??"), 0, 2, Qt::AlignHCenter); + add_label(&(this->_screen_coordinates.y), StatusBar::_y_template.arg("??"), 0, 3, Qt::AlignHCenter); + + add_label(&(this->_scaled_coordinates.title), "Системные координаты:", 1, 1, Qt::AlignRight); + add_label(&(this->_scaled_coordinates.x), StatusBar::_x_template.arg("??"), 1, 2, Qt::AlignHCenter); + add_label(&(this->_scaled_coordinates.y), StatusBar::_y_template.arg("??"), 1, 3, Qt::AlignHCenter); + + this->_layout->setColumnStretch(4, 1); +} + +void StatusBar::set_coordinates(unsigned int screen_x, unsigned int screen_y, unsigned int scaled_x, unsigned int scaled_y) { + this->_screen_coordinates.x->setText(StatusBar::_x_template.arg(screen_x)); + this->_screen_coordinates.y->setText(StatusBar::_y_template.arg(screen_y)); + + this->_scaled_coordinates.x->setText(StatusBar::_x_template.arg(scaled_x)); + this->_scaled_coordinates.y->setText(StatusBar::_y_template.arg(scaled_y)); +} + +void StatusBar::set_scale(unsigned int current_scale) { + this->_scale_label->setText(StatusBar::_scale_label_template.arg(current_scale)); +} + +void StatusBar::set_painter_index(unsigned int index, unsigned int painters_count) { + this->_painter_label->setText(StatusBar::_painter_label_template.arg(index).arg(painters_count)); +} + +Canvas::Canvas(QWidget *parent) : QWidget(parent), _current_frame{}, _sync{} { + this->setMouseTracking(true); + this->grabKeyboard(); + + this->_mouse_processing.is_pressed = false; + this->_mouse_processing.pressed_at = QPoint(0, 0); + this->_mouse_processing.is_moved_while_pressed = false; +} + +void Canvas::set_frame(QImage &f) { + this->_sync.lock(); + this->_current_frame = std::move(f); + this->_sync.unlock(); +} + +void Canvas::paintEvent(QPaintEvent *event) { + QPainter painter{}; + painter.begin(this); + this->_sync.lock(); + painter.drawImage(this->_current_frame.rect(), this->_current_frame); + this->_sync.unlock(); + painter.end(); +} + +void Canvas::resizeEvent(QResizeEvent *event) { + QWidget::resizeEvent(event); + emit this->resized(event->size()); +} + +MainWindow::MainWindow() : QWidget{} { + this->_layout = new QGridLayout(this); + this->setLayout(this->_layout); + + this->_status_bar = new StatusBar(this); + this->_layout->addWidget(this->_status_bar, 0, 0); + + this->_canvas = new Canvas(this); + this->_layout->addWidget(this->_canvas, 1, 0); + + this->_layout->setColumnStretch(0, 1); + this->_layout->setRowStretch(1, 1); +} + +void Canvas::keyPressEvent(QKeyEvent *event) { + emit this->key_pressed(static_cast(event->key())); + std::cout << "key pressed" << std::endl; +} + +void Canvas::mousePressEvent(QMouseEvent *event) { + this->_mouse_processing.is_pressed = true; + this->_mouse_processing.pressed_at = event->pos(); + this->_mouse_processing.is_moved_while_pressed = false; + std::cout << "mouse press" << std::endl; +} + +void Canvas::mouseMoveEvent(QMouseEvent *event) { + if (this->_mouse_processing.is_pressed) { + this->_mouse_processing.is_moved_while_pressed = true; + emit this->pressed_mouse_moved(event->pos()); + } else { + emit this->pressed_mouse_moved(event->pos()); + } + std::cout << "mouse move" << std::endl; +} + +void Canvas::mouseReleaseEvent(QMouseEvent *event) { + this->_mouse_processing.is_pressed = false; + if (this->_mouse_processing.is_moved_while_pressed) { + emit this->mouse_dragged(this->_mouse_processing.pressed_at, event->pos()); + } else { + emit this->mouse_clicked(event->pos()); + } + std::cout << "mouse release" << std::endl; +} + diff --git a/src/ui.hpp b/src/ui.hpp new file mode 100644 index 0000000..41ca1be --- /dev/null +++ b/src/ui.hpp @@ -0,0 +1,107 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + + +#include "frame.hpp" + +class StatusBar : public QWidget { +Q_OBJECT +private: + QGridLayout *_layout; + + QLabel *_scale_label; + static const QString _scale_label_template; + + QLabel *_painter_label; + static const QString _painter_label_template; + + struct { + QLabel *title; + QLabel *x; + QLabel *y; + } _screen_coordinates, _scaled_coordinates; + static const QString _x_template, _y_template; + + +public: + explicit StatusBar(QWidget *parent); + +public slots: + + void set_coordinates(unsigned screen_x, unsigned screen_y, unsigned scaled_x, unsigned scaled_y); + + void set_scale(unsigned current_scale); + + void set_painter_index(unsigned index, unsigned painters_count); +}; + +class Canvas : public QWidget { +Q_OBJECT +private: + QMutex _sync; + QImage _current_frame; + +public: + explicit Canvas(QWidget *parent); + +public slots: + + void set_frame(QImage &f); + +public: + void paintEvent(QPaintEvent *event) override; + + void resizeEvent(QResizeEvent *event) override; + +signals: + + void resized(QSize new_size); + + +private: + struct { + bool is_pressed; + QPoint pressed_at; + bool is_moved_while_pressed; + } _mouse_processing; +public: + + void keyPressEvent(QKeyEvent *event) override; + + void mousePressEvent(QMouseEvent *event) override; + + void mouseMoveEvent(QMouseEvent *event) override; + + void mouseReleaseEvent(QMouseEvent *event) override; + +signals: + + void key_pressed(Qt::Key key); + + void released_mouse_moved(QPoint to); + + void pressed_mouse_moved(QPoint to); + + void mouse_clicked(QPoint where); + + void mouse_dragged(QPoint from, QPoint to); + +}; + +class MainWindow : public QWidget { +Q_OBJECT +private: + QGridLayout *_layout; +public: + StatusBar *_status_bar; + Canvas *_canvas; + + MainWindow(); +}; \ No newline at end of file