diff --git a/Include/RmlUi/Core/ComputedValues.h b/Include/RmlUi/Core/ComputedValues.h index 2dd8b35b3..e9c8934cb 100644 --- a/Include/RmlUi/Core/ComputedValues.h +++ b/Include/RmlUi/Core/ComputedValues.h @@ -30,6 +30,7 @@ #include "Animation.h" #include "Element.h" +#include "RenderBox.h" #include "StyleTypes.h" #include "Types.h" #include @@ -213,7 +214,7 @@ namespace Style { explicit ComputedValues(Element* element) : element(element) {} // clang-format off - + // -- Common -- LengthPercentageAuto width() const { return LengthPercentageAuto(common.width_type, common.width_value); } LengthPercentageAuto height() const { return LengthPercentageAuto(common.height_type, common.height_value); } @@ -248,7 +249,7 @@ namespace Style { Colourb border_bottom_color() const { return common.border_bottom_color; } Colourb border_left_color() const { return common.border_left_color; } bool has_decorator() const { return common.has_decorator; } - + // -- Inherited -- String font_family() const; String cursor() const; @@ -301,7 +302,7 @@ namespace Style { float border_top_right_radius() const { return (float)rare.border_top_right_radius; } float border_bottom_right_radius() const { return (float)rare.border_bottom_right_radius; } float border_bottom_left_radius() const { return (float)rare.border_bottom_left_radius; } - Vector4f border_radius() const { return {(float)rare.border_top_left_radius, (float)rare.border_top_right_radius, + CornerSizes border_radius() const { return {(float)rare.border_top_left_radius, (float)rare.border_top_right_radius, (float)rare.border_bottom_right_radius, (float)rare.border_bottom_left_radius}; } Clip clip() const { return rare.clip; } Drag drag() const { return rare.drag; } @@ -315,7 +316,7 @@ namespace Style { bool has_filter() const { return rare.has_filter; } bool has_backdrop_filter() const { return rare.has_backdrop_filter; } bool has_box_shadow() const { return rare.has_box_shadow; } - + // -- Assignment -- // Common void width (LengthPercentageAuto value) { common.width_type = value.type; common.width_value = value.value; } diff --git a/Include/RmlUi/Core/Element.h b/Include/RmlUi/Core/Element.h index e5014368e..5c77a0cbb 100644 --- a/Include/RmlUi/Core/Element.h +++ b/Include/RmlUi/Core/Element.h @@ -35,6 +35,7 @@ #include "Header.h" #include "ObserverPtr.h" #include "Property.h" +#include "RenderBox.h" #include "ScriptInterface.h" #include "ScrollTypes.h" #include "StyleTypes.h" @@ -158,6 +159,11 @@ class RMLUICORE_API Element : public ScriptInterface, public EnableObserverPtr; +// Ordered by top-left, top-right, bottom-right, bottom-left. +using CornerSizes = Array; + +/** + Provides the data needed to generate a mesh for a given element's box. + */ + +class RenderBox { +public: + RenderBox(Vector2f fill_size, Vector2f border_offset, EdgeSizes border_widths, CornerSizes border_radius) : + fill_size(fill_size), border_offset(border_offset), border_widths(border_widths), border_radius(border_radius) + {} + + /// Returns the size of the fill area of the box. + Vector2f GetFillSize() const { return fill_size; } + /// Sets the size of the fill area of the box. + void SetFillSize(Vector2f value) { fill_size = value; } + /// Returns the offset from the border area to the fill area of the box. + Vector2f GetFillOffset() const { return {border_widths[3], border_widths[0]}; } + + /// Returns the offset to the border area of the box. + Vector2f GetBorderOffset() const { return border_offset; } + /// Sets the border offset. + void SetBorderOffset(Vector2f value) { border_offset = value; } + + /// Returns the border widths of the box. + EdgeSizes GetBorderWidths() const { return border_widths; } + /// Sets the border widths of the box. + void SetBorderWidths(EdgeSizes value) { border_widths = value; } + + /// Returns the border radius of the box. + CornerSizes GetBorderRadius() const { return border_radius; } + /// Sets the border radius of the box. + void SetBorderRadius(CornerSizes value) { border_radius = value; } + +private: + Vector2f fill_size; + Vector2f border_offset; + EdgeSizes border_widths; + CornerSizes border_radius; +}; + +} // namespace Rml +#endif diff --git a/Source/Core/CMakeLists.txt b/Source/Core/CMakeLists.txt index b5d0f79b6..09ed63c2e 100644 --- a/Source/Core/CMakeLists.txt +++ b/Source/Core/CMakeLists.txt @@ -301,6 +301,7 @@ target_sources(rmlui_core PRIVATE "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/PropertyParser.h" "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/PropertySpecification.h" "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Rectangle.h" + "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/RenderBox.h" "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/RenderInterface.h" "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/RenderInterfaceCompatibility.h" "${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/RenderManager.h" diff --git a/Source/Core/DecoratorGradient.cpp b/Source/Core/DecoratorGradient.cpp index d47fed2fa..81057f428 100644 --- a/Source/Core/DecoratorGradient.cpp +++ b/Source/Core/DecoratorGradient.cpp @@ -189,19 +189,18 @@ bool DecoratorStraightGradient::Initialise(const Direction in_direction, const C DecoratorDataHandle DecoratorStraightGradient::GenerateElementData(Element* element, BoxArea paint_area) const { - const Box& box = element->GetBox(); - + const RenderBox render_box = element->GetRenderBox(paint_area); const ComputedValues& computed = element->GetComputedValues(); const float opacity = computed.opacity(); Mesh mesh; - MeshUtilities::GenerateBackground(mesh, element->GetBox(), Vector2f(0), computed.border_radius(), ColourbPremultiplied(), paint_area); + MeshUtilities::GenerateBackground(mesh, render_box, ColourbPremultiplied()); ColourbPremultiplied colour_start = start.ToPremultiplied(opacity); ColourbPremultiplied colour_stop = stop.ToPremultiplied(opacity); - const Vector2f offset = box.GetPosition(paint_area); - const Vector2f size = box.GetSize(paint_area); + const Vector2f offset = render_box.GetFillOffset(); + const Vector2f size = render_box.GetFillSize(); Vector& vertices = mesh.vertices; @@ -296,10 +295,8 @@ DecoratorDataHandle DecoratorLinearGradient::GenerateElementData(Element* elemen RMLUI_ASSERT(!color_stops.empty()); - const Box& box = element->GetBox(); - const Vector2f dimensions = box.GetSize(paint_area); - - LinearGradientShape gradient_shape = CalculateShape(dimensions); + const RenderBox render_box = element->GetRenderBox(paint_area); + LinearGradientShape gradient_shape = CalculateShape(render_box.GetFillSize()); // One-pixel minimum color stop spacing to avoid aliasing. const float soft_spacing = 1.f / gradient_shape.length; @@ -320,9 +317,9 @@ DecoratorDataHandle DecoratorLinearGradient::GenerateElementData(Element* elemen Mesh mesh; const ComputedValues& computed = element->GetComputedValues(); const byte alpha = byte(computed.opacity() * 255.f); - MeshUtilities::GenerateBackground(mesh, box, Vector2f(), computed.border_radius(), ColourbPremultiplied(alpha, alpha), paint_area); + MeshUtilities::GenerateBackground(mesh, render_box, ColourbPremultiplied(alpha, alpha)); - const Vector2f render_offset = box.GetPosition(paint_area); + const Vector2f render_offset = render_box.GetFillOffset(); for (Vertex& vertex : mesh.vertices) vertex.tex_coord = vertex.position - render_offset; @@ -458,7 +455,7 @@ bool DecoratorRadialGradient::Initialise(bool in_repeating, Shape in_shape, Size return !color_stops.empty(); } -DecoratorDataHandle DecoratorRadialGradient::GenerateElementData(Element* element, BoxArea box_area) const +DecoratorDataHandle DecoratorRadialGradient::GenerateElementData(Element* element, BoxArea paint_area) const { RenderManager* render_manager = element->GetRenderManager(); if (!render_manager) @@ -466,8 +463,8 @@ DecoratorDataHandle DecoratorRadialGradient::GenerateElementData(Element* elemen RMLUI_ASSERT(!color_stops.empty() && (shape == Shape::Circle || shape == Shape::Ellipse)); - const Box& box = element->GetBox(); - const Vector2f dimensions = box.GetSize(box_area); + const RenderBox render_box = element->GetRenderBox(paint_area); + const Vector2f dimensions = render_box.GetFillSize(); RadialGradientShape gradient_shape = CalculateRadialGradientShape(element, dimensions); @@ -489,9 +486,9 @@ DecoratorDataHandle DecoratorRadialGradient::GenerateElementData(Element* elemen Mesh mesh; const ComputedValues& computed = element->GetComputedValues(); const byte alpha = byte(computed.opacity() * 255.f); - MeshUtilities::GenerateBackground(mesh, box, Vector2f(), computed.border_radius(), ColourbPremultiplied(alpha, alpha), box_area); + MeshUtilities::GenerateBackground(mesh, render_box, ColourbPremultiplied(alpha, alpha)); - const Vector2f render_offset = box.GetPosition(box_area); + const Vector2f render_offset = render_box.GetFillOffset(); for (Vertex& vertex : mesh.vertices) vertex.tex_coord = vertex.position - render_offset; @@ -657,7 +654,7 @@ bool DecoratorConicGradient::Initialise(bool in_repeating, float in_angle, Vecto return !color_stops.empty(); } -DecoratorDataHandle DecoratorConicGradient::GenerateElementData(Element* element, BoxArea box_area) const +DecoratorDataHandle DecoratorConicGradient::GenerateElementData(Element* element, BoxArea paint_area) const { RenderManager* render_manager = element->GetRenderManager(); if (!render_manager) @@ -665,8 +662,8 @@ DecoratorDataHandle DecoratorConicGradient::GenerateElementData(Element* element RMLUI_ASSERT(!color_stops.empty()); - const Box& box = element->GetBox(); - const Vector2f dimensions = box.GetSize(box_area); + const RenderBox render_box = element->GetRenderBox(paint_area); + const Vector2f dimensions = render_box.GetFillSize(); const Vector2f center = Vector2f{element->ResolveNumericValue(position.x, dimensions.x), element->ResolveNumericValue(position.y, dimensions.y)}.Round(); @@ -686,9 +683,9 @@ DecoratorDataHandle DecoratorConicGradient::GenerateElementData(Element* element Mesh mesh; const ComputedValues& computed = element->GetComputedValues(); const byte alpha = byte(computed.opacity() * 255.f); - MeshUtilities::GenerateBackground(mesh, box, Vector2f(), computed.border_radius(), ColourbPremultiplied(alpha, alpha), box_area); + MeshUtilities::GenerateBackground(mesh, render_box, ColourbPremultiplied(alpha, alpha)); - const Vector2f render_offset = box.GetPosition(box_area); + const Vector2f render_offset = render_box.GetFillOffset(); for (Vertex& vertex : mesh.vertices) vertex.tex_coord = vertex.position - render_offset; diff --git a/Source/Core/DecoratorNinePatch.cpp b/Source/Core/DecoratorNinePatch.cpp index e6abfe650..39102ce9a 100644 --- a/Source/Core/DecoratorNinePatch.cpp +++ b/Source/Core/DecoratorNinePatch.cpp @@ -61,8 +61,9 @@ DecoratorDataHandle DecoratorNinePatch::GenerateElementData(Element* element, Bo Texture texture = GetTexture(); const Vector2f texture_dimensions(texture.GetDimensions()); - const Vector2f surface_offset = element->GetBox().GetPosition(paint_area); - const Vector2f surface_dimensions = element->GetBox().GetSize(paint_area).Round(); + const RenderBox render_box = element->GetRenderBox(paint_area); + const Vector2f surface_offset = render_box.GetFillOffset(); + const Vector2f surface_dimensions = render_box.GetFillSize(); const ColourbPremultiplied quad_colour = computed.image_color().ToPremultiplied(computed.opacity()); diff --git a/Source/Core/DecoratorShader.cpp b/Source/Core/DecoratorShader.cpp index 997068362..fd9cc3090 100644 --- a/Source/Core/DecoratorShader.cpp +++ b/Source/Core/DecoratorShader.cpp @@ -52,14 +52,14 @@ bool DecoratorShader::Initialise(String&& in_value) return true; } -DecoratorDataHandle DecoratorShader::GenerateElementData(Element* element, BoxArea render_area) const +DecoratorDataHandle DecoratorShader::GenerateElementData(Element* element, BoxArea paint_area) const { RenderManager* render_manager = element->GetRenderManager(); if (!render_manager) return INVALID_DECORATORDATAHANDLE; - const Box& box = element->GetBox(); - const Vector2f dimensions = box.GetSize(render_area); + const RenderBox render_box = element->GetRenderBox(paint_area); + const Vector2f dimensions = render_box.GetFillSize(); CompiledShader shader = render_manager->CompileShader("shader", Dictionary{{"value", Variant(value)}, {"dimensions", Variant(dimensions)}}); if (!shader) return INVALID_DECORATORDATAHANDLE; @@ -68,9 +68,9 @@ DecoratorDataHandle DecoratorShader::GenerateElementData(Element* element, BoxAr const ComputedValues& computed = element->GetComputedValues(); const byte alpha = byte(computed.opacity() * 255.f); - MeshUtilities::GenerateBackground(mesh, box, Vector2f(), computed.border_radius(), ColourbPremultiplied(alpha, alpha), render_area); + MeshUtilities::GenerateBackground(mesh, render_box, ColourbPremultiplied(alpha, alpha)); - const Vector2f offset = box.GetPosition(render_area); + const Vector2f offset = render_box.GetFillOffset(); for (Vertex& vertex : mesh.vertices) vertex.tex_coord = (vertex.position - offset) / dimensions; diff --git a/Source/Core/DecoratorTiledBox.cpp b/Source/Core/DecoratorTiledBox.cpp index 00570c2e8..0d52c805d 100644 --- a/Source/Core/DecoratorTiledBox.cpp +++ b/Source/Core/DecoratorTiledBox.cpp @@ -105,8 +105,9 @@ DecoratorDataHandle DecoratorTiledBox::GenerateElementData(Element* element, Box tiles[i].CalculateDimensions(GetTexture(tiles[i].texture_index)); } - const Vector2f offset = element->GetBox().GetPosition(paint_area); - const Vector2f size = element->GetBox().GetSize(paint_area); + const RenderBox render_box = element->GetRenderBox(paint_area); + const Vector2f offset = render_box.GetFillOffset(); + const Vector2f size = render_box.GetFillSize(); // Calculate the natural dimensions of tile corners and edges. const Vector2f natural_top_left = tiles[TOP_LEFT_CORNER].GetNaturalDimensions(element); diff --git a/Source/Core/DecoratorTiledHorizontal.cpp b/Source/Core/DecoratorTiledHorizontal.cpp index 3d4a385e8..9b3f607f3 100644 --- a/Source/Core/DecoratorTiledHorizontal.cpp +++ b/Source/Core/DecoratorTiledHorizontal.cpp @@ -82,8 +82,9 @@ DecoratorDataHandle DecoratorTiledHorizontal::GenerateElementData(Element* eleme for (int i = 0; i < 3; i++) tiles[i].CalculateDimensions(GetTexture(tiles[i].texture_index)); - const Vector2f offset = element->GetBox().GetPosition(paint_area); - const Vector2f size = element->GetBox().GetSize(paint_area); + const RenderBox render_box = element->GetRenderBox(paint_area); + const Vector2f offset = render_box.GetFillOffset(); + const Vector2f size = render_box.GetFillSize(); Vector2f left_dimensions = tiles[LEFT].GetNaturalDimensions(element); Vector2f right_dimensions = tiles[RIGHT].GetNaturalDimensions(element); diff --git a/Source/Core/DecoratorTiledImage.cpp b/Source/Core/DecoratorTiledImage.cpp index a81968959..8efa27806 100644 --- a/Source/Core/DecoratorTiledImage.cpp +++ b/Source/Core/DecoratorTiledImage.cpp @@ -52,8 +52,9 @@ DecoratorDataHandle DecoratorTiledImage::GenerateElementData(Element* element, B const ComputedValues& computed = element->GetComputedValues(); - const Vector2f offset = element->GetBox().GetPosition(paint_area); - const Vector2f size = element->GetBox().GetSize(paint_area); + const RenderBox render_box = element->GetRenderBox(paint_area); + const Vector2f offset = render_box.GetFillOffset(); + const Vector2f size = render_box.GetFillSize(); // Generate the geometry for the tile. Mesh mesh; diff --git a/Source/Core/DecoratorTiledVertical.cpp b/Source/Core/DecoratorTiledVertical.cpp index c55a1a363..668623d56 100644 --- a/Source/Core/DecoratorTiledVertical.cpp +++ b/Source/Core/DecoratorTiledVertical.cpp @@ -83,8 +83,9 @@ DecoratorDataHandle DecoratorTiledVertical::GenerateElementData(Element* element for (int i = 0; i < 3; i++) tiles[i].CalculateDimensions(GetTexture(tiles[i].texture_index)); - const Vector2f offset = element->GetBox().GetPosition(paint_area); - const Vector2f size = element->GetBox().GetSize(paint_area); + const RenderBox render_box = element->GetRenderBox(paint_area); + const Vector2f offset = render_box.GetFillOffset(); + const Vector2f size = render_box.GetFillSize(); Vector2f top_dimensions = tiles[TOP].GetNaturalDimensions(element); Vector2f bottom_dimensions = tiles[BOTTOM].GetNaturalDimensions(element); diff --git a/Source/Core/Element.cpp b/Source/Core/Element.cpp index a77176cd6..56589a1f1 100644 --- a/Source/Core/Element.cpp +++ b/Source/Core/Element.cpp @@ -93,9 +93,9 @@ static float GetScrollOffsetDelta(ScrollAlignment alignment, float begin_offset, Element::Element(const String& tag) : local_stacking_context(false), local_stacking_context_forced(false), stacking_context_dirty(false), computed_values_are_default_initialized(true), - visible(true), offset_fixed(false), absolute_offset_dirty(true), dirty_definition(false), dirty_child_definitions(false), dirty_animation(false), - dirty_transition(false), dirty_transform(false), dirty_perspective(false), tag(tag), relative_offset_base(0, 0), relative_offset_position(0, 0), - absolute_offset(0, 0), scroll_offset(0, 0) + visible(true), offset_fixed(false), absolute_offset_dirty(true), rounded_main_padding_size_dirty(true), dirty_definition(false), + dirty_child_definitions(false), dirty_animation(false), dirty_transition(false), dirty_transform(false), dirty_perspective(false), tag(tag), + relative_offset_base(0, 0), relative_offset_position(0, 0), absolute_offset(0, 0), scroll_offset(0, 0) { RMLUI_ASSERT(tag == StringUtilities::ToLower(tag)); parent = nullptr; @@ -206,10 +206,7 @@ void Element::Render() RMLUI_ZoneText(name.c_str(), name.size()); #endif - // TODO: This is a work-around for the dirty offset not being properly updated when used by containing block children. This results - // in scrolling not working properly. We don't care about the return value, the call is only used to force the absolute offset to update. - if (absolute_offset_dirty) - GetAbsoluteOffset(BoxArea::Border); + UpdateAbsoluteOffsetAndRenderBoxData(); // Rebuild our stacking context if necessary. if (stacking_context_dirty) @@ -382,28 +379,51 @@ Vector2f Element::GetRelativeOffset(BoxArea area) Vector2f Element::GetAbsoluteOffset(BoxArea area) { - if (absolute_offset_dirty) + UpdateAbsoluteOffsetAndRenderBoxData(); + return area == BoxArea::Border ? absolute_offset : absolute_offset + GetBox().GetPosition(area); +} + +void Element::UpdateAbsoluteOffsetAndRenderBoxData() +{ + if (absolute_offset_dirty || rounded_main_padding_size_dirty) { absolute_offset_dirty = false; + rounded_main_padding_size_dirty = false; + Vector2f offset_from_ancestors; if (offset_parent) - absolute_offset = offset_parent->GetAbsoluteOffset(BoxArea::Border) + relative_offset_base + relative_offset_position; - else - absolute_offset = relative_offset_base + relative_offset_position; + offset_from_ancestors = offset_parent->GetAbsoluteOffset(BoxArea::Border); if (!offset_fixed) { // Add any parent scrolling onto our position as well. if (offset_parent) - absolute_offset -= offset_parent->scroll_offset; + offset_from_ancestors -= offset_parent->scroll_offset; // Finally, there may be relatively positioned elements between ourself and our containing block, add their relative offsets as well. for (Element* ancestor = parent; ancestor && ancestor != offset_parent; ancestor = ancestor->parent) - absolute_offset += ancestor->relative_offset_position; + offset_from_ancestors += ancestor->relative_offset_position; } - } - return absolute_offset + GetBox().GetPosition(area); + const Vector2f relative_offset = relative_offset_base + relative_offset_position; + absolute_offset = relative_offset + offset_from_ancestors; + + // Next, we find the rounded size of the box so that elements can be placed border-to-border next to each other + // without any gaps. To achieve this, we have to adjust their rounded/rendered sizes based on their position, in + // such a way that the bottom-right of this element exactly matches the top-left of the next element. The order + // of floating-point operations matter here, we want to replicate the operations in the layout engine as close + // as possible to avoid any gaps. + const Vector2f main_padding_size = main_box.GetSize(BoxArea::Padding); + const Vector2f bottom_right_absolute_offset = (relative_offset + main_padding_size) + offset_from_ancestors; + const Vector2f new_rounded_main_padding_size = bottom_right_absolute_offset.Round() - absolute_offset.Round(); + if (new_rounded_main_padding_size != rounded_main_padding_size) + { + rounded_main_padding_size = new_rounded_main_padding_size; + meta->background_border.DirtyBackground(); + meta->background_border.DirtyBorder(); + meta->effects.DirtyEffectsData(); + } + } } void Element::SetClipArea(BoxArea _clip_area) @@ -430,11 +450,20 @@ void Element::SetBox(const Box& box) { if (box != main_box || additional_boxes.size() > 0) { +#ifdef RMLUI_DEBUG + for (const BoxEdge edge : {BoxEdge::Top, BoxEdge::Right, BoxEdge::Bottom, BoxEdge::Left}) + { + const float border_width = box.GetEdge(BoxArea::Border, edge); + if (border_width != Math::Round(border_width)) + Log::Message(Log::LT_WARNING, "Expected integer border width but got %g px on element: %s", border_width, GetAddress().c_str()); + } +#endif + main_box = box; additional_boxes.clear(); OnResize(); - + rounded_main_padding_size_dirty = true; meta->background_border.DirtyBackground(); meta->background_border.DirtyBorder(); meta->effects.DirtyEffectsData(); @@ -444,9 +473,7 @@ void Element::SetBox(const Box& box) void Element::AddBox(const Box& box, Vector2f offset) { additional_boxes.emplace_back(PositionedBox{box, offset}); - OnResize(); - meta->background_border.DirtyBackground(); meta->background_border.DirtyBorder(); meta->effects.DirtyEffectsData(); @@ -461,18 +488,57 @@ const Box& Element::GetBox(int index, Vector2f& offset) { offset = Vector2f(0); - if (index < 1) - return main_box; - const int additional_box_index = index - 1; - if (additional_box_index >= (int)additional_boxes.size()) + if (index < 1 || additional_box_index >= (int)additional_boxes.size()) return main_box; offset = additional_boxes[additional_box_index].offset; - return additional_boxes[additional_box_index].box; } +RenderBox Element::GetRenderBox(BoxArea fill_area, int index) +{ + RMLUI_ASSERTMSG(fill_area >= BoxArea::Border && fill_area <= BoxArea::Content, + "Render box can only be generated with fill area of border, padding or content."); + + UpdateAbsoluteOffsetAndRenderBoxData(); + + struct BoxReference { + const Box& box; + Vector2f padding_size; + Vector2f offset; + }; + auto GetBoxAndOffset = [this, index]() { + const int additional_box_index = index - 1; + if (index < 1 || additional_box_index >= (int)additional_boxes.size()) + return BoxReference{main_box, rounded_main_padding_size, {}}; + const PositionedBox& positioned_box = additional_boxes[additional_box_index]; + return BoxReference{positioned_box.box, positioned_box.box.GetSize(BoxArea::Padding), positioned_box.offset.Round()}; + }; + + BoxReference box = GetBoxAndOffset(); + + EdgeSizes edge_sizes = {}; + for (int area = (int)BoxArea::Border; area < (int)fill_area; area++) + { + edge_sizes[0] += box.box.GetEdge(BoxArea(area), BoxEdge::Top); + edge_sizes[1] += box.box.GetEdge(BoxArea(area), BoxEdge::Right); + edge_sizes[2] += box.box.GetEdge(BoxArea(area), BoxEdge::Bottom); + edge_sizes[3] += box.box.GetEdge(BoxArea(area), BoxEdge::Left); + } + Vector2f inner_size; + switch (fill_area) + { + case BoxArea::Border: inner_size = box.padding_size + box.box.GetFrameSize(BoxArea::Border); break; + case BoxArea::Padding: inner_size = box.padding_size; break; + case BoxArea::Content: inner_size = box.padding_size - box.box.GetFrameSize(BoxArea::Padding); break; + case BoxArea::Margin: + case BoxArea::Auto: RMLUI_ERROR; + } + + return RenderBox{inner_size, box.offset, edge_sizes, meta->computed_values.border_radius()}; +} + int Element::GetNumBoxes() { return 1 + (int)additional_boxes.size(); diff --git a/Source/Core/ElementBackgroundBorder.cpp b/Source/Core/ElementBackgroundBorder.cpp index e2c1beb95..621ec626d 100644 --- a/Source/Core/ElementBackgroundBorder.cpp +++ b/Source/Core/ElementBackgroundBorder.cpp @@ -60,7 +60,10 @@ void ElementBackgroundBorder::Render(Element* element) if (shadow && shadow->geometry) shadow->geometry.Render(element->GetAbsoluteOffset(BoxArea::Border), shadow->texture); else if (Background* background = GetBackground(BackgroundType::BackgroundBorder)) - background->geometry.Render(element->GetAbsoluteOffset(BoxArea::Border)); + { + auto offset = element->GetAbsoluteOffset(BoxArea::Border); + background->geometry.Render(offset); + } } void ElementBackgroundBorder::DirtyBackground() @@ -84,15 +87,13 @@ Geometry* ElementBackgroundBorder::GetClipGeometry(Element* element, BoxArea cli default: RMLUI_ERROR; return nullptr; } + RenderManager* render_manager = element->GetRenderManager(); Geometry& geometry = GetOrCreateBackground(type).geometry; - if (!geometry) + if (render_manager && !geometry) { Mesh mesh = geometry.Release(Geometry::ReleaseMode::ClearMesh); - const Box& box = element->GetBox(); - const Vector4f border_radius = element->GetComputedValues().border_radius(); - MeshUtilities::GenerateBackground(mesh, box, {}, border_radius, ColourbPremultiplied(255), clip_area); - if (RenderManager* render_manager = element->GetRenderManager()) - geometry = render_manager->MakeGeometry(std::move(mesh)); + MeshUtilities::GenerateBackground(mesh, element->GetRenderBox(clip_area), ColourbPremultiplied(255)); + geometry = render_manager->MakeGeometry(std::move(mesh)); } return &geometry; @@ -144,17 +145,14 @@ void ElementBackgroundBorder::GenerateGeometry(Element* element) ConvertColor(computed.border_bottom_color()), ConvertColor(computed.border_left_color()), }; - const Vector4f border_radius = computed.border_radius(); + const CornerSizes border_radius = computed.border_radius(); Geometry& geometry = GetOrCreateBackground(BackgroundType::BackgroundBorder).geometry; Mesh mesh = geometry.Release(Geometry::ReleaseMode::ClearMesh); for (int i = 0; i < element->GetNumBoxes(); i++) - { - Vector2f offset; - const Box& box = element->GetBox(i, offset); - MeshUtilities::GenerateBackgroundBorder(mesh, box, offset, border_radius, background_color, border_colors); - } + MeshUtilities::GenerateBackgroundBorder(mesh, element->GetRenderBox(BoxArea::Padding, i), background_color, border_colors); + geometry = render_manager->MakeGeometry(std::move(mesh)); if (has_box_shadow) diff --git a/Source/Core/ElementUtilities.cpp b/Source/Core/ElementUtilities.cpp index 0a5c6eddc..7ce1901e7 100644 --- a/Source/Core/ElementUtilities.cpp +++ b/Source/Core/ElementUtilities.cpp @@ -187,7 +187,7 @@ bool ElementUtilities::GetClippingRegion(Element* element, Rectanglei& out_clip_ { Geometry* clip_geometry = clipping_element->GetElementBackgroundBorder()->GetClipGeometry(clipping_element, clip_area); const ClipMaskOperation clip_operation = (out_clip_mask_list->empty() ? ClipMaskOperation::Set : ClipMaskOperation::Intersect); - const Vector2f absolute_offset = clipping_element->GetAbsoluteOffset(BoxArea::Border); + const Vector2f absolute_offset = clipping_element->GetAbsoluteOffset(BoxArea::Border).Round(); out_clip_mask_list->push_back(ClipMaskGeometry{clip_operation, clip_geometry, absolute_offset, transform}); } @@ -201,8 +201,8 @@ bool ElementUtilities::GetClippingRegion(Element* element, Rectanglei& out_clip_ if (has_clipping_content && !disable_scissor_clipping) { // Shrink the scissor region to the element's client area. - Vector2f element_offset = clipping_element->GetAbsoluteOffset(clip_area); - Vector2f element_size = clipping_element->GetBox().GetSize(clip_area); + Vector2f element_offset = clipping_element->GetAbsoluteOffset(clip_area).Round(); + Vector2f element_size = clipping_element->GetRenderBox(clip_area).GetFillSize(); Rectanglef element_region = Rectanglef::FromPositionSize(element_offset, element_size); clip_region = element_region.IntersectIfValid(clip_region); diff --git a/Source/Core/Elements/ElementImage.cpp b/Source/Core/Elements/ElementImage.cpp index 6f087db14..4f78144f4 100644 --- a/Source/Core/Elements/ElementImage.cpp +++ b/Source/Core/Elements/ElementImage.cpp @@ -86,8 +86,7 @@ void ElementImage::OnRender() if (geometry_dirty) GenerateGeometry(); - // Render the geometry beginning at this element's content region. - geometry.Render(GetAbsoluteOffset(BoxArea::Content).Round(), texture); + geometry.Render(GetAbsoluteOffset(BoxArea::Border), texture); } void ElementImage::OnAttributeChange(const ElementAttributes& changed_attributes) @@ -131,9 +130,7 @@ void ElementImage::OnPropertyChange(const PropertyIdSet& changed_properties) Element::OnPropertyChange(changed_properties); if (changed_properties.Contains(PropertyId::ImageColor) || changed_properties.Contains(PropertyId::Opacity)) - { - GenerateGeometry(); - } + geometry_dirty = true; } void ElementImage::OnChildAdd(Element* child) @@ -147,7 +144,7 @@ void ElementImage::OnChildAdd(Element* child) void ElementImage::OnResize() { - GenerateGeometry(); + geometry_dirty = true; } void ElementImage::OnDpRatioChange() @@ -185,10 +182,10 @@ void ElementImage::GenerateGeometry() } const ComputedValues& computed = GetComputedValues(); - ColourbPremultiplied quad_colour = computed.image_color().ToPremultiplied(computed.opacity()); - Vector2f quad_size = GetBox().GetSize(BoxArea::Content).Round(); + const ColourbPremultiplied quad_colour = computed.image_color().ToPremultiplied(computed.opacity()); + const RenderBox render_box = GetRenderBox(BoxArea::Content); - MeshUtilities::GenerateQuad(mesh, Vector2f(0, 0), quad_size, quad_colour, texcoords[0], texcoords[1]); + MeshUtilities::GenerateQuad(mesh, render_box.GetFillOffset(), render_box.GetFillSize(), quad_colour, texcoords[0], texcoords[1]); if (RenderManager* render_manager = GetRenderManager()) geometry = render_manager->MakeGeometry(std::move(mesh)); diff --git a/Source/Core/GeometryBackgroundBorder.cpp b/Source/Core/GeometryBackgroundBorder.cpp index 3250253d8..c8c99fdee 100644 --- a/Source/Core/GeometryBackgroundBorder.cpp +++ b/Source/Core/GeometryBackgroundBorder.cpp @@ -37,7 +37,7 @@ namespace Rml { GeometryBackgroundBorder::GeometryBackgroundBorder(Vector& vertices, Vector& indices) : vertices(vertices), indices(indices) {} BorderMetrics GeometryBackgroundBorder::ComputeBorderMetrics(Vector2f outer_position, EdgeSizes edge_sizes, Vector2f inner_size, - Vector4f outer_radii_def) + CornerSizes outer_radii_def) { BorderMetrics metrics = {}; @@ -68,7 +68,7 @@ BorderMetrics GeometryBackgroundBorder::ComputeBorderMetrics(Vector2f outer_posi if (has_radius) { auto& outer_radii = metrics.outer_radii; - outer_radii = {outer_radii_def.x, outer_radii_def.y, outer_radii_def.z, outer_radii_def.w}; + outer_radii = outer_radii_def; // Scale the radii such that we have no overlapping curves. float scale_factor = FLT_MAX; diff --git a/Source/Core/GeometryBackgroundBorder.h b/Source/Core/GeometryBackgroundBorder.h index f4bb4cef6..153907141 100644 --- a/Source/Core/GeometryBackgroundBorder.h +++ b/Source/Core/GeometryBackgroundBorder.h @@ -72,7 +72,7 @@ class GeometryBackgroundBorder { /// @param inner_size Size of the inner area. /// @param outer_radii The radius of the outer edge at each corner. /// @return The computed metrics. - static BorderMetrics ComputeBorderMetrics(Vector2f outer_position, EdgeSizes edge_sizes, Vector2f inner_size, Vector4f outer_radii); + static BorderMetrics ComputeBorderMetrics(Vector2f outer_position, EdgeSizes edge_sizes, Vector2f inner_size, CornerSizes outer_radii); // Generate geometry for the background, defined by the inner area of the border metrics. void DrawBackground(const BorderMetrics& metrics, ColourbPremultiplied color); diff --git a/Source/Core/GeometryBoxShadow.cpp b/Source/Core/GeometryBoxShadow.cpp index b09872dfc..93f68b743 100644 --- a/Source/Core/GeometryBoxShadow.cpp +++ b/Source/Core/GeometryBoxShadow.cpp @@ -39,7 +39,7 @@ namespace Rml { void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& out_shadow_texture, RenderManager& render_manager, Element* element, - Geometry& background_border_geometry, BoxShadowList shadow_list, const Vector4f border_radius, const float opacity) + Geometry& background_border_geometry, BoxShadowList shadow_list, const CornerSizes border_radius, const float opacity) { // Find the box-shadow texture dimension and offset required to cover all box-shadows and element boxes combined. Vector2f element_offset_in_texture; @@ -75,9 +75,8 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& // Extend the render-texture further to cover all the element's boxes. for (int i = 0; i < element->GetNumBoxes(); i++) { - Vector2f offset; - const Box& box = element->GetBox(i, offset); - texture_region = texture_region.Join(Rectanglef::FromPositionSize(offset, box.GetSize(BoxArea::Border))); + const RenderBox box = element->GetRenderBox(BoxArea::Border, i); + texture_region = texture_region.Join(Rectanglef::FromPositionSize(box.GetBorderOffset(), box.GetFillSize())); } texture_region = texture_region.Extend(-extend_min, extend_max); @@ -109,14 +108,11 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& // Generate the geometry for all the element's boxes and extend the render-texture further to cover all of them. for (int i = 0; i < element->GetNumBoxes(); i++) { - Vector2f offset; - const Box& box = element->GetBox(i, offset); ColourbPremultiplied white(255); - if (has_inner_shadow) - MeshUtilities::GenerateBackground(mesh_padding, box, offset, border_radius, white, BoxArea::Padding); + MeshUtilities::GenerateBackground(mesh_padding, element->GetRenderBox(BoxArea::Padding, i), white); if (has_outer_shadow) - MeshUtilities::GenerateBackground(mesh_padding_border, box, offset, border_radius, white, BoxArea::Border); + MeshUtilities::GenerateBackground(mesh_padding_border, element->GetRenderBox(BoxArea::Border, i), white); } const RenderState initial_render_state = render_manager.GetState(); @@ -152,7 +148,7 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& const float spread_distance = shadow.spread_distance.number; const float blur_radius = shadow.blur_radius.number; - Vector4f spread_radii = border_radius; + CornerSizes spread_radii = border_radius; for (int i = 0; i < 4; i++) { float& radius = spread_radii[i]; @@ -167,22 +163,15 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& Mesh mesh_shadow; - // Generate the shadow geometry. For outer box-shadows it is rendered normally, while for inner box-shadows it is used as a clipping mask. + // Generate the shadow geometry. For outer box-shadows it is rendered normally, while for inset box-shadows it is used as a clipping mask. for (int i = 0; i < element->GetNumBoxes(); i++) { - Vector2f offset; - Box box = element->GetBox(i, offset); const float signed_spread_distance = (inset ? -spread_distance : spread_distance); - offset -= Vector2f(signed_spread_distance); - - for (int j = 0; j < Box::num_edges; j++) - { - BoxEdge edge = (BoxEdge)j; - const float new_size = box.GetEdge(BoxArea::Padding, edge) + signed_spread_distance; - box.SetEdge(BoxArea::Padding, edge, new_size); - } - - MeshUtilities::GenerateBackground(mesh_shadow, box, offset, spread_radii, shadow.color, inset ? BoxArea::Padding : BoxArea::Border); + RenderBox render_box = element->GetRenderBox(inset ? BoxArea::Padding : BoxArea::Border, i); + render_box.SetFillSize(Math::Max(render_box.GetFillSize() + Vector2f(2.f * signed_spread_distance), Vector2f{0.001f})); + render_box.SetBorderRadius(spread_radii); + render_box.SetBorderOffset(render_box.GetBorderOffset() - Vector2f(signed_spread_distance)); + MeshUtilities::GenerateBackground(mesh_shadow, render_box, shadow.color); } CompiledFilter blur; diff --git a/Source/Core/GeometryBoxShadow.h b/Source/Core/GeometryBoxShadow.h index 3d39a2dce..83713848a 100644 --- a/Source/Core/GeometryBoxShadow.h +++ b/Source/Core/GeometryBoxShadow.h @@ -29,6 +29,7 @@ #ifndef RMLUI_CORE_GEOMETRYBOXSHADOW_H #define RMLUI_CORE_GEOMETRYBOXSHADOW_H +#include "../../Include/RmlUi/Core/RenderBox.h" #include "../../Include/RmlUi/Core/Types.h" namespace Rml { @@ -50,7 +51,7 @@ class GeometryBoxShadow { /// @param[in] border_radius The border radius of the element. /// @param[in] opacity The opacity of the element. static void Generate(Geometry& out_shadow_geometry, CallbackTexture& out_shadow_texture, RenderManager& render_manager, Element* element, - Geometry& background_border_geometry, BoxShadowList shadow_list, Vector4f border_radius, float opacity); + Geometry& background_border_geometry, BoxShadowList shadow_list, CornerSizes border_radius, float opacity); }; } // namespace Rml diff --git a/Source/Core/MeshUtilities.cpp b/Source/Core/MeshUtilities.cpp index 5776e5880..07e2be384 100644 --- a/Source/Core/MeshUtilities.cpp +++ b/Source/Core/MeshUtilities.cpp @@ -82,31 +82,22 @@ void MeshUtilities::GenerateLine(Mesh& mesh, Vector2f position, Vector2f size, C MeshUtilities::GenerateQuad(mesh, position, size, color); } -void MeshUtilities::GenerateBackgroundBorder(Mesh& out_mesh, const Box& box, Vector2f offset, Vector4f border_radius, - ColourbPremultiplied background_color, const ColourbPremultiplied border_colors[4]) +void MeshUtilities::GenerateBackgroundBorder(Mesh& out_mesh, const RenderBox& render_box, ColourbPremultiplied background_color, + const ColourbPremultiplied border_colors[4]) { RMLUI_ASSERT(border_colors); Vector& vertices = out_mesh.vertices; Vector& indices = out_mesh.indices; - EdgeSizes border_widths = { - // TODO: Move rounding to computed values (round border only). - Math::Round(box.GetEdge(BoxArea::Border, BoxEdge::Top)), - Math::Round(box.GetEdge(BoxArea::Border, BoxEdge::Right)), - Math::Round(box.GetEdge(BoxArea::Border, BoxEdge::Bottom)), - Math::Round(box.GetEdge(BoxArea::Border, BoxEdge::Left)), - }; - + const EdgeSizes& border_widths = render_box.GetBorderWidths(); int num_borders = 0; - for (int i = 0; i < 4; i++) if (border_colors[i].alpha > 0 && border_widths[i] > 0) num_borders += 1; - const Vector2f padding_size = box.GetSize(BoxArea::Padding).Round(); - - const bool has_background = (background_color.alpha > 0 && padding_size.x > 0 && padding_size.y > 0); + const Vector2f fill_size = render_box.GetFillSize(); + const bool has_background = (background_color.alpha > 0 && fill_size.x > 0 && fill_size.y > 0); const bool has_border = (num_borders > 0); if (!has_background && !has_border) @@ -120,7 +111,8 @@ void MeshUtilities::GenerateBackgroundBorder(Mesh& out_mesh, const Box& box, Vec // Generate the geometry. GeometryBackgroundBorder geometry(vertices, indices); - const BorderMetrics metrics = GeometryBackgroundBorder::ComputeBorderMetrics(offset.Round(), border_widths, padding_size, border_radius); + const BorderMetrics metrics = + GeometryBackgroundBorder::ComputeBorderMetrics(render_box.GetBorderOffset(), border_widths, fill_size, render_box.GetBorderRadius()); if (has_background) geometry.DrawBackground(metrics, background_color); @@ -130,19 +122,15 @@ void MeshUtilities::GenerateBackgroundBorder(Mesh& out_mesh, const Box& box, Vec #if 0 // Debug draw vertices - if (border_radius != Vector4f(0)) + if (render_box.border_radius != CornerSizes{}) { const int num_vertices = (int)vertices.size(); const int num_indices = (int)indices.size(); - - vertices.resize(num_vertices + 4 * num_vertices); - indices.resize(num_indices + 6 * num_indices); + vertices.reserve(num_vertices + 4 * num_vertices); + indices.reserve(num_indices + 6 * num_indices); for (int i = 0; i < num_vertices; i++) - { - MeshUtilities::GenerateQuad(vertices.data() + num_vertices + 4 * i, indices.data() + num_indices + 6 * i, vertices[i].position, - Vector2f(3, 3), ColourbPremultiplied(255, 0, (i % 2) == 0 ? 0 : 255), num_vertices + 4 * i); - } + MeshUtilities::GenerateQuad(out_mesh, vertices[i].position, Vector2f(3, 3), ColourbPremultiplied(255, 0, (i % 2) == 0 ? 0 : 255)); } #endif @@ -155,29 +143,15 @@ void MeshUtilities::GenerateBackgroundBorder(Mesh& out_mesh, const Box& box, Vec #endif } -void MeshUtilities::GenerateBackground(Mesh& out_mesh, const Box& box, Vector2f offset, Vector4f border_radius, ColourbPremultiplied color, - BoxArea fill_area) +void MeshUtilities::GenerateBackground(Mesh& out_mesh, const RenderBox& render_box, ColourbPremultiplied color) { - RMLUI_ASSERTMSG(fill_area >= BoxArea::Border && fill_area <= BoxArea::Content, - "Rectangle geometry only supports border, padding and content boxes."); - - EdgeSizes edge_sizes = {}; - for (int area = (int)BoxArea::Border; area < (int)fill_area; area++) - { - // TODO: Move rounding to computed values (round border only). - edge_sizes[0] += Math::Round(box.GetEdge(BoxArea(area), BoxEdge::Top)); - edge_sizes[1] += Math::Round(box.GetEdge(BoxArea(area), BoxEdge::Right)); - edge_sizes[2] += Math::Round(box.GetEdge(BoxArea(area), BoxEdge::Bottom)); - edge_sizes[3] += Math::Round(box.GetEdge(BoxArea(area), BoxEdge::Left)); - } - - const Vector2f inner_size = box.GetSize(fill_area).Round(); - - const bool has_background = (color.alpha > 0 && inner_size.x > 0 && inner_size.y > 0); + const Vector2f fill_size = render_box.GetFillSize(); + const bool has_background = (color.alpha > 0 && fill_size.x > 0 && fill_size.y > 0); if (!has_background) return; - const BorderMetrics metrics = GeometryBackgroundBorder::ComputeBorderMetrics(offset.Round(), edge_sizes, inner_size, border_radius); + const BorderMetrics metrics = GeometryBackgroundBorder::ComputeBorderMetrics(render_box.GetBorderOffset(), render_box.GetBorderWidths(), + fill_size, render_box.GetBorderRadius()); Vector& vertices = out_mesh.vertices; Vector& indices = out_mesh.indices; diff --git a/Source/Core/RenderManager.cpp b/Source/Core/RenderManager.cpp index a5d889db2..8c1cc0242 100644 --- a/Source/Core/RenderManager.cpp +++ b/Source/Core/RenderManager.cpp @@ -238,6 +238,8 @@ CompiledGeometryHandle RenderManager::GetCompiledGeometryHandle(StableVectorInde void RenderManager::Render(const Geometry& geometry, Vector2f translation, Texture texture, const CompiledShader& shader) { RMLUI_ASSERT(geometry); + RMLUI_ASSERTMSG(translation == translation.Round(), "RenderManager::Render expects translation to be rounded"); + if (geometry.render_manager != this || (shader && shader.render_manager != this) || (texture && texture.render_manager != this)) { RMLUI_ERRORMSG("Trying to render geometry with resources constructed in different render managers."); diff --git a/Source/Debugger/DebuggerPlugin.cpp b/Source/Debugger/DebuggerPlugin.cpp index 73dc6368d..9b6a300b7 100644 --- a/Source/Debugger/DebuggerPlugin.cpp +++ b/Source/Debugger/DebuggerPlugin.cpp @@ -168,9 +168,8 @@ void DebuggerPlugin::Render() ElementUtilities::ApplyTransform(*element); for (int j = 0; j < element->GetNumBoxes(); ++j) { - Vector2f box_offset; - const Box& box = element->GetBox(j, box_offset); - Geometry::RenderOutline(element->GetAbsoluteOffset(BoxArea::Border) + box_offset, box.GetSize(BoxArea::Border), + const RenderBox box = element->GetRenderBox(BoxArea::Border, j); + Geometry::RenderOutline(element->GetAbsoluteOffset(BoxArea::Border) + box.GetBorderOffset(), box.GetFillSize(), Colourb(255, 0, 0, 128), 1); } diff --git a/Tests/Source/UnitTests/CMakeLists.txt b/Tests/Source/UnitTests/CMakeLists.txt index 5553ebd72..732180fa3 100644 --- a/Tests/Source/UnitTests/CMakeLists.txt +++ b/Tests/Source/UnitTests/CMakeLists.txt @@ -9,6 +9,7 @@ add_executable(${TARGET_NAME} Debugger.cpp Decorator.cpp Element.cpp + ElementBackgroundBorder.cpp ElementDocument.cpp ElementHandle.cpp ElementFormControlSelect.cpp diff --git a/Tests/Source/UnitTests/ElementBackgroundBorder.cpp b/Tests/Source/UnitTests/ElementBackgroundBorder.cpp new file mode 100644 index 000000000..fbf475c2a --- /dev/null +++ b/Tests/Source/UnitTests/ElementBackgroundBorder.cpp @@ -0,0 +1,214 @@ +/* + * This source file is part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019-2024 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +#include "../Common/TestsInterface.h" +#include "../Common/TestsShell.h" +#include +#include +#include +#include +#include +#include + +using namespace Rml; + +static String GenerateRowsRml(int num_rows, String row_rml) +{ + String rml; + rml.reserve(num_rows * row_rml.size()); + for (int i = 0; i < num_rows; i++) + rml += row_rml; + return rml; +} + +static const String document_basic_rml = R"( + + +Demo + + + + + +
+
+ +
+)"; + +TEST_CASE("ElementBackgroundBorder.render_stats") +{ + Context* context = TestsShell::GetContext(); + REQUIRE(context); + + ElementDocument* document = context->LoadDocumentFromMemory(document_basic_rml); + REQUIRE(document); + document->Show(); + + constexpr int num_rows = 10; + const String row_rml = "
"; + const String inner_rml = GenerateRowsRml(num_rows, row_rml); + + Element* wrapper = document->GetElementById("wrapper"); + REQUIRE(wrapper); + wrapper->SetInnerRML(inner_rml); + + context->Update(); + context->Render(); + + TestsShell::RenderLoop(); + + TestsRenderInterface* render_interface = TestsShell::GetTestsRenderInterface(); + if (!render_interface) + return; + + MESSAGE(TestsShell::GetRenderStats()); + render_interface->Reset(); + + for (int i = 1; i < 50; i++) + { + wrapper->SetScrollTop(1.3333f * float(i)); + context->Update(); + context->Render(); + } + + wrapper->SetScrollTop(FLT_MAX); + context->Update(); + context->Render(); + + // Ensure that we have no unnecessary compile geometry commands. The geometry for the background-border should not + // change in size as long as scrolling occurs in integer increments. + CHECK(render_interface->GetCounters().compile_geometry == 0); + + document->Close(); + TestsShell::ShutdownShell(); +} + +static const String document_relative_offset_rml = R"( + + +Demo + + + + + +
+
+ +
+)"; + +TEST_CASE("ElementBackgroundBorder.background_edges_line_up_with_relative_offset") +{ + Context* context = TestsShell::GetContext(); + REQUIRE(context); + + ElementDocument* document = context->LoadDocumentFromMemory(document_relative_offset_rml); + REQUIRE(document); + document->Show(); + + constexpr int num_children = 10; + const String row_rml = "
"; + const String inner_rml = GenerateRowsRml(num_children, row_rml); + + Element* wrapper = document->GetElementById("wrapper"); + REQUIRE(wrapper); + wrapper->SetInnerRML(inner_rml); + + context->Update(); + context->Render(); + + TestsShell::RenderLoop(); + + TestsRenderInterface* render_interface = TestsShell::GetTestsRenderInterface(); + if (!render_interface) + return; + + MESSAGE(TestsShell::GetRenderStats()); + render_interface->Reset(); + + for (int i = 1; i < 100; i++) + { + for (int child_index = 0; child_index < num_children; child_index++) + { + Element* child = wrapper->GetChild(child_index); + child->SetProperty(PropertyId::Top, Property(1.3333f * float(i), Unit::PX)); + } + + context->Update(); + context->Render(); + + for (int child_index = 0; child_index < num_children - 1; child_index++) + { + Element* current_child = wrapper->GetChild(child_index); + Element* next_child = wrapper->GetChild(child_index + 1); + Vector2f current_bottom_right = current_child->GetAbsoluteOffset(BoxArea::Border).Round() + current_child->GetRenderBox().GetFillSize(); + Vector2f next_top_left = next_child->GetAbsoluteOffset(BoxArea::Border).Round(); + CHECK(current_bottom_right.y == next_top_left.y); + } + } + + // When changing the position using fractional increments we expect the size of the backgrounds to change, resulting + // in new geometry. This is done to ensure that the top and bottom of each background lines up with the one for the + // next element, thereby avoiding any gaps. + CHECK(render_interface->GetCounters().compile_geometry > 0); + MESSAGE("Compile geometry after movement: ", render_interface->GetCounters().compile_geometry); + + document->Close(); + TestsShell::ShutdownShell(); +}