From 0853df4d2abb2f84d9e7cea431f46c4c79ddda7d Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Sun, 19 Apr 2026 10:03:57 +0300 Subject: [PATCH] Added some scroll area and grid fixes --- src/GUIHolder.cpp | 2 +- .../ElementHelpers/ScrollAreaHelpers.cpp | 17 +- src/GUIStuff/Elements/GridScrollArea.cpp | 47 +++-- src/GUIStuff/Elements/GridScrollArea.hpp | 1 + src/GUIStuff/GUIManager.cpp | 168 ++++++++++++++++++ src/GUIStuff/GUIManager.hpp | 1 + src/Screens/FileSelectScreen.cpp | 11 +- 7 files changed, 224 insertions(+), 23 deletions(-) diff --git a/src/GUIHolder.cpp b/src/GUIHolder.cpp index d840ba9..29df2bd 100644 --- a/src/GUIHolder.cpp +++ b/src/GUIHolder.cpp @@ -125,7 +125,7 @@ void GUIHolder::draw(SkCanvas* canvas, bool skiaAA) { gui.io.redrawSurface = true; } - gui.draw(canvas, skiaAA); + gui.draw_force(canvas, skiaAA); } void GUIHolder::input_paste_callback(const CustomEvents::PasteEvent& paste) { diff --git a/src/GUIStuff/ElementHelpers/ScrollAreaHelpers.cpp b/src/GUIStuff/ElementHelpers/ScrollAreaHelpers.cpp index 5b5b005..81b5087 100644 --- a/src/GUIStuff/ElementHelpers/ScrollAreaHelpers.cpp +++ b/src/GUIStuff/ElementHelpers/ScrollAreaHelpers.cpp @@ -1,5 +1,6 @@ #include "ScrollAreaHelpers.hpp" #include "../GUIManager.hpp" +#include "../Elements/LayoutElement.hpp" namespace GUIStuff { namespace ElementHelpers { @@ -15,6 +16,7 @@ ScrollArea* scroll_area_many_entries(GUIManager& gui, const char* id, const Scro float absScrollAmount = std::fabs(params.scrollOffset->y()); size_t startPoint = absScrollAmount / options.entryHeight; size_t elementsContainable = (params.containerDimensions.y() / options.entryHeight) + 2; // Displaying 2 more elements ensures that there isn't any empty space in the container + elementsContainable = std::min(elementsContainable, 100); size_t endPoint = std::min(options.entryCount, startPoint + elementsContainable); if(startPoint != 0) { @@ -25,8 +27,19 @@ ScrollArea* scroll_area_many_entries(GUIManager& gui, const char* id, const Scro }) { } } - for(size_t i = startPoint; i < endPoint; i++) - gui.new_id(i, [&] { options.elementContent(i); }); + gui.new_id("many element list", [&] { + for(size_t i = startPoint; i < endPoint; i++) { + gui.new_id(i, [&] { + gui.element("elem", [&] (LayoutElement*, const Clay_ElementId& lId) { + CLAY(lId, { + .layout = { .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(options.entryHeight)}} + }) { + options.elementContent(i); + } + }); + }); + } + }); if(endPoint != options.entryCount) { CLAY_AUTO_ID({ diff --git a/src/GUIStuff/Elements/GridScrollArea.cpp b/src/GUIStuff/Elements/GridScrollArea.cpp index 4b7b8c8..e9f24e6 100644 --- a/src/GUIStuff/Elements/GridScrollArea.cpp +++ b/src/GUIStuff/Elements/GridScrollArea.cpp @@ -7,9 +7,11 @@ namespace GUIStuff { GridScrollArea::GridScrollArea(GUIManager& gui): Element(gui) {} -void GridScrollArea::layout(const Clay_ElementId& id, const Options& options) { +void GridScrollArea::layout(const Clay_ElementId& id, const Options& o) { using namespace ElementHelpers; + options = o; + ScrollBarManyEntriesOptions opts; opts.clipHorizontal = true; opts.entryHeight = options.entryHeight; @@ -18,29 +20,36 @@ void GridScrollArea::layout(const Clay_ElementId& id, const Options& options) { if(extraCallback) extraCallback(contParams); }; size_t entriesPerRow = static_cast(rowWidth / options.entryMaximumWidth) + 1; - size_t rowCount = options.entryCount / entriesPerRow; + size_t rowCount = (options.entryCount + entriesPerRow - 1) / entriesPerRow; float entryWidth = rowWidth == 0.0f ? 100.0f : (rowWidth / entriesPerRow); opts.entryCount = rowCount; opts.elementContent = [&] (size_t rowIndex) { - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(options.entryHeight)}, - .childAlignment = { .x = options.childAlignmentX} + gui.new_id("grid row", [&] { + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(options.entryHeight)}, + .childAlignment = { .x = options.childAlignmentX} + } + }) { + size_t startIndex = rowIndex * entriesPerRow; + size_t endIndex = std::min(rowIndex * entriesPerRow + entriesPerRow, o.entryCount); + for(size_t i = startIndex; i < endIndex; i++) { + gui.new_id(static_cast(i), [&] { + CLAY_AUTO_ID({ + .layout = {.sizing = {.width = CLAY_SIZING_FIXED(entryWidth), .height = CLAY_SIZING_FIXED(options.entryHeight)}} + }) { + options.elementContent(i); + } + }); + } } - }) { - for(size_t i = 0; i < entriesPerRow; i++) { - size_t entryNum = rowIndex * entriesPerRow + i; - gui.new_id(static_cast(i), [&] { - CLAY_AUTO_ID({ - .layout = {.sizing = {.width = CLAY_SIZING_FIXED(entryWidth), .height = CLAY_SIZING_FIXED(options.entryHeight)}} - }) { - options.elementContent(entryNum); - } - }); - } - } + }); }; - scroll_area_many_entries(gui, "scroll many area", opts); + CLAY(id, { + .layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}} + }) { + scroll_area_many_entries(gui, "scroll many area", opts); + } } } diff --git a/src/GUIStuff/Elements/GridScrollArea.hpp b/src/GUIStuff/Elements/GridScrollArea.hpp index 5cab388..2d9a9f4 100644 --- a/src/GUIStuff/Elements/GridScrollArea.hpp +++ b/src/GUIStuff/Elements/GridScrollArea.hpp @@ -20,6 +20,7 @@ class GridScrollArea : public Element { void layout(const Clay_ElementId& id, const Options& options); private: + Options options; float rowWidth = 0.0f; }; diff --git a/src/GUIStuff/GUIManager.cpp b/src/GUIStuff/GUIManager.cpp index d6847a2..576976e 100644 --- a/src/GUIStuff/GUIManager.cpp +++ b/src/GUIStuff/GUIManager.cpp @@ -294,6 +294,174 @@ void GUIManager::draw(SkCanvas* c, bool skiaAA) { 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); + canvas->translate(io.windowPos.x(), io.windowPos.y()); + + 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_vec4(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_vec4(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_vec4(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) { diff --git a/src/GUIStuff/GUIManager.hpp b/src/GUIStuff/GUIManager.hpp index d4d93ba..88c5af5 100644 --- a/src/GUIStuff/GUIManager.hpp +++ b/src/GUIStuff/GUIManager.hpp @@ -18,6 +18,7 @@ class GUIManager { GUIManager(); void draw(SkCanvas* c, bool skiaAA); + void draw_force(SkCanvas* canvas, bool skiaAA); void set_to_layout(); void layout_if_necessary(); diff --git a/src/Screens/FileSelectScreen.cpp b/src/Screens/FileSelectScreen.cpp index bda7f02..757dcea 100644 --- a/src/Screens/FileSelectScreen.cpp +++ b/src/Screens/FileSelectScreen.cpp @@ -5,6 +5,7 @@ #include "Helpers/ConvertVec.hpp" #include "../GUIStuff/Elements/GridScrollArea.hpp" #include "../GUIStuff/ElementHelpers/ButtonHelpers.hpp" +#include "../GUIStuff/ElementHelpers/ScrollAreaHelpers.hpp" FileSelectScreen::FileSelectScreen(MainProgram& m): Screen(m) {} @@ -22,11 +23,19 @@ void FileSelectScreen::gui_layout_run() { }, }) { text_label(gui, "Test grid"); + //scroll_area_many_entries(gui, "Test scroll", ScrollBarManyEntriesOptions{ + // .entryHeight = 100.0f, + // .entryCount = 2000, + // .clipHorizontal = true, + // .elementContent = [&] (size_t i) { + // text_button_sized(gui, "button", "Square " + std::to_string(i), CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)); + // } + //}); gui.element("Test grid", GridScrollArea::Options{ .entryMaximumWidth = 100.0f, .childAlignmentX = CLAY_ALIGN_X_LEFT, .entryHeight = 100.0f, - .entryCount = 700, + .entryCount = 200, .elementContent = [&](size_t i) { CLAY_AUTO_ID({ .layout = {