/* * InfiniPaint * Copyright (C) 2025-2026 Yousef Khadadeh * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "GUIManager.hpp" #include #include #include #include #include #include #include #include "Elements/GUIStuffHelpers.hpp" #include #include #include #include #include #include #include #include //#define CLAY_DEBUG_MENU_ENABLED SkColor4f convert_clay_color_to_skcolor4f(const Clay_Color& color) { #ifdef CLAY_DEBUG_MENU_ENABLED if(color.a > 1.0f) return {color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f}; #endif return convert_vec4(color); } namespace GUIStuff { GUIManager::GUIManager() { Clay_SetMaxMeasureTextCacheWordCount(100000); Clay_SetMaxElementCount(32768); clayArena = Clay_CreateArenaWithCapacityAndMemory(Clay_MinMemorySize(), malloc(Clay_MinMemorySize())); clayInstance = Clay_Initialize(clayArena, Clay_Dimensions(1.0f, 1.0f), (Clay_ErrorHandler)clay_error_handler); Clay_SetMeasureTextFunction(clay_skia_measure_text, this); #ifdef CLAY_DEBUG_MENU_ENABLED Clay_SetDebugModeEnabled(true); #endif setToLayout = true; postCallbackFuncIsHighPriority = false; lastInteractionIsTouch = false; } Clay_Dimensions GUIManager::clay_skia_measure_text(Clay_StringSlice str, Clay_TextElementConfig* config, void* userData) { GUIManager* window = static_cast(userData); skia::textlayout::ParagraphStyle pStyle; pStyle.setTextAlign(skia::textlayout::TextAlign::kLeft); skia::textlayout::TextStyle tStyle; tStyle.setFontSize(config->fontSize); tStyle.setFontFamilies(window->io.fonts->get_default_font_families()); pStyle.setTextStyle(tStyle); skia::textlayout::ParagraphBuilderImpl a(pStyle, window->io.fonts->collection, SkUnicodes::ICU::Make()); a.addText(str.chars, str.length); std::unique_ptr p = a.Build(); p->layout(std::numeric_limits::max()); return Clay_Dimensions(p->getMaxIntrinsicWidth(), p->getHeight()); } void GUIManager::calculate_new_clip_rect(std::vector>& clipRectStack, std::optional>& clipRect, bool& clipNoDraw) { if(clipRectStack.empty()) { clipNoDraw = false; clipRect = std::nullopt; return; } SCollision::AABB toRet = clipRectStack.front(); for(size_t i = 1; i < clipRectStack.size(); i++) { if(!SCollision::collide(toRet, clipRectStack[i])) { clipNoDraw = true; clipRect = std::nullopt; return; } toRet = toRet.get_intersection_between_aabbs(clipRectStack[i]); } clipNoDraw = false; clipRect = toRet; } void GUIManager::clip_rect_transform(SkCanvas* canvas, std::vector>& clipRectStack, std::optional>& clipRect, bool& clipNoDraw) { canvas->save(); // Calculating clip rect before transform should resolve the 1 pixel edge bug calculate_new_clip_rect(clipRectStack, clipRect, clipNoDraw); if(clipRect.has_value()) { SCollision::AABB c = clipRect.value(); c.min *= io.guiScaleMultiplier; c.max *= io.guiScaleMultiplier; canvas->clipIRect(c.get_sk_irect()); } canvas->scale(io.guiScaleMultiplier, io.guiScaleMultiplier); } void GUIManager::draw(SkCanvas* c, bool skiaAA) { if(io.redrawSurface) { oldRenderCommandMap.clear(); setToUpdateInvalidateDrawAreaFromLayout = false; } update_invalidated_draw_area_from_layout(); if(io.redrawSurface || invalidDrawBB.has_value()) { SkCanvas* canvas = io.surface->getCanvas(); std::optional> clipRect; std::vector> clipRectStack; bool clipNoDraw = false; if(!io.redrawSurface) clipRectStack.emplace_back(invalidDrawBB.value()); clip_rect_transform(canvas, clipRectStack, clipRect, clipNoDraw); canvas->clear({0.0f, 0.0f, 0.0f, 0.0f}); for(size_t i = 0; i < static_cast(renderCommands.length); i++) { Clay_RenderCommand* command = Clay_RenderCommandArray_Get(&renderCommands, i); Clay_BoundingBox bb = command->boundingBox; if(command->commandType != CLAY_RENDER_COMMAND_TYPE_SCISSOR_START && command->commandType != CLAY_RENDER_COMMAND_TYPE_SCISSOR_END) { if(clipNoDraw || (clipRect.has_value() && !SCollision::collide(get_invalid_draw_bb_from_command(command), clipRect.value()))) continue; } switch(command->commandType) { case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { Clay_RectangleRenderData* config = &command->renderData.rectangle; SkVector radii[4] = { {config->cornerRadius.topLeft, config->cornerRadius.topLeft}, {config->cornerRadius.topRight, config->cornerRadius.topRight}, {config->cornerRadius.bottomRight, config->cornerRadius.bottomRight}, {config->cornerRadius.bottomLeft, config->cornerRadius.bottomLeft} }; SkRRect rrect; rrect.setRectRadii( SkRect::MakeXYWH(bb.x, bb.y, bb.width, bb.height), radii ); SkPaint paint; paint.setAntiAlias(skiaAA); paint.setStyle(SkPaint::kFill_Style); paint.setColor4f(convert_clay_color_to_skcolor4f(config->backgroundColor)); canvas->drawRRect(rrect, paint); break; } case CLAY_RENDER_COMMAND_TYPE_TEXT: { Clay_TextRenderData* config = &command->renderData.text; skia::textlayout::ParagraphStyle pStyle; pStyle.setTextAlign(skia::textlayout::TextAlign::kLeft); skia::textlayout::TextStyle tStyle; tStyle.setFontSize(config->fontSize); tStyle.setFontFamilies(io.fonts->get_default_font_families()); tStyle.setColor(convert_clay_color_to_skcolor4f(config->textColor).toSkColor()); pStyle.setTextStyle(tStyle); skia::textlayout::ParagraphBuilderImpl a(pStyle, io.fonts->collection, SkUnicodes::ICU::Make()); a.addText(config->stringContents.chars, config->stringContents.length); std::unique_ptr p = a.Build(); p->layout(std::numeric_limits::max()); p->paint(canvas, bb.x, bb.y); break; } case CLAY_RENDER_COMMAND_TYPE_BORDER: { Clay_BorderRenderData* config = &command->renderData.border; SkPaint p; p.setColor4f(convert_clay_color_to_skcolor4f(config->color)); p.setStyle(SkPaint::kStroke_Style); p.setAntiAlias(skiaAA); float halfLineWidth = 0.0f; // Top Left corner if (config->cornerRadius.topLeft > 0.0f) { SkPathBuilder path; float lineWidth = config->width.top; p.setStrokeWidth(lineWidth); path.moveTo(bb.x + halfLineWidth, bb.y + config->cornerRadius.topLeft + halfLineWidth); path.arcTo(SkPoint{(bb.x + halfLineWidth), (bb.y + halfLineWidth)}, SkPoint{(bb.x + config->cornerRadius.topLeft + halfLineWidth), (bb.y + halfLineWidth)}, config->cornerRadius.topLeft); canvas->drawPath(path.detach(), p); } // Top border if (config->width.top > 0.0f) { SkPathBuilder path; float lineWidth = config->width.top; p.setStrokeWidth(lineWidth); path.moveTo((bb.x + config->cornerRadius.topLeft + halfLineWidth), (bb.y + halfLineWidth)); path.lineTo((bb.x + bb.width - config->cornerRadius.topRight - halfLineWidth), (bb.y + halfLineWidth)); canvas->drawPath(path.detach(), p); } // Top Right Corner if (config->cornerRadius.topRight > 0.0f) { SkPathBuilder path; float lineWidth = config->width.top; p.setStrokeWidth(lineWidth); path.moveTo((bb.x + bb.width - config->cornerRadius.topRight - halfLineWidth), (bb.y + halfLineWidth)); path.arcTo(SkPoint{(bb.x + bb.width - halfLineWidth), (bb.y + halfLineWidth)}, SkPoint{(bb.x + bb.width - halfLineWidth), (bb.y + config->cornerRadius.topRight + halfLineWidth)}, config->cornerRadius.topRight); canvas->drawPath(path.detach(), p); } // Right border if (config->width.right > 0.0f) { SkPathBuilder path; float lineWidth = config->width.right; p.setStrokeWidth(lineWidth); path.moveTo((bb.x + bb.width - halfLineWidth), (bb.y + config->cornerRadius.topRight + halfLineWidth)); path.lineTo((bb.x + bb.width - halfLineWidth), (bb.y + bb.height - config->cornerRadius.bottomRight - halfLineWidth)); canvas->drawPath(path.detach(), p); } // Bottom Right Corner if (config->cornerRadius.bottomRight > 0.0f) { SkPathBuilder path; float lineWidth = config->width.bottom; p.setStrokeWidth(lineWidth); path.moveTo((bb.x + bb.width - halfLineWidth), (bb.y + bb.height - config->cornerRadius.bottomRight - halfLineWidth)); path.arcTo(SkPoint{(bb.x + bb.width - halfLineWidth), (bb.y + bb.height - halfLineWidth)}, SkPoint{(bb.x + bb.width - config->cornerRadius.bottomRight - halfLineWidth), (bb.y + bb.height - halfLineWidth)}, config->cornerRadius.bottomRight); canvas->drawPath(path.detach(), p); } // Bottom Border if (config->width.bottom > 0.0f) { SkPathBuilder path; float lineWidth = config->width.bottom; p.setStrokeWidth(lineWidth); path.moveTo((bb.x + config->cornerRadius.bottomLeft + halfLineWidth), (bb.y + bb.height - halfLineWidth)); path.lineTo((bb.x + bb.width - config->cornerRadius.bottomRight - halfLineWidth), (bb.y + bb.height - halfLineWidth)); canvas->drawPath(path.detach(), p); } // Bottom Left Corner if (config->cornerRadius.bottomLeft > 0.0f) { SkPathBuilder path; float lineWidth = config->width.bottom; p.setStrokeWidth(lineWidth); path.moveTo((bb.x + config->cornerRadius.bottomLeft + halfLineWidth), (bb.y + bb.height - halfLineWidth)); path.arcTo(SkPoint{(bb.x + halfLineWidth), (bb.y + bb.height - halfLineWidth)}, SkPoint{(bb.x + halfLineWidth), (bb.y + bb.height - config->cornerRadius.bottomLeft - halfLineWidth)}, config->cornerRadius.bottomLeft); canvas->drawPath(path.detach(), p); } // Left Border if (config->width.left > 0.0f) { SkPathBuilder path; float lineWidth = config->width.left; p.setStrokeWidth(lineWidth); path.moveTo((bb.x + halfLineWidth), (bb.y + bb.height - config->cornerRadius.bottomLeft - halfLineWidth)); path.lineTo((bb.x + halfLineWidth), (bb.y + config->cornerRadius.topRight + halfLineWidth)); canvas->drawPath(path.detach(), p); } break; } case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { Clay_ClipRenderData* clip = &command->renderData.clip; if(!clip->horizontal) { bb.x = 0.0f; bb.width = 30000.0f; } if(!clip->vertical) { bb.y = 0.0f; bb.height = 30000.0f; } bb.x -= 1; bb.y -= 1; bb.width += 1; bb.height += 1; canvas->restore(); clipRectStack.emplace_back(clay_bounding_box_to_aabb(bb)); clip_rect_transform(canvas, clipRectStack, clipRect, clipNoDraw); break; } case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { if(!clipRectStack.empty()) { canvas->restore(); clipRectStack.pop_back(); clip_rect_transform(canvas, clipRectStack, clipRect, clipNoDraw); } break; } case CLAY_RENDER_COMMAND_TYPE_CUSTOM: { Element* customElement = static_cast(command->renderData.custom.customData); customElement->clay_draw(canvas, io, command, skiaAA); break; } default: break; } } canvas->restore(); } invalidDrawBB = std::nullopt; io.redrawSurface = false; c->drawImage(io.surface->makeTemporaryImage(), 0, 0, {SkFilterMode::kNearest, SkMipmapMode::kNone}, nullptr); } void GUIManager::draw_force(SkCanvas* canvas, bool skiaAA) { canvas->save(); canvas->scale(io.guiScaleMultiplier, io.guiScaleMultiplier); for(size_t i = 0; i < static_cast(renderCommands.length); i++) { Clay_RenderCommand* command = Clay_RenderCommandArray_Get(&renderCommands, i); Clay_BoundingBox bb = command->boundingBox; switch(command->commandType) { case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { Clay_RectangleRenderData* config = &command->renderData.rectangle; SkVector radii[4] = { {config->cornerRadius.topLeft, config->cornerRadius.topLeft}, {config->cornerRadius.topRight, config->cornerRadius.topRight}, {config->cornerRadius.bottomRight, config->cornerRadius.bottomRight}, {config->cornerRadius.bottomLeft, config->cornerRadius.bottomLeft} }; SkRRect rrect; rrect.setRectRadii( SkRect::MakeXYWH(bb.x, bb.y, bb.width, bb.height), radii ); SkPaint paint; paint.setAntiAlias(skiaAA); paint.setStyle(SkPaint::kFill_Style); paint.setColor4f(convert_clay_color_to_skcolor4f(config->backgroundColor)); canvas->drawRRect(rrect, paint); break; } case CLAY_RENDER_COMMAND_TYPE_TEXT: { Clay_TextRenderData* config = &command->renderData.text; skia::textlayout::ParagraphStyle pStyle; pStyle.setTextAlign(skia::textlayout::TextAlign::kLeft); skia::textlayout::TextStyle tStyle; tStyle.setFontSize(config->fontSize); tStyle.setFontFamilies(io.fonts->get_default_font_families()); tStyle.setColor(convert_clay_color_to_skcolor4f(config->textColor).toSkColor()); pStyle.setTextStyle(tStyle); skia::textlayout::ParagraphBuilderImpl a(pStyle, io.fonts->collection, SkUnicodes::ICU::Make()); a.addText(config->stringContents.chars, config->stringContents.length); std::unique_ptr p = a.Build(); p->layout(std::numeric_limits::max()); p->paint(canvas, bb.x, bb.y); break; } case CLAY_RENDER_COMMAND_TYPE_BORDER: { Clay_BorderRenderData* config = &command->renderData.border; SkPaint p; p.setColor4f(convert_clay_color_to_skcolor4f(config->color)); p.setStyle(SkPaint::kStroke_Style); p.setAntiAlias(skiaAA); float halfLineWidth = 0.0f; // Top Left corner if (config->cornerRadius.topLeft > 0.0f) { SkPathBuilder path; float lineWidth = config->width.top; p.setStrokeWidth(lineWidth); path.moveTo(bb.x + halfLineWidth, bb.y + config->cornerRadius.topLeft + halfLineWidth); path.arcTo(SkPoint{(bb.x + halfLineWidth), (bb.y + halfLineWidth)}, SkPoint{(bb.x + config->cornerRadius.topLeft + halfLineWidth), (bb.y + halfLineWidth)}, config->cornerRadius.topLeft); canvas->drawPath(path.detach(), p); } // Top border if (config->width.top > 0.0f) { SkPathBuilder path; float lineWidth = config->width.top; p.setStrokeWidth(lineWidth); path.moveTo((bb.x + config->cornerRadius.topLeft + halfLineWidth), (bb.y + halfLineWidth)); path.lineTo((bb.x + bb.width - config->cornerRadius.topRight - halfLineWidth), (bb.y + halfLineWidth)); canvas->drawPath(path.detach(), p); } // Top Right Corner if (config->cornerRadius.topRight > 0.0f) { SkPathBuilder path; float lineWidth = config->width.top; p.setStrokeWidth(lineWidth); path.moveTo((bb.x + bb.width - config->cornerRadius.topRight - halfLineWidth), (bb.y + halfLineWidth)); path.arcTo(SkPoint{(bb.x + bb.width - halfLineWidth), (bb.y + halfLineWidth)}, SkPoint{(bb.x + bb.width - halfLineWidth), (bb.y + config->cornerRadius.topRight + halfLineWidth)}, config->cornerRadius.topRight); canvas->drawPath(path.detach(), p); } // Right border if (config->width.right > 0.0f) { SkPathBuilder path; float lineWidth = config->width.right; p.setStrokeWidth(lineWidth); path.moveTo((bb.x + bb.width - halfLineWidth), (bb.y + config->cornerRadius.topRight + halfLineWidth)); path.lineTo((bb.x + bb.width - halfLineWidth), (bb.y + bb.height - config->cornerRadius.bottomRight - halfLineWidth)); canvas->drawPath(path.detach(), p); } // Bottom Right Corner if (config->cornerRadius.bottomRight > 0.0f) { SkPathBuilder path; float lineWidth = config->width.bottom; p.setStrokeWidth(lineWidth); path.moveTo((bb.x + bb.width - halfLineWidth), (bb.y + bb.height - config->cornerRadius.bottomRight - halfLineWidth)); path.arcTo(SkPoint{(bb.x + bb.width - halfLineWidth), (bb.y + bb.height - halfLineWidth)}, SkPoint{(bb.x + bb.width - config->cornerRadius.bottomRight - halfLineWidth), (bb.y + bb.height - halfLineWidth)}, config->cornerRadius.bottomRight); canvas->drawPath(path.detach(), p); } // Bottom Border if (config->width.bottom > 0.0f) { SkPathBuilder path; float lineWidth = config->width.bottom; p.setStrokeWidth(lineWidth); path.moveTo((bb.x + config->cornerRadius.bottomLeft + halfLineWidth), (bb.y + bb.height - halfLineWidth)); path.lineTo((bb.x + bb.width - config->cornerRadius.bottomRight - halfLineWidth), (bb.y + bb.height - halfLineWidth)); canvas->drawPath(path.detach(), p); } // Bottom Left Corner if (config->cornerRadius.bottomLeft > 0.0f) { SkPathBuilder path; float lineWidth = config->width.bottom; p.setStrokeWidth(lineWidth); path.moveTo((bb.x + config->cornerRadius.bottomLeft + halfLineWidth), (bb.y + bb.height - halfLineWidth)); path.arcTo(SkPoint{(bb.x + halfLineWidth), (bb.y + bb.height - halfLineWidth)}, SkPoint{(bb.x + halfLineWidth), (bb.y + bb.height - config->cornerRadius.bottomLeft - halfLineWidth)}, config->cornerRadius.bottomLeft); canvas->drawPath(path.detach(), p); } // Left Border if (config->width.left > 0.0f) { SkPathBuilder path; float lineWidth = config->width.left; p.setStrokeWidth(lineWidth); path.moveTo((bb.x + halfLineWidth), (bb.y + bb.height - config->cornerRadius.bottomLeft - halfLineWidth)); path.lineTo((bb.x + halfLineWidth), (bb.y + config->cornerRadius.topRight + halfLineWidth)); canvas->drawPath(path.detach(), p); } break; } case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { Clay_ClipRenderData* clip = &command->renderData.clip; if(!clip->horizontal) { bb.x = 0.0f; bb.width = std::numeric_limits::max(); } if(!clip->vertical) { bb.y = 0.0f; bb.height = std::numeric_limits::max(); } canvas->save(); SkRect clipRect = SkRect::MakeXYWH(bb.x - 1.0f, bb.y - 1.0f, bb.width + 1.0f, bb.height + 1.0f); canvas->clipRect(clipRect); break; } case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { canvas->restore(); break; } case CLAY_RENDER_COMMAND_TYPE_CUSTOM: { Element* customElement = static_cast(command->renderData.custom.customData); customElement->clay_draw(canvas, io, command, skiaAA); break; } default: break; } } canvas->restore(); } SCollision::AABB GUIManager::get_invalid_draw_bb_from_command(const Clay_RenderCommand* command) { BorderData extraPadding; if(command->commandType == CLAY_RENDER_COMMAND_TYPE_BORDER) { const Clay_BorderRenderData& border = command->renderData.border; extraPadding.top = border.width.top; extraPadding.bottom = border.width.bottom; extraPadding.left = border.width.left; extraPadding.right = border.width.right; } return get_invalid_draw_bb(clay_bounding_box_to_aabb(command->boundingBox), extraPadding); } SCollision::AABB GUIManager::get_invalid_draw_bb(SCollision::AABB bb, const BorderData& extraPadding) { constexpr float EXTRA_AREA = 3.0f; bb.min.y() -= EXTRA_AREA + extraPadding.top; bb.max.y() += EXTRA_AREA + extraPadding.bottom; bb.min.x() -= EXTRA_AREA + extraPadding.left; bb.max.x() += EXTRA_AREA + extraPadding.right; return bb; } void GUIManager::update_invalidated_draw_area_from_layout() { if(setToUpdateInvalidateDrawAreaFromLayout) { auto renderCommandMapCopy = oldRenderCommandMap; for(int32_t i = 0; i < renderCommands.length; i++) { const Clay_RenderCommand& renderCommand = renderCommands.internalArray[i]; auto oldCommandIt = oldRenderCommandMap.find(renderCommand.id); if(oldCommandIt != oldRenderCommandMap.end()) { Clay_RenderCommand& oldRenderCommand = oldCommandIt->second; if(std::memcmp(&renderCommand, &oldRenderCommand, sizeof(Clay_RenderCommand)) != 0) { invalidate_draw_in_area(get_invalid_draw_bb_from_command(&oldRenderCommand)); invalidate_draw_in_area(get_invalid_draw_bb_from_command(&renderCommand)); oldRenderCommand = renderCommand; } } else { invalidate_draw_in_area(get_invalid_draw_bb_from_command(&renderCommand)); oldRenderCommandMap.emplace(renderCommand.id, renderCommand); } renderCommandMapCopy.erase(renderCommand.id); } for(auto& [id, command] : renderCommandMapCopy) { invalidate_draw_in_area(get_invalid_draw_bb_from_command(&command)); oldRenderCommandMap.erase(id); } setToUpdateInvalidateDrawAreaFromLayout = false; } } void GUIManager::update() { for(ElementContainer* e : orderedElements) e->elem->update(); for(auto& [id, animation] : animations) animation.update(*this); } GUIFloatAnimation* GUIManager::float_animation(const char* animationID, const GUIFloatAnimationData& animation) { GUIFloatAnimation* toRet = nullptr; new_id(animationID, [&] { auto [it, placed] = animations.emplace(idStack, animation); GUIFloatAnimation& actualInternalAnim = it->second; actualInternalAnim.isUsedThisFrame = true; toRet = &actualInternalAnim; }); return toRet; } void GUIManager::update_window(const Vector2f& windowSize, const SCollision::AABB& safeWindowRect, float guiScaleMultiplier) { if(io.windowSize != windowSize || io.safeWindowRect != safeWindowRect || io.guiScaleMultiplier != guiScaleMultiplier) { io.redrawSurface = true; set_to_layout(); } io.windowSize = windowSize / guiScaleMultiplier; io.safeWindowRect = safeWindowRect; io.safeWindowRect.min /= guiScaleMultiplier; io.safeWindowRect.max /= guiScaleMultiplier; io.guiScaleMultiplier = guiScaleMultiplier; } void GUIManager::set_to_layout() { setToLayout = true; } void GUIManager::layout_if_necessary() { if(setToLayout) { layout(); setToLayout = false; setToUpdateInvalidateDrawAreaFromLayout = true; } } void GUIManager::layout() { constexpr int LAYOUT_RUN_COUNT = 8; for(int i = 0; i < LAYOUT_RUN_COUNT; i++) single_layout_run(); } void GUIManager::layout_begin() { for(auto& [id, e] : elements) e.isUsedThisFrame = false; for(auto& [id, a] : animations) a.isUsedThisFrame = false; #ifdef CLAY_DEBUG_MENU_ENABLED Clay_SetPointerState({io.input->mouse.pos.x(), io.input->mouse.pos.y()}, io.input->mouse.leftDown); #endif Clay_UpdateScrollContainers(false, {0, 0}, 0.1f); // Important regardless if scroll position is updated with Clay or not Clay_SetLayoutDimensions(Clay_Dimensions(io.windowSize.x(), io.windowSize.y())); Clay_SetCurrentContext(clayInstance); strArena.reset(); orderedElements.clear(); Clay_BeginLayout(); } void GUIManager::layout_end() { renderCommands = Clay_EndLayout(io.deltaTime); if(!idStack.empty()) throw std::runtime_error("[GUIManager::end] ID Stack is not empty on end (push_id and pop_id calls not equal)"); std::erase_if(elements, [](auto& p) { return !p.second.isUsedThisFrame; }); std::erase_if(animations, [](auto& p) { return !p.second.isUsedThisFrame; }); std::stable_sort(orderedElements.begin(), orderedElements.end(), [](ElementContainer* a, ElementContainer* b) { return b->elem->zIndex < a->elem->zIndex; // Order from highest to lowest zIndex }); } void GUIManager::single_layout_run() { layout_begin(); io.layoutRun(); layout_end(); } void GUIManager::clay_error_handler(Clay_ErrorData errorData) { Logger::get().log("INFO", "[Clay Error] " + std::string(errorData.errorText.chars)); } void GUIManager::in_dynamic_area(const std::function& f) { bool oldDynamicArea = dynamicArea; dynamicArea = true; f(); dynamicArea = oldDynamicArea; } bool GUIManager::is_dynamic_area() { return dynamicArea; } void GUIManager::new_id(const char* id, const std::function& f) { push_id(id); f(); pop_id(); } void GUIManager::new_id(int64_t id, const std::function& f) { push_id(id); f(); pop_id(); } void GUIManager::set_z_index(int16_t z, const std::function& f) { auto oldClippingRegion = clippingRegion; clippingRegion = std::nullopt; set_z_index_keep_clipping_region(z, f); clippingRegion = oldClippingRegion; } void GUIManager::set_z_index_keep_clipping_region(int16_t z, const std::function& f) { int16_t oldZIndex = zIndex; zIndex = z; f(); zIndex = oldZIndex; } int16_t GUIManager::get_z_index() { return zIndex; } void GUIManager::push_id(int64_t id) { idStack.emplace_back(id); } void GUIManager::push_id(const char* id) { idStack.emplace_back(id); } void GUIManager::pop_id() { idStack.pop_back(); } void GUIManager::set_post_callback_func(const std::function& f) { if(!postCallbackFuncIsHighPriority) postCallbackFunc = f; } void GUIManager::set_post_callback_func_high_priority(const std::function& f) { postCallbackFuncIsHighPriority = true; postCallbackFunc = f; } void GUIManager::run_post_callback_func() { if(postCallbackFunc) { postCallbackFunc(); postCallbackFunc = nullptr; } postCallbackFuncIsHighPriority = false; } void GUIManager::invalidate_draw_element(Element* element, const BorderData& extraPadding) { if(element->get_bb().has_value()) invalidate_draw_in_area(get_invalid_draw_bb(element->get_bb().value(), extraPadding)); } void GUIManager::invalidate_draw_in_area(const SCollision::AABB& bb) { if(invalidDrawBB.has_value()) invalidDrawBB.value().include_aabb_in_bounds(bb); else invalidDrawBB = bb; } bool GUIManager::last_interaction_is_touch() { return lastInteractionIsTouch; } bool GUIManager::cursor_obstructed() const { return cursorObstructed; } void GUIManager::deselect_all() { for(ElementContainer* e : orderedElements) e->elem->deselect(); } void GUIManager::input_paste_callback(const CustomEvents::PasteEvent& paste) { for(ElementContainer* e : orderedElements) e->elem->input_paste_callback(paste); } void GUIManager::input_android_text_box_input_callback(const CustomEvents::AndroidTextBoxInputEvent& textboxInput) { for(ElementContainer* e : orderedElements) e->elem->input_android_text_box_input_callback(textboxInput); } void GUIManager::input_text_key_callback(const InputManager::KeyCallbackArgs& key) { for(ElementContainer* e : orderedElements) e->elem->input_text_key_callback(key); } void GUIManager::input_text_callback(const InputManager::TextCallbackArgs& text) { for(ElementContainer* e : orderedElements) e->elem->input_text_callback(text); } void GUIManager::input_key_callback(const InputManager::KeyCallbackArgs& key) { for(ElementContainer* e : orderedElements) e->elem->input_key_callback(key); } void GUIManager::input_mouse_button_callback(InputManager::MouseButtonCallbackArgs button) { if(button.deviceType != InputManager::MouseDeviceType::TOUCH) { lastInteractionIsTouch = false; button.pos /= io.guiScaleMultiplier; mouse_callback(button.pos, [&button] (ElementContainer* e) { e->elem->input_mouse_button_callback(button); }); } } void GUIManager::input_mouse_motion_callback(InputManager::MouseMotionCallbackArgs motion) { if(motion.deviceType != InputManager::MouseDeviceType::TOUCH) { lastInteractionIsTouch = false; motion.pos /= io.guiScaleMultiplier; motion.move /= io.guiScaleMultiplier; mouse_callback(motion.pos, [&motion] (ElementContainer* e) { e->elem->input_mouse_motion_callback(motion); }); } } void GUIManager::input_mouse_wheel_callback(InputManager::MouseWheelCallbackArgs wheel) { wheel.mousePos /= io.guiScaleMultiplier; mouse_callback(wheel.mousePos, [&wheel] (ElementContainer* e) { e->elem->input_mouse_wheel_callback(wheel); }); } void GUIManager::input_finger_touch_callback(InputManager::FingerTouchCallbackArgs touch) { lastInteractionIsTouch = true; touch.pos /= io.guiScaleMultiplier; mouse_callback(touch.pos, [&touch] (ElementContainer* e) { e->elem->input_finger_touch_callback(touch); }); } void GUIManager::input_finger_motion_callback(InputManager::FingerMotionCallbackArgs motion) { lastInteractionIsTouch = true; motion.pos /= io.guiScaleMultiplier; motion.move /= io.guiScaleMultiplier; mouse_callback(motion.pos, [&motion] (ElementContainer* e) { e->elem->input_finger_motion_callback(motion); }); } std::optional GUIManager::get_text_box_start_info() { for(ElementContainer* e : orderedElements) { auto toRet = e->elem->get_text_box_start_info(); if(toRet) return toRet; } return std::nullopt; } void GUIManager::mouse_callback(const Vector2f& mousePos, const std::function& f) { cursorObstructed = false; int16_t zIndexObstructed = 0; for(ElementContainer* e : orderedElements) e->elem->childMouseHovering = false; for(ElementContainer* e : orderedElements) { if((!cursorObstructed || zIndexObstructed == e->elem->zIndex) && e->elem->collides_with_point(mousePos)) { zIndexObstructed = e->elem->zIndex; cursorObstructed = true; e->elem->mouseHovering = true; Element* nextParent = e->elem->parent; while(nextParent) { nextParent->childMouseHovering |= true; nextParent = nextParent->parent; } } else e->elem->mouseHovering = false; } for(ElementContainer* e : orderedElements) f(e); } }