Qt widgets

This commit is contained in:
Andrew Golovashevich 2024-11-13 00:11:31 +03:00
parent 6cc15c1989
commit 8264d93da3
4 changed files with 254 additions and 246 deletions

View File

@ -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
src/ui.hpp
src/ui.cpp
)
qt5_use_modules(cg1 Widgets)

View File

@ -1,248 +1,13 @@
// lab_1_basics.cpp : Определяет точку входа для приложения.
#include <QApplication>
#include <QPushButton>
#include "ui.hpp"
#include <Windows.h>
#include <commctrl.h>
#include "stdio.h"
#include "frame.hpp"
#include "painter.hpp"
int main(int argc, char **argv) {
QApplication qapp(argc, argv);
// Windows-приложение для создания буфера кадра
auto w = new MainWindow();
w->show();
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
QApplication::exec();
}
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<PainterState *>(GetWindowLongPtr(hWnd, 0));
auto *painter = *reinterpret_cast<Painter **>(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<PainterState *>(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<Painter const *const *>(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<PainterState *>(GetWindowLongPtr(hWnd, 0))->angle += 0.05;
InvalidateRect(hWnd, nullptr, false);
break;
case WM_DESTROY:
delete reinterpret_cast<PainterState *>(GetWindowLongPtr(hWnd, 0));
SetWindowLongPtr(hWnd, 0, reinterpret_cast<LONG_PTR>(nullptr));
PostQuitMessage(0);
break;
default:
// Все сообщения, не обрабатываемые в данной функции, направляются на обработку по умолчанию
return DefWindowProcA(hWnd, message, wParam, lParam);
}
return 0;
}

129
src/ui.cpp Normal file
View File

@ -0,0 +1,129 @@
#include <QWidget>
#include <QGridLayout>
#include <QLabel>
#include <QResizeEvent>
#include <QPainter>
#include <QDebug>
#include <iostream>
#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<Qt::AlignmentFlag> 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<Qt::Key>(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;
}

107
src/ui.hpp Normal file
View File

@ -0,0 +1,107 @@
#pragma once
#include <QWidget>
#include <QGridLayout>
#include <QLabel>
#include <QSize>
#include <QResizeEvent>
#include <QMutex>
#include <QMainWindow>
#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();
};