This mirrors the previous refactor of the TextRenderer. All calls to draw_rect and draw_triangle now buffer vertex data (position and color). A begin()/flush() system is introduced to manage batching.
222 lines
7.2 KiB
C++
222 lines
7.2 KiB
C++
#include "ui_window.h"
|
|
#include <SDL3/SDL_events.h>
|
|
#include <SDL3/SDL_hidapi.h>
|
|
#include <memory>
|
|
#include "ui/i_window_content.h"
|
|
#include "ui_renderer.h"
|
|
|
|
UIWindow::UIWindow(const char* title, int x, int y, int width, int height) :
|
|
_title(title),
|
|
_x(x),
|
|
_y(y),
|
|
_width(width),
|
|
_height(height),
|
|
_content(nullptr),
|
|
_is_dragging(false),
|
|
_is_hovered(false),
|
|
_is_focused(false),
|
|
_should_close(false),
|
|
_state(WindowState::NORMAL),
|
|
_is_resizing(false),
|
|
_resize_margin(10) {
|
|
/* Init title bar buttons. */
|
|
_title_bar_buttons.push_back({WindowButtonAction::CLOSE, {0.8f, 0.2f, 0.2f}});
|
|
_title_bar_buttons.push_back({WindowButtonAction::MAXIMIZE, {0.2f, 0.8f, 0.2f}});
|
|
_title_bar_buttons.push_back({WindowButtonAction::MINIMIZE, {0.8f, 0.8f, 0.2f}});
|
|
}
|
|
|
|
UIWindow::~UIWindow(void) {}
|
|
|
|
void UIWindow::minimize(void) {
|
|
_state = WindowState::MINIMIZED;
|
|
}
|
|
|
|
void UIWindow::restore(void) {
|
|
_state = WindowState::NORMAL;
|
|
}
|
|
|
|
bool UIWindow::is_minimized(void) const {
|
|
return _state == WindowState::MINIMIZED;
|
|
}
|
|
|
|
const std::string& UIWindow::get_title(void) const {
|
|
return _title;
|
|
}
|
|
|
|
Rect UIWindow::get_rect(void) const {
|
|
return { _x, _y, _width, _height };
|
|
}
|
|
|
|
bool UIWindow::is_mouse_over_resize_handle(int mouse_x, int mouse_y) const {
|
|
return (mouse_x >= _x + _width - _resize_margin && mouse_x <= _x + _width &&
|
|
mouse_y >= _y + _height - _resize_margin &&
|
|
mouse_y <= _y + _height);
|
|
}
|
|
|
|
bool UIWindow::should_close(void) const {
|
|
return _should_close;
|
|
}
|
|
|
|
void UIWindow::update(void) {
|
|
if(_content) {
|
|
if(_content->should_close()) { _should_close = true; }
|
|
}
|
|
}
|
|
|
|
void UIWindow::set_content(std::unique_ptr<IWindowContent> content) {
|
|
_content = std::move(content);
|
|
}
|
|
|
|
void UIWindow::set_focused(bool focused) {
|
|
_is_focused = focused;
|
|
}
|
|
|
|
IWindowContent* UIWindow::get_content(void) {
|
|
return _content.get();
|
|
}
|
|
|
|
bool UIWindow::is_point_inside(int x, int y) {
|
|
return (x >= _x && x <= _x + _width &&
|
|
y >= _y && y <= _y + _height);
|
|
}
|
|
|
|
void UIWindow::render(const RenderContext& context) {
|
|
int title_bar_height = 30;
|
|
|
|
context.ui_renderer->begin_shapes();
|
|
context.ui_renderer->begin_text();
|
|
|
|
/* Define colours. */
|
|
const Color frame_color = { 0.2f, 0.2f, 0.25f };
|
|
const Color title_bar_color = { 0.15f, 0.15f, 0.2f };
|
|
const Color focused_title_bar_color = { 0.3f, 0.3f, 0.4f };
|
|
const Color title_text_color = { 0.9f, 0.9f, 0.9f };
|
|
const Color resize_handle_color = { 0.9f, 0.9f, 0.9f };
|
|
|
|
/* Draw main window frame/background. */
|
|
context.ui_renderer->draw_rect(_x, _y, _width, _height, frame_color);
|
|
|
|
/* Draw title bar. */
|
|
if(_is_focused) {
|
|
context.ui_renderer->draw_rect(_x, _y, _width, title_bar_height, focused_title_bar_color);
|
|
} else {
|
|
context.ui_renderer->draw_rect(_x, _y, _width, title_bar_height, title_bar_color);
|
|
}
|
|
|
|
/* Draw title text. */
|
|
context.ui_renderer->render_text(_title.c_str(), _x+5, _y+20, title_text_color);
|
|
|
|
/* Draw title bar buttons. */
|
|
int button_size = 20;
|
|
int button_margin = 5;
|
|
int x_offset = _x + _width - button_size - button_margin;
|
|
for(const auto& button : _title_bar_buttons) {
|
|
context.ui_renderer->draw_rect(x_offset, _y+button_margin, button_size,
|
|
button_size, button.color);
|
|
x_offset -= (button_size + button_margin);
|
|
}
|
|
|
|
/* Draw Resize handle. */
|
|
int corner_x = _x + _width;
|
|
int corner_y = _y + _height;
|
|
context.ui_renderer->draw_triangle(corner_x, corner_y - 10, corner_x - 10,
|
|
corner_y, corner_x, corner_y, resize_handle_color);
|
|
|
|
/* Flush the shapes and text for the window frame and title bar. */
|
|
context.ui_renderer->flush_shapes();
|
|
context.ui_renderer->flush_text();
|
|
|
|
if(_content) {
|
|
int content_screen_y = _y + title_bar_height;
|
|
int content_height = _height - title_bar_height;
|
|
/* Got to pass GL y-coord for scissor box to work correctly. */
|
|
int content_gl_y = context.screen_height - content_screen_y - content_height;
|
|
_content->render(context, _x, content_screen_y, content_gl_y, _width, content_height);
|
|
}
|
|
}
|
|
|
|
void UIWindow::handle_event(SDL_Event* event, int screen_width,
|
|
int screen_height, int taskbar_height) {
|
|
int title_bar_height = 30;
|
|
|
|
if(event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
|
|
int mouse_x = event->button.x;
|
|
int mouse_y = event->button.y;
|
|
|
|
/* Check for title bar button clicks. */
|
|
int button_size = 20;
|
|
int button_margin = 5;
|
|
int x_offset = _x + _width - button_size - button_margin;
|
|
for(const auto& button : _title_bar_buttons) {
|
|
if(mouse_x >= x_offset && mouse_x <= x_offset + button_size &&
|
|
mouse_y >= _y + button_margin && mouse_y <= _y + button_margin + button_size) {
|
|
switch(button.action) {
|
|
case WindowButtonAction::CLOSE:
|
|
_should_close = true;
|
|
break;
|
|
case WindowButtonAction::MAXIMIZE:
|
|
if(_state == WindowState::MAXIMIZED) {
|
|
_x = _pre_maximize_rect.x;
|
|
_y = _pre_maximize_rect.y;
|
|
_width = _pre_maximize_rect.w;
|
|
_height = _pre_maximize_rect.h;
|
|
_state = WindowState::NORMAL;
|
|
} else {
|
|
_pre_maximize_rect = { _x, _y, _width, _height };
|
|
_x = 0; _y = 0;
|
|
_width = screen_width; _height = screen_height - taskbar_height;
|
|
_state = WindowState::MAXIMIZED;
|
|
}
|
|
break;
|
|
case WindowButtonAction::MINIMIZE:
|
|
minimize();
|
|
break;
|
|
}
|
|
return; /* Button clicked, no need to process further. */
|
|
}
|
|
x_offset -= (button_size + button_margin);
|
|
}
|
|
|
|
/* Check for resize handle click (bottom-right corner). */
|
|
if(is_mouse_over_resize_handle(mouse_x, mouse_y) &&
|
|
_state != WindowState::MAXIMIZED) {
|
|
_is_resizing = true;
|
|
} else if(mouse_x >= _x && mouse_x <= _x + _width &&
|
|
mouse_y >= _y && mouse_y <= _y + title_bar_height &&
|
|
_state != WindowState::MAXIMIZED) {
|
|
/* Is click within title bar? */
|
|
_is_dragging = true;
|
|
_drag_offset_x = mouse_x - _x;
|
|
_drag_offset_y = mouse_y - _y;
|
|
}
|
|
} else if(event->type == SDL_EVENT_MOUSE_BUTTON_UP) {
|
|
_is_dragging = false;
|
|
_is_resizing = false;
|
|
} else if(event->type == SDL_EVENT_MOUSE_MOTION) {
|
|
if(_is_dragging) {
|
|
_x = event->motion.x - _drag_offset_x;
|
|
_y = event->motion.y - _drag_offset_y;
|
|
} else if(_is_resizing) {
|
|
int new_width = event->motion.x - _x;
|
|
int new_height = event->motion.y - _y;
|
|
_width = (new_width > 100) ? new_width : 100; /* Min width. */
|
|
_height = (new_height > 80) ? new_height : 80; /* Min height. */
|
|
}
|
|
|
|
/* Check if mouse hovered over window. */
|
|
_is_hovered = is_point_inside(event->motion.x, event->motion.y);
|
|
} else if(event->type == SDL_EVENT_MOUSE_WHEEL) {
|
|
/* Only scroll window if mouse is hovering. */
|
|
if(_is_hovered && _content) {
|
|
/* SDL's wheel motion is negative scroll up on the y. */
|
|
int content_height_gl = _height - title_bar_height;
|
|
_content->scroll(-event->wheel.y, content_height_gl);
|
|
}
|
|
}
|
|
if(_content) {
|
|
int y_gl = screen_height - _y - _height;
|
|
_content->handle_input(event, _x, _y, y_gl);
|
|
}
|
|
}
|
|
|