ElementBackgroundBorder.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. /*
  2. * This source file is part of RmlUi, the HTML/CSS Interface Middleware
  3. *
  4. * For the latest information, see http://github.com/mikke89/RmlUi
  5. *
  6. * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
  7. * Copyright (c) 2019-2023 The RmlUi Team, and contributors
  8. *
  9. * Permission is hereby granted, free of charge, to any person obtaining a copy
  10. * of this software and associated documentation files (the "Software"), to deal
  11. * in the Software without restriction, including without limitation the rights
  12. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. * copies of the Software, and to permit persons to whom the Software is
  14. * furnished to do so, subject to the following conditions:
  15. *
  16. * The above copyright notice and this permission notice shall be included in
  17. * all copies or substantial portions of the Software.
  18. *
  19. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. * THE SOFTWARE.
  26. *
  27. */
  28. #include "ElementBackgroundBorder.h"
  29. #include "../../Include/RmlUi/Core/Box.h"
  30. #include "../../Include/RmlUi/Core/ComputedValues.h"
  31. #include "../../Include/RmlUi/Core/Context.h"
  32. #include "../../Include/RmlUi/Core/DecorationTypes.h"
  33. #include "../../Include/RmlUi/Core/Element.h"
  34. #include "../../Include/RmlUi/Core/GeometryUtilities.h"
  35. namespace Rml {
  36. ElementBackgroundBorder::ElementBackgroundBorder() {}
  37. void ElementBackgroundBorder::Render(Element* element)
  38. {
  39. if (background_dirty || border_dirty)
  40. {
  41. for (auto& background : backgrounds)
  42. background.second.geometry.Release(true);
  43. GenerateGeometry(element);
  44. background_dirty = false;
  45. border_dirty = false;
  46. }
  47. Geometry* shadow_geometry = GetGeometry(BackgroundType::BoxShadow);
  48. if (shadow_geometry && *shadow_geometry)
  49. shadow_geometry->Render(element->GetAbsoluteOffset(BoxArea::Border));
  50. else if (Geometry* geometry = GetGeometry(BackgroundType::BackgroundBorder))
  51. geometry->Render(element->GetAbsoluteOffset(BoxArea::Border));
  52. }
  53. void ElementBackgroundBorder::DirtyBackground()
  54. {
  55. background_dirty = true;
  56. }
  57. void ElementBackgroundBorder::DirtyBorder()
  58. {
  59. border_dirty = true;
  60. }
  61. Geometry* ElementBackgroundBorder::GetClipGeometry(Element* element, BoxArea clip_area)
  62. {
  63. BackgroundType type = {};
  64. switch (clip_area)
  65. {
  66. case Rml::BoxArea::Border: type = BackgroundType::ClipBorder; break;
  67. case Rml::BoxArea::Padding: type = BackgroundType::ClipPadding; break;
  68. case Rml::BoxArea::Content: type = BackgroundType::ClipContent; break;
  69. default: RMLUI_ERROR; return nullptr;
  70. }
  71. Geometry& geometry = GetOrCreateBackground(type).geometry;
  72. if (!geometry)
  73. {
  74. const Box& box = element->GetBox();
  75. const Vector4f border_radius = element->GetComputedValues().border_radius();
  76. GeometryUtilities::GenerateBackground(&geometry, box, {}, border_radius, Colourb(255), clip_area);
  77. }
  78. return &geometry;
  79. }
  80. Geometry* ElementBackgroundBorder::GetGeometry(BackgroundType type)
  81. {
  82. auto it = backgrounds.find(type);
  83. if (it != backgrounds.end())
  84. return &it->second.geometry;
  85. return nullptr;
  86. }
  87. ElementBackgroundBorder::Background& ElementBackgroundBorder::GetOrCreateBackground(BackgroundType type)
  88. {
  89. auto it = backgrounds.find(type);
  90. if (it != backgrounds.end())
  91. return it->second;
  92. return backgrounds.emplace(type, Background{}).first->second;
  93. }
  94. void ElementBackgroundBorder::GenerateGeometry(Element* element)
  95. {
  96. const ComputedValues& computed = element->GetComputedValues();
  97. Colourb background_color = computed.background_color();
  98. Colourb border_colors[4] = {
  99. computed.border_top_color(),
  100. computed.border_right_color(),
  101. computed.border_bottom_color(),
  102. computed.border_left_color(),
  103. };
  104. const Vector4f border_radius = computed.border_radius();
  105. const bool has_box_shadow = computed.has_box_shadow();
  106. if (!has_box_shadow)
  107. {
  108. // Apply opacity except if we have a box shadow. In the latter case the background is rendered opaquely into the box-shadow texture, while
  109. // opacity is applied to the entire box-shadow texture when that is rendered.
  110. const float opacity = computed.opacity();
  111. if (opacity < 1.f)
  112. {
  113. background_color.alpha = (byte)(opacity * (float)background_color.alpha);
  114. for (int i = 0; i < 4; ++i)
  115. border_colors[i].alpha = (byte)(opacity * (float)border_colors[i].alpha);
  116. }
  117. }
  118. Geometry& geometry = GetOrCreateBackground(BackgroundType::BackgroundBorder).geometry;
  119. RMLUI_ASSERT(!geometry);
  120. for (int i = 0; i < element->GetNumBoxes(); i++)
  121. {
  122. Vector2f offset;
  123. const Box& box = element->GetBox(i, offset);
  124. GeometryUtilities::GenerateBackgroundBorder(&geometry, box, offset, border_radius, background_color, border_colors);
  125. }
  126. if (has_box_shadow)
  127. {
  128. const Property* p_box_shadow = element->GetLocalProperty(PropertyId::BoxShadow);
  129. RMLUI_ASSERT(p_box_shadow->value.GetType() == Variant::BOXSHADOWLIST);
  130. BoxShadowList shadow_list = p_box_shadow->value.Get<BoxShadowList>();
  131. GenerateBoxShadow(element, std::move(shadow_list), border_radius, computed.opacity());
  132. }
  133. }
  134. void ElementBackgroundBorder::GenerateBoxShadow(Element* element, BoxShadowList shadow_list, const Vector4f border_radius, const float opacity)
  135. {
  136. // Find the box-shadow texture dimension and offset required to cover all box-shadows and element boxes combined.
  137. Vector2f element_offset_in_texture;
  138. Vector2i texture_dimensions;
  139. // Resolve all lengths to px units.
  140. for (BoxShadow& shadow : shadow_list)
  141. {
  142. shadow.blur_radius = NumericValue(element->ResolveLength(shadow.blur_radius), Unit::PX);
  143. shadow.spread_distance = NumericValue(element->ResolveLength(shadow.spread_distance), Unit::PX);
  144. shadow.offset_x = NumericValue(element->ResolveLength(shadow.offset_x), Unit::PX);
  145. shadow.offset_y = NumericValue(element->ResolveLength(shadow.offset_y), Unit::PX);
  146. }
  147. {
  148. Vector2f extend_min;
  149. Vector2f extend_max;
  150. // Extend the render-texture to encompass box-shadow blur and spread.
  151. for (const BoxShadow& shadow : shadow_list)
  152. {
  153. if (!shadow.inset)
  154. {
  155. const float extend = 1.5f * shadow.blur_radius.number + shadow.spread_distance.number;
  156. const Vector2f offset = {shadow.offset_x.number, shadow.offset_y.number};
  157. extend_min = Math::Min(extend_min, offset - Vector2f(extend));
  158. extend_max = Math::Max(extend_max, offset + Vector2f(extend));
  159. }
  160. }
  161. Rectanglef texture_region;
  162. // Extend the render-texture further to cover all the element's boxes.
  163. for (int i = 0; i < element->GetNumBoxes(); i++)
  164. {
  165. Vector2f offset;
  166. const Box& box = element->GetBox(i, offset);
  167. texture_region.Join(Rectanglef::FromPositionSize(offset, box.GetSize(BoxArea::Border)));
  168. }
  169. texture_region.ExtendTopLeft(-extend_min);
  170. texture_region.ExtendBottomRight(extend_max);
  171. Math::ExpandToPixelGrid(texture_region);
  172. element_offset_in_texture = -texture_region.TopLeft();
  173. texture_dimensions = Vector2i(texture_region.Size());
  174. }
  175. Geometry& background_border_geometry = *GetGeometry(BackgroundType::BackgroundBorder);
  176. // Callback for generating the box-shadow texture. Using a callback ensures that the texture can be regenerated at any time, for example if the
  177. // device loses its GPU context and the client calls Rml::ReleaseTextures().
  178. auto p_callback = [&background_border_geometry, element, border_radius, texture_dimensions, element_offset_in_texture, shadow_list](
  179. RenderInterface* render_interface, const String& /*name*/, TextureHandle& out_handle, Vector2i& out_dimensions) -> bool {
  180. Context* context = element->GetContext();
  181. if (!context)
  182. {
  183. RMLUI_ERROR;
  184. return false;
  185. }
  186. Geometry geometry_padding; // Render geometry for inner box-shadow.
  187. Geometry geometry_padding_border; // Clipping mask for outer box-shadow.
  188. bool has_inner_shadow = false;
  189. bool has_outer_shadow = false;
  190. for (const BoxShadow& shadow : shadow_list)
  191. {
  192. if (shadow.inset)
  193. has_inner_shadow = true;
  194. else
  195. has_outer_shadow = true;
  196. }
  197. // Generate the geometry for all the element's boxes and extend the render-texture further to cover all of them.
  198. for (int i = 0; i < element->GetNumBoxes(); i++)
  199. {
  200. Vector2f offset;
  201. const Box& box = element->GetBox(i, offset);
  202. if (has_inner_shadow)
  203. GeometryUtilities::GenerateBackground(&geometry_padding, box, offset, border_radius, Colourb(255), BoxArea::Padding);
  204. if (has_outer_shadow)
  205. GeometryUtilities::GenerateBackground(&geometry_padding_border, box, offset, border_radius, Colourb(255), BoxArea::Border);
  206. }
  207. RenderManager& render_manager = context->GetRenderManager();
  208. const RenderState initial_render_state = render_manager.GetState();
  209. render_manager.ResetState();
  210. render_manager.SetScissorRegion(Rectanglei::FromSize(texture_dimensions));
  211. render_interface->PushLayer(LayerFill::Clear);
  212. background_border_geometry.Render(element_offset_in_texture);
  213. for (int shadow_index = (int)shadow_list.size() - 1; shadow_index >= 0; shadow_index--)
  214. {
  215. const BoxShadow& shadow = shadow_list[shadow_index];
  216. const Vector2f shadow_offset = {shadow.offset_x.number, shadow.offset_y.number};
  217. const bool inset = shadow.inset;
  218. const float spread_distance = shadow.spread_distance.number;
  219. const float blur_radius = shadow.blur_radius.number;
  220. Vector4f spread_radii = border_radius;
  221. for (int i = 0; i < 4; i++)
  222. {
  223. float& radius = spread_radii[i];
  224. float spread_factor = (inset ? -1.f : 1.f);
  225. if (radius < spread_distance)
  226. {
  227. const float ratio_minus_one = (radius / spread_distance) - 1.f;
  228. spread_factor *= 1.f + ratio_minus_one * ratio_minus_one * ratio_minus_one;
  229. }
  230. radius = Math::Max(radius + spread_factor * spread_distance, 0.f);
  231. }
  232. Geometry shadow_geometry;
  233. // Generate the shadow geometry. For outer box-shadows it is rendered normally, while for inner box-shadows it is used as a clipping mask.
  234. for (int i = 0; i < element->GetNumBoxes(); i++)
  235. {
  236. Vector2f offset;
  237. Box box = element->GetBox(i, offset);
  238. const float signed_spread_distance = (inset ? -spread_distance : spread_distance);
  239. offset -= Vector2f(signed_spread_distance);
  240. for (int j = 0; j < Box::num_edges; j++)
  241. {
  242. BoxEdge edge = (BoxEdge)j;
  243. const float new_size = box.GetEdge(BoxArea::Padding, edge) + signed_spread_distance;
  244. box.SetEdge(BoxArea::Padding, edge, new_size);
  245. }
  246. GeometryUtilities::GenerateBackground(&shadow_geometry, box, offset, spread_radii, shadow.color,
  247. inset ? BoxArea::Padding : BoxArea::Border);
  248. }
  249. CompiledFilterHandle blur = {};
  250. if (blur_radius > 0.5f)
  251. {
  252. blur = render_interface->CompileFilter("blur", Dictionary{{"radius", Variant(blur_radius)}});
  253. if (blur)
  254. render_interface->PushLayer(LayerFill::Clear);
  255. }
  256. if (inset)
  257. {
  258. render_manager.SetClipMask(ClipMaskOperation::SetInverse, &shadow_geometry, shadow_offset + element_offset_in_texture);
  259. for (Rml::Vertex& vertex : geometry_padding.GetVertices())
  260. vertex.colour = shadow.color;
  261. geometry_padding.Release();
  262. geometry_padding.Render(element_offset_in_texture);
  263. render_manager.SetClipMask(ClipMaskOperation::Set, &geometry_padding, element_offset_in_texture);
  264. }
  265. else
  266. {
  267. render_manager.SetClipMask(ClipMaskOperation::SetInverse, &geometry_padding_border, element_offset_in_texture);
  268. shadow_geometry.Render(shadow_offset + element_offset_in_texture);
  269. }
  270. if (blur)
  271. {
  272. render_interface->PopLayer(BlendMode::Blend, {blur});
  273. render_interface->ReleaseCompiledFilter(blur);
  274. }
  275. }
  276. TextureHandle shadow_texture = render_interface->SaveLayerAsTexture(texture_dimensions);
  277. if (!shadow_texture)
  278. return false;
  279. render_interface->PopLayer(BlendMode::Discard, {});
  280. render_manager.SetState(initial_render_state);
  281. out_dimensions = texture_dimensions;
  282. out_handle = shadow_texture;
  283. return true;
  284. };
  285. // Generate the geometry for the box-shadow texture.
  286. Background& shadow_background = GetOrCreateBackground(BackgroundType::BoxShadow);
  287. Geometry& shadow_geometry = shadow_background.geometry;
  288. Texture& shadow_texture = shadow_background.texture;
  289. RMLUI_ASSERT(!shadow_geometry);
  290. Vector<Vertex>& vertices = shadow_geometry.GetVertices();
  291. Vector<int>& indices = shadow_geometry.GetIndices();
  292. vertices.resize(4);
  293. indices.resize(6);
  294. const byte alpha = byte(opacity * 255.f);
  295. GeometryUtilities::GenerateQuad(vertices.data(), indices.data(), -element_offset_in_texture, Vector2f(texture_dimensions), Colourb(255, alpha));
  296. shadow_texture.Set("box-shadow", p_callback);
  297. shadow_geometry.SetTexture(&shadow_texture);
  298. }
  299. } // namespace Rml