2023-11-16 22:58:47 +01:00
|
|
|
#include "Window.h"
|
|
|
|
|
#include "Resources.h"
|
|
|
|
|
#include "Graphics.h"
|
|
|
|
|
#include "Enums.h"
|
|
|
|
|
#include "Command.h"
|
|
|
|
|
#include <GLFW/glfw3.h>
|
|
|
|
|
|
|
|
|
|
using namespace Seele;
|
|
|
|
|
using namespace Seele::Vulkan;
|
|
|
|
|
|
|
|
|
|
double currentFrameDelta = 0;
|
|
|
|
|
double Gfx::getCurrentFrameDelta()
|
|
|
|
|
{
|
|
|
|
|
return currentFrameDelta;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void glfwKeyCallback(GLFWwindow* handle, int key, int, int action, int modifier)
|
|
|
|
|
{
|
2023-12-03 00:29:02 +01:00
|
|
|
if (key == -1)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-11-16 22:58:47 +01:00
|
|
|
Window* window = (Window*)glfwGetWindowUserPointer(handle);
|
2023-11-18 11:04:30 +01:00
|
|
|
window->keyPress((KeyCode)key, (InputAction)action, (KeyModifier)modifier);
|
2023-11-16 22:58:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void glfwMouseMoveCallback(GLFWwindow* handle, double xpos, double ypos)
|
|
|
|
|
{
|
|
|
|
|
Window* window = (Window*)glfwGetWindowUserPointer(handle);
|
2023-11-18 11:04:30 +01:00
|
|
|
window->mouseMove(xpos, ypos);
|
2023-11-16 22:58:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void glfwMouseButtonCallback(GLFWwindow* handle, int button, int action, int modifier)
|
|
|
|
|
{
|
|
|
|
|
Window* window = (Window*)glfwGetWindowUserPointer(handle);
|
2023-11-18 11:04:30 +01:00
|
|
|
window->mouseButton((MouseButton)button, (InputAction)action, (KeyModifier)modifier);
|
2023-11-16 22:58:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void glfwScrollCallback(GLFWwindow* handle, double xoffset, double yoffset)
|
|
|
|
|
{
|
|
|
|
|
Window* window = (Window*)glfwGetWindowUserPointer(handle);
|
2023-11-18 11:04:30 +01:00
|
|
|
window->scroll(xoffset, yoffset);
|
2023-11-16 22:58:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void glfwFileCallback(GLFWwindow* handle, int count, const char** paths)
|
|
|
|
|
{
|
|
|
|
|
Window* window = (Window*)glfwGetWindowUserPointer(handle);
|
2023-11-18 11:04:30 +01:00
|
|
|
window->fileDrop(count, paths);
|
2023-11-16 22:58:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void glfwCloseCallback(GLFWwindow* handle)
|
|
|
|
|
{
|
|
|
|
|
Window* window = (Window*)glfwGetWindowUserPointer(handle);
|
2023-11-18 11:04:30 +01:00
|
|
|
window->close();
|
2023-11-16 22:58:47 +01:00
|
|
|
}
|
2024-01-24 23:10:33 +01:00
|
|
|
|
|
|
|
|
void glfwFramebufferResizeCallback(GLFWwindow* handle, int width, int height)
|
2023-11-17 22:31:26 +01:00
|
|
|
{
|
|
|
|
|
Window* window = (Window*)glfwGetWindowUserPointer(handle);
|
|
|
|
|
window->resize(width, height);
|
|
|
|
|
}
|
2023-11-16 22:58:47 +01:00
|
|
|
|
|
|
|
|
Window::Window(PGraphics graphics, const WindowCreateInfo &createInfo)
|
|
|
|
|
: graphics(graphics)
|
|
|
|
|
, preferences(createInfo)
|
|
|
|
|
, instance(graphics->getInstance())
|
|
|
|
|
, swapchain(VK_NULL_HANDLE)
|
|
|
|
|
{
|
2024-01-31 09:49:53 +01:00
|
|
|
float xscale, yscale;
|
|
|
|
|
glfwGetMonitorContentScale(glfwGetPrimaryMonitor(), &xscale, &yscale);
|
2023-11-16 22:58:47 +01:00
|
|
|
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
2024-01-31 09:49:53 +01:00
|
|
|
GLFWwindow *handle = glfwCreateWindow(createInfo.width / xscale, createInfo.height / yscale, createInfo.title, nullptr, nullptr);
|
2023-11-16 22:58:47 +01:00
|
|
|
windowHandle = handle;
|
|
|
|
|
glfwSetWindowUserPointer(handle, this);
|
|
|
|
|
|
|
|
|
|
glfwSetKeyCallback(handle, &glfwKeyCallback);
|
|
|
|
|
glfwSetCursorPosCallback(handle, &glfwMouseMoveCallback);
|
|
|
|
|
glfwSetMouseButtonCallback(handle, &glfwMouseButtonCallback);
|
|
|
|
|
glfwSetScrollCallback(handle, &glfwScrollCallback);
|
|
|
|
|
glfwSetDropCallback(handle, &glfwFileCallback);
|
|
|
|
|
glfwSetWindowCloseCallback(handle, &glfwCloseCallback);
|
2024-01-24 23:10:33 +01:00
|
|
|
glfwSetFramebufferSizeCallback(handle, &glfwFramebufferResizeCallback);
|
|
|
|
|
//glfwSetWindowSizeCallback(handle, &glfwResizeCallback);
|
2023-11-16 22:58:47 +01:00
|
|
|
|
|
|
|
|
glfwCreateWindowSurface(instance, handle, nullptr, &surface);
|
|
|
|
|
|
2023-11-17 22:31:26 +01:00
|
|
|
querySurface();
|
2023-11-16 22:58:47 +01:00
|
|
|
chooseSwapSurfaceFormat();
|
|
|
|
|
framebufferFormat = cast(format.format);
|
|
|
|
|
chooseSwapPresentMode();
|
|
|
|
|
chooseSwapExtent();
|
|
|
|
|
framebufferWidth = extent.width;
|
|
|
|
|
framebufferHeight = extent.height;
|
|
|
|
|
createSwapChain();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Window::~Window()
|
|
|
|
|
{
|
|
|
|
|
vkDestroySwapchainKHR(graphics->getDevice(), swapchain, nullptr);
|
|
|
|
|
vkDestroySurfaceKHR(instance, surface, nullptr);
|
|
|
|
|
glfwDestroyWindow(static_cast<GLFWwindow *>(windowHandle));
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-28 21:42:18 +01:00
|
|
|
void Window::pollInput()
|
2023-11-16 22:58:47 +01:00
|
|
|
{
|
|
|
|
|
glfwPollEvents();
|
2023-12-28 21:42:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Window::beginFrame()
|
|
|
|
|
{
|
2024-01-24 23:10:33 +01:00
|
|
|
imageAvailableFences[currentSemaphoreIndex]->wait(100000);
|
|
|
|
|
imageAvailableFences[currentSemaphoreIndex]->reset();
|
|
|
|
|
vkAcquireNextImageKHR(graphics->getDevice(), swapchain, std::numeric_limits<uint64>::max(), imageAvailableSemaphores[currentSemaphoreIndex]->getHandle(), imageAvailableFences[currentSemaphoreIndex]->getHandle(), ¤tImageIndex);
|
2024-01-31 09:49:53 +01:00
|
|
|
graphics->getGraphicsCommands()->getCommands()->waitForSemaphore(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, imageAvailableSemaphores[currentSemaphoreIndex]);
|
2023-11-16 22:58:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Window::endFrame()
|
|
|
|
|
{
|
2024-01-31 09:49:53 +01:00
|
|
|
swapChainTextures[currentImageIndex]->changeLayout(Gfx::SE_IMAGE_LAYOUT_PRESENT_SRC_KHR,
|
|
|
|
|
Gfx::SE_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, Gfx::SE_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
|
|
|
|
Gfx::SE_ACCESS_MEMORY_READ_BIT, Gfx::SE_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
|
2023-11-16 22:58:47 +01:00
|
|
|
graphics->getGraphicsCommands()->submitCommands(renderingDoneSemaphores[currentSemaphoreIndex]);
|
|
|
|
|
VkSemaphore renderDoneHandle = renderingDoneSemaphores[currentSemaphoreIndex]->getHandle();
|
|
|
|
|
VkPresentInfoKHR presentInfo = {
|
|
|
|
|
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
|
|
|
|
|
.pNext = nullptr,
|
|
|
|
|
.waitSemaphoreCount = 1,
|
|
|
|
|
.pWaitSemaphores = &renderDoneHandle,
|
|
|
|
|
.swapchainCount = 1,
|
|
|
|
|
.pSwapchains = &swapchain,
|
|
|
|
|
.pImageIndices = ¤tImageIndex,
|
|
|
|
|
.pResults = nullptr,
|
|
|
|
|
};
|
|
|
|
|
VkResult r = vkQueuePresentKHR(graphics->getGraphicsCommands()->getQueue()->getHandle(), &presentInfo);
|
|
|
|
|
if(r == VK_SUCCESS)
|
|
|
|
|
{ }
|
|
|
|
|
else if (r == VK_ERROR_OUT_OF_DATE_KHR || r == VK_SUBOPTIMAL_KHR)
|
|
|
|
|
{
|
|
|
|
|
createSwapChain();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
VK_CHECK(r);
|
|
|
|
|
}
|
|
|
|
|
currentSemaphoreIndex = (currentSemaphoreIndex + 1) % Gfx::numFramesBuffered;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Window::onWindowCloseEvent()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Gfx::PTexture2D Window::getBackBuffer()
|
|
|
|
|
{
|
|
|
|
|
return PTexture2D(swapChainTextures[currentImageIndex]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Window::setKeyCallback(std::function<void(KeyCode, InputAction, KeyModifier)> callback)
|
|
|
|
|
{
|
|
|
|
|
keyCallback = callback;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Window::setMouseMoveCallback(std::function<void(double, double)> callback)
|
|
|
|
|
{
|
|
|
|
|
mouseMoveCallback = callback;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Window::setMouseButtonCallback(std::function<void(MouseButton, InputAction, KeyModifier)> callback)
|
|
|
|
|
{
|
|
|
|
|
mouseButtonCallback = callback;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Window::setScrollCallback(std::function<void(double, double)> callback)
|
|
|
|
|
{
|
|
|
|
|
scrollCallback = callback;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Window::setFileCallback(std::function<void(int, const char**)> callback)
|
|
|
|
|
{
|
|
|
|
|
fileCallback = callback;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Window::setCloseCallback(std::function<void()> callback)
|
|
|
|
|
{
|
|
|
|
|
closeCallback = callback;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-09 16:41:12 +02:00
|
|
|
void Window::setResizeCallback(std::function<void(uint32, uint32)> callback)
|
2023-11-18 11:04:30 +01:00
|
|
|
{
|
|
|
|
|
resizeCallback = callback;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Window::keyPress(KeyCode code, InputAction action, KeyModifier modifier)
|
|
|
|
|
{
|
|
|
|
|
keyCallback(code, action, modifier);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Window::mouseMove(double x, double y)
|
|
|
|
|
{
|
|
|
|
|
mouseMoveCallback(x, y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Window::mouseButton(MouseButton button, InputAction action, KeyModifier modifier)
|
|
|
|
|
{
|
|
|
|
|
mouseButtonCallback(button, action, modifier);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Window::scroll(double x, double y)
|
|
|
|
|
{
|
|
|
|
|
scrollCallback(x, y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Window::fileDrop(int num, const char** files)
|
|
|
|
|
{
|
|
|
|
|
fileCallback(num, files);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Window::close()
|
|
|
|
|
{
|
|
|
|
|
closeCallback();
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-17 22:31:26 +01:00
|
|
|
void Window::resize(int width, int height)
|
|
|
|
|
{
|
2023-12-28 21:42:18 +01:00
|
|
|
if (width == 0 || height == 0)
|
|
|
|
|
{
|
|
|
|
|
paused = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
paused = false;
|
2023-11-17 22:31:26 +01:00
|
|
|
querySurface();
|
|
|
|
|
chooseSwapSurfaceFormat();
|
|
|
|
|
framebufferFormat = cast(format.format);
|
|
|
|
|
chooseSwapPresentMode();
|
|
|
|
|
chooseSwapExtent();
|
|
|
|
|
framebufferWidth = extent.width;
|
|
|
|
|
framebufferHeight = extent.height;
|
|
|
|
|
createSwapChain();
|
2023-11-18 11:04:30 +01:00
|
|
|
resizeCallback(width, height);
|
2023-11-17 22:31:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Window::querySurface()
|
|
|
|
|
{
|
|
|
|
|
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(graphics->getPhysicalDevice(), surface, &capabilities);
|
|
|
|
|
|
|
|
|
|
uint32 numFormats;
|
|
|
|
|
vkGetPhysicalDeviceSurfaceFormatsKHR(graphics->getPhysicalDevice(), surface, &numFormats, nullptr);
|
|
|
|
|
supportedFormats.resize(numFormats);
|
|
|
|
|
vkGetPhysicalDeviceSurfaceFormatsKHR(graphics->getPhysicalDevice(), surface, &numFormats, supportedFormats.data());
|
|
|
|
|
|
|
|
|
|
uint32 numPresentModes;
|
|
|
|
|
vkGetPhysicalDeviceSurfacePresentModesKHR(graphics->getPhysicalDevice(), surface, &numPresentModes, nullptr);
|
|
|
|
|
supportedPresentModes.resize(numFormats);
|
|
|
|
|
vkGetPhysicalDeviceSurfacePresentModesKHR(graphics->getPhysicalDevice(), surface, &numPresentModes, supportedPresentModes.data());
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-16 22:58:47 +01:00
|
|
|
void Window::chooseSwapSurfaceFormat()
|
|
|
|
|
{
|
|
|
|
|
for (const auto& supportedFormat : supportedFormats)
|
|
|
|
|
{
|
|
|
|
|
if (supportedFormat.format == cast(preferences.preferredFormat))
|
|
|
|
|
{
|
|
|
|
|
format = supportedFormat;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (const auto& supportedFormat : supportedFormats)
|
|
|
|
|
{
|
|
|
|
|
if (supportedFormat.format == VK_FORMAT_R8G8B8A8_SRGB && supportedFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
|
|
|
|
|
{
|
|
|
|
|
format = supportedFormat;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
format = supportedFormats[0];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Window::chooseSwapPresentMode()
|
|
|
|
|
{
|
2023-11-22 13:18:54 +01:00
|
|
|
/*for (const auto& supportedPresentMode : supportedPresentModes)
|
2023-11-16 22:58:47 +01:00
|
|
|
{
|
|
|
|
|
if (supportedPresentMode == VK_PRESENT_MODE_MAILBOX_KHR)
|
|
|
|
|
{
|
|
|
|
|
presentMode = supportedPresentMode;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-11-22 13:18:54 +01:00
|
|
|
}*/
|
2023-11-16 22:58:47 +01:00
|
|
|
presentMode = VK_PRESENT_MODE_FIFO_KHR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Window::chooseSwapExtent()
|
|
|
|
|
{
|
|
|
|
|
if (capabilities.currentExtent.width != std::numeric_limits<uint32>::max()) {
|
|
|
|
|
extent = capabilities.currentExtent;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
int width, height;
|
|
|
|
|
glfwGetFramebufferSize(static_cast<GLFWwindow*>(windowHandle), &width, &height);
|
|
|
|
|
|
|
|
|
|
extent = {
|
|
|
|
|
static_cast<uint32>(width),
|
|
|
|
|
static_cast<uint32>(height),
|
|
|
|
|
};
|
|
|
|
|
extent.width = std::clamp(extent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width);
|
|
|
|
|
extent.height = std::clamp(extent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Window::createSwapChain()
|
|
|
|
|
{
|
|
|
|
|
uint32 imageCount = Gfx::numFramesBuffered;
|
|
|
|
|
VkSwapchainCreateInfoKHR createInfo = {
|
|
|
|
|
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
|
|
|
|
|
.pNext = nullptr,
|
|
|
|
|
.surface = surface,
|
|
|
|
|
.minImageCount = Gfx::numFramesBuffered,
|
|
|
|
|
.imageFormat = format.format,
|
|
|
|
|
.imageColorSpace = format.colorSpace,
|
|
|
|
|
.imageExtent = extent,
|
|
|
|
|
.imageArrayLayers = 1,
|
2023-12-10 22:27:59 +01:00
|
|
|
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
|
2023-11-16 22:58:47 +01:00
|
|
|
.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
|
|
|
|
.preTransform = capabilities.currentTransform,
|
|
|
|
|
.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
|
|
|
|
|
.presentMode = presentMode,
|
|
|
|
|
.clipped = VK_TRUE,
|
|
|
|
|
.oldSwapchain = swapchain,
|
|
|
|
|
};
|
|
|
|
|
VK_CHECK(vkCreateSwapchainKHR(graphics->getDevice(), &createInfo, nullptr, &swapchain));
|
|
|
|
|
|
2023-11-17 18:46:37 +01:00
|
|
|
VK_CHECK(vkGetSwapchainImagesKHR(graphics->getDevice(), swapchain, &imageCount, swapChainImages.data()));
|
2023-11-16 22:58:47 +01:00
|
|
|
|
2023-11-17 18:46:37 +01:00
|
|
|
for (uint32 i = 0; i < imageCount; ++i)
|
2023-11-16 22:58:47 +01:00
|
|
|
{
|
|
|
|
|
swapChainTextures[i] = new Texture2D(graphics, TextureCreateInfo{
|
|
|
|
|
.format = cast(format.format),
|
|
|
|
|
.width = extent.width,
|
|
|
|
|
.height = extent.height,
|
|
|
|
|
.depth = 1,
|
|
|
|
|
.mipLevels = 1,
|
|
|
|
|
.layers = 1,
|
|
|
|
|
.elements = 1,
|
|
|
|
|
.samples = 1,
|
2023-12-10 22:27:59 +01:00
|
|
|
.usage = Gfx::SE_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | Gfx::SE_IMAGE_USAGE_TRANSFER_DST_BIT,
|
2023-11-16 22:58:47 +01:00
|
|
|
}, swapChainImages[i]);
|
2024-01-24 23:10:33 +01:00
|
|
|
if(imageAvailableFences[i] != nullptr)
|
|
|
|
|
{
|
|
|
|
|
imageAvailableFences[i]->wait(100000);
|
|
|
|
|
imageAvailableFences[i]->reset();
|
|
|
|
|
}
|
|
|
|
|
imageAvailableFences[i] = new Fence(graphics);
|
2023-11-16 22:58:47 +01:00
|
|
|
imageAvailableSemaphores[i] = new Semaphore(graphics);
|
|
|
|
|
renderingDoneSemaphores[i] = new Semaphore(graphics);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-09 16:41:12 +02:00
|
|
|
Viewport::Viewport(PWindow owner, const ViewportCreateInfo &viewportInfo)
|
|
|
|
|
: Gfx::Viewport(owner, viewportInfo)
|
2023-11-16 22:58:47 +01:00
|
|
|
{
|
|
|
|
|
handle.width = static_cast<float>(sizeX);
|
|
|
|
|
handle.height = static_cast<float>(sizeY);
|
|
|
|
|
handle.x = static_cast<float>(offsetX);
|
|
|
|
|
handle.y = static_cast<float>(offsetY) + handle.height;
|
|
|
|
|
handle.height = -handle.height;
|
|
|
|
|
handle.minDepth = 0.f;
|
|
|
|
|
handle.maxDepth = 1.f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Viewport::~Viewport()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Viewport::resize(uint32 newX, uint32 newY)
|
|
|
|
|
{
|
|
|
|
|
sizeX = newX;
|
|
|
|
|
sizeY = newY;
|
|
|
|
|
handle.width = static_cast<float>(sizeX);
|
|
|
|
|
handle.y = static_cast<float>(sizeY + offsetX);
|
|
|
|
|
handle.height = -static_cast<float>(sizeY);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Viewport::move(uint32 newOffsetX, uint32 newOffsetY)
|
|
|
|
|
{
|
|
|
|
|
offsetX = newOffsetX;
|
|
|
|
|
offsetY = newOffsetY;
|
|
|
|
|
handle.x = static_cast<float>(offsetX);
|
|
|
|
|
handle.y = static_cast<float>(offsetY + sizeY);
|
|
|
|
|
}
|