DecoratorGradient.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  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 "DecoratorGradient.h"
  29. #include "../../Include/RmlUi/Core/ComputedValues.h"
  30. #include "../../Include/RmlUi/Core/Element.h"
  31. #include "../../Include/RmlUi/Core/ElementUtilities.h"
  32. #include "../../Include/RmlUi/Core/Geometry.h"
  33. #include "../../Include/RmlUi/Core/GeometryUtilities.h"
  34. #include "../../Include/RmlUi/Core/Math.h"
  35. #include "../../Include/RmlUi/Core/PropertyDefinition.h"
  36. #include "ComputeProperty.h"
  37. namespace Rml {
  38. // Returns the point along the input line ('line_point', 'line_vector') closest to the input 'point'.
  39. static Vector2f IntersectionPointToLineNormal(const Vector2f point, const Vector2f line_point, const Vector2f line_vector)
  40. {
  41. const Vector2f delta = line_point - point;
  42. return line_point - delta.DotProduct(line_vector) * line_vector;
  43. }
  44. /// Convert all color stop positions to normalized numbers.
  45. /// @param[in] element The element to resolve lengths against.
  46. /// @param[in] gradient_line_length The length of the gradient line, along which color stops are placed.
  47. /// @param[in] soft_spacing The desired minimum distance between stops to avoid aliasing, in normalized number units.
  48. /// @param[in] unresolved_stops
  49. /// @return A list of resolved color stops, all in number units.
  50. static ColorStopList ResolveColorStops(Element* element, const float gradient_line_length, const float soft_spacing,
  51. const ColorStopList& unresolved_stops)
  52. {
  53. ColorStopList stops = unresolved_stops;
  54. const int num_stops = (int)stops.size();
  55. // Resolve all lengths, percentages, and angles to numbers. After this step all stops with a unit other than Number are considered as Auto.
  56. for (ColorStop& stop : stops)
  57. {
  58. if (Any(stop.position.unit & Unit::LENGTH))
  59. {
  60. const float resolved_position = element->ResolveLength(stop.position);
  61. stop.position = NumericValue(resolved_position / gradient_line_length, Unit::NUMBER);
  62. }
  63. else if (stop.position.unit == Unit::PERCENT)
  64. {
  65. stop.position = NumericValue(stop.position.number * 0.01f, Unit::NUMBER);
  66. }
  67. else if (Any(stop.position.unit & Unit::ANGLE))
  68. {
  69. stop.position = NumericValue(ComputeAngle(stop.position) * (1.f / (2.f * Math::RMLUI_PI)), Unit::NUMBER);
  70. }
  71. }
  72. // Resolve auto positions of the first and last color stops.
  73. auto resolve_edge_stop = [](ColorStop& stop, float auto_to_number) {
  74. if (stop.position.unit != Unit::NUMBER)
  75. stop.position = NumericValue(auto_to_number, Unit::NUMBER);
  76. };
  77. resolve_edge_stop(stops[0], 0.f);
  78. resolve_edge_stop(stops[num_stops - 1], 1.f);
  79. // Ensures that color stop positions are strictly increasing, and have at least 1px spacing to avoid aliasing.
  80. auto nudge_stop = [prev_position = stops[0].position.number](ColorStop& stop, bool update_prev = true) mutable {
  81. stop.position.number = Math::Max(stop.position.number, prev_position);
  82. if (update_prev)
  83. prev_position = stop.position.number;
  84. };
  85. int auto_begin_i = -1;
  86. // Evenly space stops with sequential auto indices, and nudge stop positions to ensure strictly increasing positions.
  87. for (int i = 1; i < num_stops; i++)
  88. {
  89. ColorStop& stop = stops[i];
  90. if (stop.position.unit != Unit::NUMBER)
  91. {
  92. // Mark the first of any consecutive auto stops.
  93. if (auto_begin_i < 0)
  94. auto_begin_i = i;
  95. }
  96. else if (auto_begin_i < 0)
  97. {
  98. // The stop has a definite position and there are no previous autos to handle, just ensure it is properly spaced.
  99. nudge_stop(stop);
  100. }
  101. else
  102. {
  103. // Space out all the previous auto stops, indices [auto_begin_i, i).
  104. nudge_stop(stop, false);
  105. const int num_auto_stops = i - auto_begin_i;
  106. const float t0 = stops[auto_begin_i - 1].position.number;
  107. const float t1 = stop.position.number;
  108. for (int j = 0; j < num_auto_stops; j++)
  109. {
  110. const float fraction_along_t0_t1 = float(j + 1) / float(num_auto_stops + 1);
  111. stops[j + auto_begin_i].position = NumericValue(t0 + (t1 - t0) * fraction_along_t0_t1, Unit::NUMBER);
  112. nudge_stop(stops[j + auto_begin_i]);
  113. }
  114. nudge_stop(stop);
  115. auto_begin_i = -1;
  116. }
  117. }
  118. // Ensures that stops are placed some minimum distance from each other to avoid aliasing, if possible.
  119. for (int i = 1; i < num_stops - 1; i++)
  120. {
  121. const float p0 = stops[i - 1].position.number;
  122. const float p1 = stops[i].position.number;
  123. const float p2 = stops[i + 1].position.number;
  124. float& new_position = stops[i].position.number;
  125. if (p1 - p0 < soft_spacing)
  126. {
  127. if (p2 - p0 < 2.f * soft_spacing)
  128. new_position = 0.5f * (p2 + p0);
  129. else
  130. new_position = p0 + soft_spacing;
  131. }
  132. }
  133. RMLUI_ASSERT(std::all_of(stops.begin(), stops.end(), [](auto&& stop) { return stop.position.unit == Unit::NUMBER; }));
  134. return stops;
  135. }
  136. DecoratorStraightGradient::DecoratorStraightGradient() {}
  137. DecoratorStraightGradient::~DecoratorStraightGradient() {}
  138. bool DecoratorStraightGradient::Initialise(const Direction in_direction, const Colourb in_start, const Colourb in_stop)
  139. {
  140. direction = in_direction;
  141. start = in_start;
  142. stop = in_stop;
  143. return true;
  144. }
  145. DecoratorDataHandle DecoratorStraightGradient::GenerateElementData(Element* element) const
  146. {
  147. Geometry* geometry = new Geometry();
  148. const Box& box = element->GetBox();
  149. const ComputedValues& computed = element->GetComputedValues();
  150. const float opacity = computed.opacity();
  151. GeometryUtilities::GenerateBackground(geometry, element->GetBox(), Vector2f(0), computed.border_radius(), Colourb());
  152. // Apply opacity
  153. Colourb colour_start = start;
  154. colour_start.alpha = (byte)(opacity * (float)colour_start.alpha);
  155. Colourb colour_stop = stop;
  156. colour_stop.alpha = (byte)(opacity * (float)colour_stop.alpha);
  157. const Vector2f padding_offset = box.GetPosition(BoxArea::Padding);
  158. const Vector2f padding_size = box.GetSize(BoxArea::Padding);
  159. Vector<Vertex>& vertices = geometry->GetVertices();
  160. if (direction == Direction::Horizontal)
  161. {
  162. for (int i = 0; i < (int)vertices.size(); i++)
  163. {
  164. const float t = Math::Clamp((vertices[i].position.x - padding_offset.x) / padding_size.x, 0.0f, 1.0f);
  165. vertices[i].colour = Math::RoundedLerp(t, colour_start, colour_stop);
  166. }
  167. }
  168. else if (direction == Direction::Vertical)
  169. {
  170. for (int i = 0; i < (int)vertices.size(); i++)
  171. {
  172. const float t = Math::Clamp((vertices[i].position.y - padding_offset.y) / padding_size.y, 0.0f, 1.0f);
  173. vertices[i].colour = Math::RoundedLerp(t, colour_start, colour_stop);
  174. }
  175. }
  176. return reinterpret_cast<DecoratorDataHandle>(geometry);
  177. }
  178. void DecoratorStraightGradient::ReleaseElementData(DecoratorDataHandle element_data) const
  179. {
  180. delete reinterpret_cast<Geometry*>(element_data);
  181. }
  182. void DecoratorStraightGradient::RenderElement(Element* element, DecoratorDataHandle element_data) const
  183. {
  184. auto* data = reinterpret_cast<Geometry*>(element_data);
  185. data->Render(element->GetAbsoluteOffset(BoxArea::Border));
  186. }
  187. DecoratorStraightGradientInstancer::DecoratorStraightGradientInstancer()
  188. {
  189. ids.direction = RegisterProperty("direction", "horizontal").AddParser("keyword", "horizontal, vertical").GetId();
  190. ids.start = RegisterProperty("start-color", "#ffffff").AddParser("color").GetId();
  191. ids.stop = RegisterProperty("stop-color", "#ffffff").AddParser("color").GetId();
  192. RegisterShorthand("decorator", "direction, start-color, stop-color", ShorthandType::FallThrough);
  193. }
  194. DecoratorStraightGradientInstancer::~DecoratorStraightGradientInstancer() {}
  195. SharedPtr<Decorator> DecoratorStraightGradientInstancer::InstanceDecorator(const String& name, const PropertyDictionary& properties_,
  196. const DecoratorInstancerInterface& /*interface_*/)
  197. {
  198. using Direction = DecoratorStraightGradient::Direction;
  199. Direction direction;
  200. if (name == "horizontal-gradient")
  201. direction = Direction::Horizontal;
  202. else if (name == "vertical-gradient")
  203. direction = Direction::Vertical;
  204. else
  205. {
  206. direction = (Direction)properties_.GetProperty(ids.direction)->Get<int>();
  207. Log::Message(Log::LT_WARNING,
  208. "Decorator syntax 'gradient(horizontal|vertical ...)' is deprecated, please replace with 'horizontal-gradient(...)' or "
  209. "'vertical-gradient(...)'");
  210. }
  211. Colourb start = properties_.GetProperty(ids.start)->Get<Colourb>();
  212. Colourb stop = properties_.GetProperty(ids.stop)->Get<Colourb>();
  213. auto decorator = MakeShared<DecoratorStraightGradient>();
  214. if (decorator->Initialise(direction, start, stop))
  215. return decorator;
  216. return nullptr;
  217. }
  218. DecoratorLinearGradient::DecoratorLinearGradient() {}
  219. DecoratorLinearGradient::~DecoratorLinearGradient() {}
  220. bool DecoratorLinearGradient::Initialise(bool in_repeating, Corner in_corner, float in_angle, const ColorStopList& in_color_stops)
  221. {
  222. repeating = in_repeating;
  223. corner = in_corner;
  224. angle = in_angle;
  225. color_stops = in_color_stops;
  226. return !color_stops.empty();
  227. }
  228. DecoratorDataHandle DecoratorLinearGradient::GenerateElementData(Element* element) const
  229. {
  230. RenderInterface* render_interface = GetRenderInterface();
  231. if (!render_interface)
  232. return INVALID_DECORATORDATAHANDLE;
  233. RMLUI_ASSERT(!color_stops.empty());
  234. BoxArea box_area = BoxArea::Padding;
  235. const Box& box = element->GetBox();
  236. const Vector2f dimensions = box.GetSize(box_area);
  237. LinearGradientShape gradient_shape = CalculateShape(dimensions);
  238. // One-pixel minimum color stop spacing to avoid aliasing.
  239. const float soft_spacing = 1.f / gradient_shape.length;
  240. ColorStopList resolved_stops = ResolveColorStops(element, gradient_shape.length, soft_spacing, color_stops);
  241. auto element_data = MakeUnique<ElementData>();
  242. element_data->shader = render_interface->CompileShader("linear-gradient",
  243. Dictionary{
  244. {"angle", Variant(angle)},
  245. {"p0", Variant(gradient_shape.p0)},
  246. {"p1", Variant(gradient_shape.p1)},
  247. {"length", Variant(gradient_shape.length)},
  248. {"repeating", Variant(repeating)},
  249. {"color_stop_list", Variant(std::move(resolved_stops))},
  250. });
  251. if (!element_data->shader)
  252. return INVALID_DECORATORDATAHANDLE;
  253. Geometry& geometry = element_data->geometry;
  254. const ComputedValues& computed = element->GetComputedValues();
  255. const byte alpha = byte(computed.opacity() * 255.f);
  256. GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), Colourb(255, alpha), box_area);
  257. const Vector2f render_offset = box.GetPosition(box_area);
  258. for (Vertex& vertex : geometry.GetVertices())
  259. vertex.tex_coord = vertex.position - render_offset;
  260. return reinterpret_cast<DecoratorDataHandle>(element_data.release());
  261. }
  262. void DecoratorLinearGradient::ReleaseElementData(DecoratorDataHandle handle) const
  263. {
  264. ElementData* element_data = reinterpret_cast<ElementData*>(handle);
  265. GetRenderInterface()->ReleaseCompiledShader(element_data->shader);
  266. delete element_data;
  267. }
  268. void DecoratorLinearGradient::RenderElement(Element* element, DecoratorDataHandle handle) const
  269. {
  270. ElementData* element_data = reinterpret_cast<ElementData*>(handle);
  271. element_data->geometry.RenderWithShader(element_data->shader, element->GetAbsoluteOffset(BoxArea::Border));
  272. }
  273. DecoratorLinearGradient::LinearGradientShape DecoratorLinearGradient::CalculateShape(Vector2f dim) const
  274. {
  275. using uint = unsigned int;
  276. const Vector2f corners[(int)Corner::Count] = {Vector2f(dim.x, 0), dim, Vector2f(0, dim.y), Vector2f(0, 0)};
  277. const Vector2f center = 0.5f * dim;
  278. uint quadrant = 0;
  279. Vector2f line_vector;
  280. if (corner == Corner::None)
  281. {
  282. // Find the target quadrant and unit vector for the given angle.
  283. quadrant = uint(Math::NormaliseAngle(angle) * (4.f / (2.f * Math::RMLUI_PI))) % 4u;
  284. line_vector = Vector2f(Math::Sin(angle), -Math::Cos(angle));
  285. }
  286. else
  287. {
  288. // Quadrant given by the corner, need to find the vector perpendicular to the line connecting the neighboring corners.
  289. quadrant = uint(corner);
  290. const Vector2f v_neighbors = (corners[(quadrant + 1u) % 4u] - corners[(quadrant + 3u) % 4u]).Normalise();
  291. line_vector = {v_neighbors.y, -v_neighbors.x};
  292. }
  293. const uint quadrant_opposite = (quadrant + 2u) % 4u;
  294. const Vector2f starting_point = IntersectionPointToLineNormal(corners[quadrant_opposite], center, line_vector);
  295. const Vector2f ending_point = IntersectionPointToLineNormal(corners[quadrant], center, line_vector);
  296. const float length = Math::Absolute(dim.x * line_vector.x) + Math::Absolute(-dim.y * line_vector.y);
  297. return LinearGradientShape{starting_point, ending_point, length};
  298. }
  299. DecoratorLinearGradientInstancer::DecoratorLinearGradientInstancer()
  300. {
  301. ids.angle = RegisterProperty("angle", "180deg").AddParser("angle").GetId();
  302. ids.direction_to = RegisterProperty("to", "unspecified").AddParser("keyword", "unspecified, to").GetId();
  303. // See Direction enum for keyword values.
  304. ids.direction_x = RegisterProperty("direction-x", "unspecified").AddParser("keyword", "unspecified=0, left=8, right=2").GetId();
  305. ids.direction_y = RegisterProperty("direction-y", "unspecified").AddParser("keyword", "unspecified=0, top=1, bottom=4").GetId();
  306. ids.color_stop_list = RegisterProperty("color-stops", "").AddParser("color_stop_list").GetId();
  307. RegisterShorthand("direction", "angle, to, direction-x, direction-y, direction-x", ShorthandType::FallThrough);
  308. RegisterShorthand("decorator", "direction?, color-stops#", ShorthandType::RecursiveCommaSeparated);
  309. }
  310. DecoratorLinearGradientInstancer::~DecoratorLinearGradientInstancer() {}
  311. SharedPtr<Decorator> DecoratorLinearGradientInstancer::InstanceDecorator(const String& name, const PropertyDictionary& properties_,
  312. const DecoratorInstancerInterface& /*interface_*/)
  313. {
  314. const Property* p_angle = properties_.GetProperty(ids.angle);
  315. const Property* p_direction_to = properties_.GetProperty(ids.direction_to);
  316. const Property* p_direction_x = properties_.GetProperty(ids.direction_x);
  317. const Property* p_direction_y = properties_.GetProperty(ids.direction_y);
  318. const Property* p_color_stop_list = properties_.GetProperty(ids.color_stop_list);
  319. if (!p_angle || !p_direction_to || !p_direction_x || !p_direction_y || !p_color_stop_list)
  320. return nullptr;
  321. using Corner = DecoratorLinearGradient::Corner;
  322. Corner corner = Corner::None;
  323. float angle = 0.f;
  324. if (p_direction_to->Get<bool>())
  325. {
  326. const Direction direction = (Direction)(p_direction_x->Get<int>() | p_direction_y->Get<int>());
  327. switch (direction)
  328. {
  329. case Direction::Top: angle = 0.f; break;
  330. case Direction::Right: angle = 0.5f * Math::RMLUI_PI; break;
  331. case Direction::Bottom: angle = Math::RMLUI_PI; break;
  332. case Direction::Left: angle = 1.5f * Math::RMLUI_PI; break;
  333. case Direction::TopLeft: corner = Corner::TopLeft; break;
  334. case Direction::TopRight: corner = Corner::TopRight; break;
  335. case Direction::BottomRight: corner = Corner::BottomRight; break;
  336. case Direction::BottomLeft: corner = Corner::BottomLeft; break;
  337. case Direction::None:
  338. default: return nullptr; break;
  339. }
  340. }
  341. else
  342. {
  343. angle = ComputeAngle(p_angle->GetNumericValue());
  344. }
  345. if (p_color_stop_list->unit != Unit::COLORSTOPLIST)
  346. return nullptr;
  347. const ColorStopList& color_stop_list = p_color_stop_list->value.GetReference<ColorStopList>();
  348. const bool repeating = (name == "repeating-linear-gradient");
  349. auto decorator = MakeShared<DecoratorLinearGradient>();
  350. if (decorator->Initialise(repeating, corner, angle, color_stop_list))
  351. return decorator;
  352. return nullptr;
  353. }
  354. } // namespace Rml