/*
* 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-2023 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 "ElementDecoration.h"
#include "../../Include/RmlUi/Core/ComputedValues.h"
#include "../../Include/RmlUi/Core/Context.h"
#include "../../Include/RmlUi/Core/Decorator.h"
#include "../../Include/RmlUi/Core/Element.h"
#include "../../Include/RmlUi/Core/ElementDocument.h"
#include "../../Include/RmlUi/Core/ElementUtilities.h"
#include "../../Include/RmlUi/Core/Filter.h"
#include "../../Include/RmlUi/Core/Profiling.h"
#include "../../Include/RmlUi/Core/RenderInterface.h"
#include "../../Include/RmlUi/Core/StyleSheet.h"
namespace Rml {
ElementDecoration::ElementDecoration(Element* _element) : element(_element) {}
ElementDecoration::~ElementDecoration()
{
ReleaseDecorators();
}
void ElementDecoration::InstanceDecorators()
{
if (!decorators_dirty)
return;
decorators_dirty = false;
decorators_data_dirty = true;
RMLUI_ZoneScopedC(0xB22222);
ReleaseDecorators();
const ComputedValues& computed = element->GetComputedValues();
if (computed.has_decorator())
{
const Property* property = element->GetLocalProperty(PropertyId::Decorator);
if (!property || property->unit != Unit::DECORATOR)
return;
DecoratorsPtr decorators_ptr = property->Get();
if (!decorators_ptr)
return;
const StyleSheet* style_sheet = element->GetStyleSheet();
if (!style_sheet)
return;
PropertySource document_source("", 0, "");
const PropertySource* source = property->source.get();
if (!source)
{
if (ElementDocument* document = element->GetOwnerDocument())
{
document_source.path = document->GetSourceURL();
source = &document_source;
}
}
const DecoratorPtrList& decorator_list = style_sheet->InstanceDecorators(*decorators_ptr, source);
for (const SharedPtr& decorator : decorator_list)
{
if (decorator)
{
DecoratorEntry decorator_handle;
decorator_handle.decorator_data = 0;
decorator_handle.decorator = decorator;
decorators.push_back(std::move(decorator_handle));
}
}
}
if (computed.has_filter() || computed.has_backdrop_filter())
{
for (const auto id : {PropertyId::Filter, PropertyId::BackdropFilter})
{
const Property* property = element->GetLocalProperty(id);
if (!property || property->unit != Unit::FILTER)
return;
FiltersPtr filters_ptr = property->Get();
if (!filters_ptr)
return;
FilterEntryList& list = (id == PropertyId::Filter ? filters : backdrop_filters);
list.reserve(filters_ptr->list.size());
for (const FilterDeclaration& declaration : filters_ptr->list)
{
SharedPtr filter = declaration.instancer->InstanceFilter(declaration.type, declaration.properties);
if (filter)
{
list.push_back({std::move(filter), CompiledFilterHandle{}});
}
else
{
const auto& source = property->source;
Log::Message(Log::LT_WARNING, "Filter '%s' in '%s' could not be instanced, declared at %s:%d", declaration.type.c_str(),
filters_ptr->value.c_str(), source ? source->path.c_str() : "", source ? source->line_number : -1);
}
}
}
}
}
void ElementDecoration::ReloadDecoratorsData()
{
if (decorators_data_dirty)
{
decorators_data_dirty = false;
for (DecoratorEntry& decorator : decorators)
{
if (decorator.decorator_data)
decorator.decorator->ReleaseElementData(decorator.decorator_data);
decorator.decorator_data = decorator.decorator->GenerateElementData(element);
}
for (FilterEntryList* list : {&filters, &backdrop_filters})
{
for (FilterEntry& filter : *list)
{
if (filter.handle)
filter.filter->ReleaseCompiledFilter(element, filter.handle);
filter.handle = filter.filter->CompileFilter(element);
}
}
}
}
void ElementDecoration::ReleaseDecorators()
{
for (DecoratorEntry& decorator : decorators)
{
if (decorator.decorator_data)
decorator.decorator->ReleaseElementData(decorator.decorator_data);
}
decorators.clear();
for (FilterEntryList* list : {&filters, &backdrop_filters})
{
for (FilterEntry& filter : *list)
{
if (filter.handle)
filter.filter->ReleaseCompiledFilter(element, filter.handle);
}
list->clear();
}
}
void ElementDecoration::RenderDecorators(RenderStage render_stage)
{
InstanceDecorators();
ReloadDecoratorsData();
if (!decorators.empty())
{
if (render_stage == RenderStage::Decoration)
{
// Render the decorators attached to this element in its current state.
// Render from back to front for correct render order.
for (int i = (int)decorators.size() - 1; i >= 0; i--)
{
DecoratorEntry& decorator = decorators[i];
decorator.decorator->RenderElement(element, decorator.decorator_data);
}
}
}
if (filters.empty() && backdrop_filters.empty())
return;
RenderInterface* render_interface = ::Rml::GetRenderInterface();
Context* context = element->GetContext();
if (!render_interface || !context)
return;
auto ApplyClippingRegion = [this, render_interface, context](bool extend_ink_overflow) {
ElementUtilities::SetClippingRegion(element); // TODO: For backdrop-filter only: Force clipping to our border-box.
// Find the region being affected by the active filters and apply it as a scissor.
Rectanglef filter_region = Rectanglef::MakeInvalid();
ElementUtilities::GetBoundingBox(filter_region, element, BoxArea::Auto);
if (extend_ink_overflow)
{
for (const auto& filter : filters)
filter.filter->ExtendInkOverflow(element, filter_region);
}
Math::ExpandToPixelGrid(filter_region);
Rectanglei scissor_region = Rectanglei::FromSize(context->GetDimensions());
Vector2i clip_position, clip_size;
if (context->GetActiveClipRegion(clip_position, clip_size))
scissor_region.Intersect(Rectanglei::FromPositionSize(clip_position, clip_size));
scissor_region.IntersectIfValid(Rectanglei(filter_region));
render_interface->EnableScissorRegion(true);
render_interface->SetScissorRegion(scissor_region.Left(), scissor_region.Top(), scissor_region.Width(), scissor_region.Height());
};
if (!backdrop_filters.empty())
{
if (render_stage == RenderStage::Enter)
{
ApplyClippingRegion(false);
render_interface->PushLayer(LayerFill::Clone);
FilterHandleList filter_handles;
for (auto& filter : backdrop_filters)
{
if (filter.handle)
filter_handles.push_back(filter.handle);
}
render_interface->PopLayer(BlendMode::Replace, filter_handles);
ElementUtilities::ApplyActiveClipRegion(context);
}
}
if (!filters.empty())
{
if (render_stage == RenderStage::Enter)
{
render_interface->PushLayer(LayerFill::Clear);
}
else if (render_stage == RenderStage::Exit)
{
ApplyClippingRegion(true);
FilterHandleList filter_handles;
for (auto& filter : filters)
{
if (filter.handle)
filter_handles.push_back(filter.handle);
}
render_interface->PopLayer(BlendMode::Blend, filter_handles);
ElementUtilities::ApplyActiveClipRegion(context);
}
}
}
void ElementDecoration::DirtyDecorators()
{
decorators_dirty = true;
}
void ElementDecoration::DirtyDecoratorsData()
{
decorators_data_dirty = true;
}
} // namespace Rml