/* * This source file is part of RmlUi, the HTML/CSS Interface Middleware * * For the latest information, see http://github.com/mikke89/RmlUi * * Copyright (c) 2018 Michael R. P. Ragazzon * 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 "PropertyParserAnimation.h" #include "../../Include/RmlUi/Core/PropertyDefinition.h" #include "../../Include/RmlUi/Core/PropertyIdSet.h" #include "../../Include/RmlUi/Core/StringUtilities.h" #include "../../Include/RmlUi/Core/StyleSheetSpecification.h" #include "PropertyShorthandDefinition.h" namespace Rml { enum class KeywordType { None, Tween, All, Alternate, Infinite, Paused }; struct Keyword { KeywordType type; Tween tween; Keyword(Tween tween) : type(KeywordType::Tween), tween(tween) {} Keyword(KeywordType type) : type(type) {} bool ValidTransition() const { return type == KeywordType::None || type == KeywordType::Tween || type == KeywordType::All; } bool ValidAnimation() const { return type == KeywordType::None || type == KeywordType::Tween || type == KeywordType::Alternate || type == KeywordType::Infinite || type == KeywordType::Paused; } }; struct PropertyParserAnimationData { const UnorderedMap keywords = { {"none", {KeywordType::None}}, {"all", {KeywordType::All}}, {"alternate", {KeywordType::Alternate}}, {"infinite", {KeywordType::Infinite}}, {"paused", {KeywordType::Paused}}, {"back-in", {Tween{Tween::Back, Tween::In}}}, {"back-out", {Tween{Tween::Back, Tween::Out}}}, {"back-in-out", {Tween{Tween::Back, Tween::InOut}}}, {"bounce-in", {Tween{Tween::Bounce, Tween::In}}}, {"bounce-out", {Tween{Tween::Bounce, Tween::Out}}}, {"bounce-in-out", {Tween{Tween::Bounce, Tween::InOut}}}, {"circular-in", {Tween{Tween::Circular, Tween::In}}}, {"circular-out", {Tween{Tween::Circular, Tween::Out}}}, {"circular-in-out", {Tween{Tween::Circular, Tween::InOut}}}, {"cubic-in", {Tween{Tween::Cubic, Tween::In}}}, {"cubic-out", {Tween{Tween::Cubic, Tween::Out}}}, {"cubic-in-out", {Tween{Tween::Cubic, Tween::InOut}}}, {"elastic-in", {Tween{Tween::Elastic, Tween::In}}}, {"elastic-out", {Tween{Tween::Elastic, Tween::Out}}}, {"elastic-in-out", {Tween{Tween::Elastic, Tween::InOut}}}, {"exponential-in", {Tween{Tween::Exponential, Tween::In}}}, {"exponential-out", {Tween{Tween::Exponential, Tween::Out}}}, {"exponential-in-out", {Tween{Tween::Exponential, Tween::InOut}}}, {"linear-in", {Tween{Tween::Linear, Tween::In}}}, {"linear-out", {Tween{Tween::Linear, Tween::Out}}}, {"linear-in-out", {Tween{Tween::Linear, Tween::InOut}}}, {"quadratic-in", {Tween{Tween::Quadratic, Tween::In}}}, {"quadratic-out", {Tween{Tween::Quadratic, Tween::Out}}}, {"quadratic-in-out", {Tween{Tween::Quadratic, Tween::InOut}}}, {"quartic-in", {Tween{Tween::Quartic, Tween::In}}}, {"quartic-out", {Tween{Tween::Quartic, Tween::Out}}}, {"quartic-in-out", {Tween{Tween::Quartic, Tween::InOut}}}, {"quintic-in", {Tween{Tween::Quintic, Tween::In}}}, {"quintic-out", {Tween{Tween::Quintic, Tween::Out}}}, {"quintic-in-out", {Tween{Tween::Quintic, Tween::InOut}}}, {"sine-in", {Tween{Tween::Sine, Tween::In}}}, {"sine-out", {Tween{Tween::Sine, Tween::Out}}}, {"sine-in-out", {Tween{Tween::Sine, Tween::InOut}}}, }; }; ControlledLifetimeResource PropertyParserAnimation::parser_data; void PropertyParserAnimation::Initialize() { parser_data.Initialize(); } void PropertyParserAnimation::Shutdown() { parser_data.Shutdown(); } PropertyParserAnimation::PropertyParserAnimation(Type type) : type(type) {} bool PropertyParserAnimation::ParseValue(Property& property, const String& value, const ParameterMap& /*parameters*/) const { StringList list_of_values; { auto lowercase_value = StringUtilities::ToLower(value); StringUtilities::ExpandString(list_of_values, lowercase_value, ','); } bool result = false; if (type == ANIMATION_PARSER) { result = ParseAnimation(property, list_of_values); } else if (type == TRANSITION_PARSER) { result = ParseTransition(property, list_of_values); } return result; } bool PropertyParserAnimation::ParseAnimation(Property& property, const StringList& animation_values) { AnimationList animation_list; for (const String& single_animation_value : animation_values) { Animation animation; StringList arguments; StringUtilities::ExpandString(arguments, single_animation_value, ' '); bool duration_found = false; bool delay_found = false; bool num_iterations_found = false; for (auto& argument : arguments) { if (argument.empty()) continue; // See if we have a or specifier as defined in keywords auto it = parser_data->keywords.find(argument); if (it != parser_data->keywords.end() && it->second.ValidAnimation()) { switch (it->second.type) { case KeywordType::None: { if (animation_list.size() > 0) // The none keyword can not be part of multiple definitions return false; property = Property{AnimationList{}, Unit::ANIMATION}; return true; } break; case KeywordType::Tween: animation.tween = it->second.tween; break; case KeywordType::Alternate: animation.alternate = true; break; case KeywordType::Infinite: if (num_iterations_found) return false; animation.num_iterations = -1; num_iterations_found = true; break; case KeywordType::Paused: animation.paused = true; break; default: break; } } else { // Either , , or a float number = 0.0f; int count = 0; if (sscanf(argument.c_str(), "%fs%n", &number, &count) == 1) { // Found a number, if there was an 's' unit, count will be positive if (count > 0) { // Duration or delay was assigned if (!duration_found) { duration_found = true; animation.duration = number; } else if (!delay_found) { delay_found = true; animation.delay = number; } else return false; } else { // No 's' unit means num_iterations was found if (!num_iterations_found) { animation.num_iterations = Math::RoundToInteger(number); num_iterations_found = true; } else return false; } } else { // Must be an animation name animation.name = argument; } } } // Validate the parsed transition if (animation.name.empty() || animation.duration <= 0.0f || (animation.num_iterations < -1 || animation.num_iterations == 0)) { return false; } animation_list.push_back(std::move(animation)); } property.value = std::move(animation_list); property.unit = Unit::ANIMATION; return true; } bool PropertyParserAnimation::ParseTransition(Property& property, const StringList& transition_values) { TransitionList transition_list{false, false, {}}; for (const String& single_transition_value : transition_values) { Transition transition; PropertyIdSet target_property_ids; StringList arguments; StringUtilities::ExpandString(arguments, single_transition_value, ' '); bool duration_found = false; bool delay_found = false; bool reverse_adjustment_factor_found = false; for (auto& argument : arguments) { if (argument.empty()) continue; // See if we have a or specifier as defined in keywords auto it = parser_data->keywords.find(argument); if (it != parser_data->keywords.end() && it->second.ValidTransition()) { if (it->second.type == KeywordType::None) { if (transition_list.transitions.size() > 0) // The none keyword can not be part of multiple definitions return false; property = Property{TransitionList{true, false, {}}, Unit::TRANSITION}; return true; } else if (it->second.type == KeywordType::All) { if (transition_list.transitions.size() > 0) // The all keyword can not be part of multiple definitions return false; transition_list.all = true; } else if (it->second.type == KeywordType::Tween) { transition.tween = it->second.tween; } } else { // Either , or a float number = 0.0f; int count = 0; if (sscanf(argument.c_str(), "%fs%n", &number, &count) == 1) { // Found a number, if there was an 's' unit, count will be positive if (count > 0) { // Duration or delay was assigned if (!duration_found) { duration_found = true; transition.duration = number; } else if (!delay_found) { delay_found = true; transition.delay = number; } else return false; } else { // No 's' unit means reverse adjustment factor was found if (!reverse_adjustment_factor_found) { reverse_adjustment_factor_found = true; transition.reverse_adjustment_factor = number; } else return false; } } else { // Must be a property name or shorthand, expand now if (auto shorthand = StyleSheetSpecification::GetShorthand(argument)) { PropertyIdSet underlying_properties = StyleSheetSpecification::GetShorthandUnderlyingProperties(shorthand->id); target_property_ids |= underlying_properties; } else if (auto definition = StyleSheetSpecification::GetProperty(argument)) { // Single property target_property_ids.Insert(definition->GetId()); } else { // Unknown property name return false; } } } } // Validate the parsed transition if ((transition_list.all && !target_property_ids.Empty()) // || (!transition_list.all && target_property_ids.Empty()) // || transition.duration <= 0.0f // || transition.reverse_adjustment_factor < 0.0f // || transition.reverse_adjustment_factor > 1.0f // ) { return false; } if (transition_list.all) { transition.id = PropertyId::Invalid; transition_list.transitions.push_back(transition); } else { for (const PropertyId id : target_property_ids) { transition.id = id; transition_list.transitions.push_back(transition); } } } property.value = std::move(transition_list); property.unit = Unit::TRANSITION; return true; } } // namespace Rml