diff options
-rw-r--r-- | include/llmr/style/properties.hpp | 59 | ||||
-rw-r--r-- | include/llmr/style/style.hpp | 51 | ||||
-rw-r--r-- | include/llmr/style/style_parser.hpp | 1 | ||||
-rw-r--r-- | include/llmr/util/transition.hpp | 31 | ||||
-rw-r--r-- | src/map/map.cpp | 16 | ||||
-rw-r--r-- | src/map/transform.cpp | 12 | ||||
-rw-r--r-- | src/style/style.cpp | 970 | ||||
-rw-r--r-- | src/style/style_parser.cpp | 99 | ||||
-rw-r--r-- | src/util/raster.cpp | 2 | ||||
-rw-r--r-- | src/util/transition.cpp | 63 |
10 files changed, 1189 insertions, 115 deletions
diff --git a/include/llmr/style/properties.hpp b/include/llmr/style/properties.hpp index 04f1819b36..37c26b4700 100644 --- a/include/llmr/style/properties.hpp +++ b/include/llmr/style/properties.hpp @@ -5,12 +5,34 @@ #include <vector> #include <string> #include <limits> +#include <set> namespace llmr { // Stores a premultiplied color, with all four channels ranging from 0..1 typedef std::array<float, 4> Color; +struct PropertyTransition { + uint16_t duration = 0; + uint16_t delay = 0; +}; + +enum class TransitionablePropertyKey { + Translate = 1, + FillColor = 2, + StrokeColor = 3, + Opacity = 4, + Width = 5, + Offset = 6, + Color = 7, + DashArray = 8, + Radius = 9, + Blur = 10, + Halo = 11, + HaloRadius = 12, + HaloBlur = 13, +}; + enum class Winding { EvenOdd = 1, NonZero = 2 @@ -63,13 +85,19 @@ struct FunctionProperty { struct IconClass { FunctionProperty enabled = true; std::array<FunctionProperty, 2> translate = {{ 0, 0 }}; + PropertyTransition translate_transition; TranslateAnchor translateAnchor = TranslateAnchor::Map; FunctionProperty size; Color color = {{ 1, 1, 1, 1 }}; + PropertyTransition color_transition; FunctionProperty opacity = 1; + PropertyTransition opacity_transition; std::string image; FunctionProperty radius = 0; + PropertyTransition radius_transition; FunctionProperty blur = 0; + PropertyTransition blur_transition; + std::set<std::string> specifiers; }; struct IconProperties { @@ -87,12 +115,19 @@ struct IconProperties { struct LineClass { FunctionProperty enabled = true; std::array<FunctionProperty, 2> translate = {{ 0, 0 }}; + PropertyTransition translate_transition; TranslateAnchor translateAnchor = TranslateAnchor::Map; FunctionProperty width; + PropertyTransition width_transition; FunctionProperty offset; + PropertyTransition offset_transition; Color color = {{ 0, 0, 0, 1 }}; + PropertyTransition color_transition; std::array<FunctionProperty, 2> dash_array = {{ 1, -1 }}; + PropertyTransition dash_array_transition; FunctionProperty opacity = 1; + PropertyTransition opacity_transition; + std::set<std::string> specifiers; }; struct LineProperties { @@ -109,13 +144,18 @@ struct LineProperties { struct FillClass { FunctionProperty enabled = true; std::array<FunctionProperty, 2> translate = {{ 0, 0 }}; + PropertyTransition translate_transition; TranslateAnchor translateAnchor = TranslateAnchor::Map; Winding winding = Winding::NonZero; FunctionProperty antialias = true; Color fill_color = {{ 0, 0, 0, 1 }}; + PropertyTransition fill_color_transition; Color stroke_color = {{ 0, 0, 0, std::numeric_limits<float>::infinity() }}; + PropertyTransition stroke_color_transition; FunctionProperty opacity = 1; + PropertyTransition opacity_transition; std::string image; + std::set<std::string> specifiers; }; struct FillProperties { @@ -133,15 +173,22 @@ struct FillProperties { struct TextClass { FunctionProperty enabled = true; std::array<FunctionProperty, 2> translate = {{ 0, 0 }}; + PropertyTransition translate_transition; TranslateAnchor translateAnchor = TranslateAnchor::Map; Color color = {{ 0, 0, 0, 1 }}; + PropertyTransition color_transition; Color halo = {{ 1, 1, 1, 0.75 }}; + PropertyTransition halo_transition; FunctionProperty halo_radius = 0.25f; + PropertyTransition halo_radius_transition; FunctionProperty halo_blur = 1.0f; + PropertyTransition halo_blur_transition; FunctionProperty size = 12.0f; FunctionProperty rotate = 0.0f; - FunctionProperty alwaysVisible = false; + FunctionProperty always_visible = false; FunctionProperty opacity = 1; + PropertyTransition opacity_transition; + std::set<std::string> specifiers; }; struct TextProperties { @@ -154,13 +201,16 @@ struct TextProperties { float halo_blur = 1.0f; float size = 12.0f; float rotate = 0.0f; - bool alwaysVisible = false; + bool always_visible = false; float opacity = 1.0; }; struct BackgroundClass { Color color = {{ 1, 1, 1, 1 }}; + PropertyTransition color_transition; FunctionProperty opacity = 1; + PropertyTransition opacity_transition; + std::set<std::string> specifiers; }; struct BackgroundProperties { @@ -171,7 +221,10 @@ struct BackgroundProperties { struct RasterClass { FunctionProperty enabled = true; std::array<FunctionProperty, 2> translate = {{ 0, 0 }}; + PropertyTransition translate_transition; FunctionProperty opacity = 1; + PropertyTransition opacity_transition; + std::set<std::string> specifiers; }; struct RasterProperties { @@ -183,6 +236,8 @@ struct RasterProperties { struct CompositeClass { FunctionProperty enabled = true; FunctionProperty opacity = 1; + PropertyTransition opacity_transition; + std::set<std::string> specifiers; }; struct CompositeProperties { diff --git a/include/llmr/style/style.hpp b/include/llmr/style/style.hpp index 920d0c6c1c..256ff4d6be 100644 --- a/include/llmr/style/style.hpp +++ b/include/llmr/style/style.hpp @@ -8,6 +8,8 @@ #include <llmr/style/layer_description.hpp> #include <llmr/style/class_description.hpp> #include <llmr/geometry/sprite_atlas.hpp> +#include <llmr/util/transition.hpp> +#include <llmr/util/uv.hpp> #include <map> #include <vector> @@ -32,6 +34,12 @@ public: size_t layerCount() const; void cascade(float z); + bool needsTransition() const; + void updateTransitions(time now); + void cancelTransitions(); + + void setDefaultTransitionDuration(uint64_t duration = 0); + public: std::shared_ptr<Sprite> sprite; @@ -40,8 +48,7 @@ public: std::vector<LayerDescription> layers; std::map<std::string, ClassDescription> classes; - - // This are applied settings. + // Currently applied settings. std::set<std::string> appliedClasses; struct { BackgroundProperties background; @@ -51,7 +58,47 @@ public: std::map<std::string, TextProperties> texts; std::map<std::string, RasterProperties> rasters; std::map<std::string, CompositeProperties> composites; + std::map<std::string, std::map<TransitionablePropertyKey, std::string>> effective_classes; } computed; + +private: + bool transitionInProgress(std::string layer_name, TransitionablePropertyKey key); + bool transitionExists(std::string layer_name, TransitionablePropertyKey key); + bool inNeedOfTransition(std::string layer_name, TransitionablePropertyKey key); + uint64_t transitionDuration(std::string layer_name, TransitionablePropertyKey key); + uint64_t transitionDelay(std::string layer_name, TransitionablePropertyKey key); + +private: + // Last applied settings. + struct { + BackgroundProperties background; + std::map<std::string, FillProperties> fills; + std::map<std::string, LineProperties> lines; + std::map<std::string, IconProperties> icons; + std::map<std::string, TextProperties> texts; + std::map<std::string, RasterProperties> rasters; + std::map<std::string, CompositeProperties> composites; + std::map<std::string, std::map<TransitionablePropertyKey, std::string>> effective_classes; + } previous; + + // Settings values currently being transitioned. + struct { + BackgroundProperties background; + std::map<std::string, FillProperties> fills; + std::map<std::string, LineProperties> lines; + std::map<std::string, IconProperties> icons; + std::map<std::string, TextProperties> texts; + std::map<std::string, RasterProperties> rasters; + std::map<std::string, CompositeProperties> composites; + } transitioning; + + std::map<std::string, std::map<TransitionablePropertyKey, PropertyTransition>> properties_to_transition; + std::map<std::string, std::map<TransitionablePropertyKey, std::shared_ptr<util::transition>>> transitions; + uint64_t default_transition_duration = 0; + bool initial_render_complete = false; + + mutable uv::rwlock mtx; + }; } diff --git a/include/llmr/style/style_parser.hpp b/include/llmr/style/style_parser.hpp index 82dd7e9b59..6668048ff8 100644 --- a/include/llmr/style/style_parser.hpp +++ b/include/llmr/style/style_parser.hpp @@ -46,6 +46,7 @@ private: Value parseValue(JSVal value); FunctionProperty::fn parseFunctionType(JSVal type); FunctionProperty parseFunction(JSVal value); + PropertyTransition parseTransition(JSVal value, std::string property_name); private: std::map<std::string, const rapidjson::Value *> constants; diff --git a/include/llmr/util/transition.hpp b/include/llmr/util/transition.hpp index cab73aab58..a981f6e83d 100644 --- a/include/llmr/util/transition.hpp +++ b/include/llmr/util/transition.hpp @@ -3,6 +3,7 @@ #include <llmr/util/noncopyable.hpp> #include <llmr/util/time.hpp> +#include <llmr/style/properties.hpp> namespace llmr { namespace util { @@ -19,6 +20,9 @@ public: duration(duration) {} inline float progress(time now) const { + if (duration == 0) return 1; + if (start > now) return 0; + return (float)(now - start) / duration; } @@ -26,32 +30,35 @@ public: virtual ~transition(); protected: + double interpolateDouble(double from, double to, double t) const; + float interpolateFloat(float from, float to, double t) const; + Color interpolateColor(Color from, Color to, double t) const; + std::array<float, 2> interpolateFloatArray(std::array<float, 2> from, std::array<float, 2> to, double t) const; + +protected: const time start, duration; }; +template <typename T> class ease_transition : public transition { public: - // Disable automatic casts. - template <typename T1, typename T2> - inline ease_transition(double from, double to, double& value, T1 start, T2 duration) = delete; + ease_transition(T from, T to, T& value, time start, time duration) + : transition(start, duration), + from(from), + to(to), + value(value) {} - // Actual constructor. - ease_transition(double from, double to, double& value, time start, time duration); state update(time now) const; private: - const double from, to; - double& value; + const T from, to; + T& value; + }; template <typename T> class timeout : public transition { public: - // Disable automatic casts. - template <typename T1, typename T2> - inline timeout(T final_value, T& value, T1 start, T2 duration) = delete; - - // Actual constructor. timeout(T final_value, T& value, time start, time duration) : transition(start, duration), final_value(final_value), diff --git a/src/map/map.cpp b/src/map/map.cpp index 9df6ecb4b8..d70821380b 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -374,17 +374,15 @@ bool Map::getDebug() const { } void Map::toggleRaster() { + style.setDefaultTransitionDuration(300); + style.cancelTransitions(); + auto it = sources.find("satellite"); if (it != sources.end()) { Source &satellite_source = *it->second; - if (satellite_source.enabled) { satellite_source.enabled = false; - - auto style_class = style.appliedClasses.find("satellite"); - if (style_class != style.appliedClasses.end()) { - style.appliedClasses.erase(style_class); - } + style.appliedClasses.erase("satellite"); } else { satellite_source.enabled = true; style.appliedClasses.insert("satellite"); @@ -458,6 +456,12 @@ void Map::prepare() { style.cascade(state.getNormalizedZoom()); } + animationTime = util::now(); + if (style.needsTransition()) { + style.updateTransitions(animationTime); + update(); + } + // Allow the sprite atlas to potentially pull new sprite images if needed. if (style.sprite && style.sprite->isLoaded()) { spriteAtlas.update(*style.sprite); diff --git a/src/map/transform.cpp b/src/map/transform.cpp index fb3458adef..93942edadd 100644 --- a/src/map/transform.cpp +++ b/src/map/transform.cpp @@ -59,9 +59,9 @@ void Transform::_moveBy(const double dx, const double dy, const time duration) { // Use a common start time for all of the transitions to avoid divergent transitions. time start = util::now(); transitions.emplace_front( - std::make_shared<util::ease_transition>(current.x, final.x, current.x, start, duration)); + std::make_shared<util::ease_transition<double>>(current.x, final.x, current.x, start, duration)); transitions.emplace_front( - std::make_shared<util::ease_transition>(current.y, final.y, current.y, start, duration)); + std::make_shared<util::ease_transition<double>>(current.y, final.y, current.y, start, duration)); } } @@ -250,12 +250,12 @@ void Transform::_setScaleXY(const double new_scale, const double xn, const doubl } else { // Use a common start time for all of the transitions to avoid divergent transitions. time start = util::now(); - transitions.emplace_front(std::make_shared<util::ease_transition>( + transitions.emplace_front(std::make_shared<util::ease_transition<double>>( current.scale, final.scale, current.scale, start, duration)); transitions.emplace_front( - std::make_shared<util::ease_transition>(current.x, final.x, current.x, start, duration)); + std::make_shared<util::ease_transition<double>>(current.x, final.x, current.x, start, duration)); transitions.emplace_front( - std::make_shared<util::ease_transition>(current.y, final.y, current.y, start, duration)); + std::make_shared<util::ease_transition<double>>(current.y, final.y, current.y, start, duration)); } const double s = final.scale * util::tileSize; @@ -335,7 +335,7 @@ void Transform::_setAngle(double new_angle, const time duration) { current.angle = final.angle; } else { time start = util::now(); - transitions.emplace_front(std::make_shared<util::ease_transition>( + transitions.emplace_front(std::make_shared<util::ease_transition<double>>( current.angle, final.angle, current.angle, start, duration)); } } diff --git a/src/style/style.cpp b/src/style/style.cpp index 54528f5271..b39a0bf5d5 100644 --- a/src/style/style.cpp +++ b/src/style/style.cpp @@ -19,135 +19,945 @@ void Style::reset() { computed.texts.clear(); computed.rasters.clear(); computed.composites.clear(); + computed.background.color = {{ 1, 1, 1, 1 }}; + computed.background.opacity = 1.0; + + properties_to_transition.clear(); } void Style::cascade(float z) { + uv::writelock lock(mtx); + + time start = util::now(); + + previous.fills = computed.fills; + previous.lines = computed.lines; + previous.icons = computed.icons; + previous.texts = computed.texts; + previous.rasters = computed.rasters; + previous.background = computed.background; + + previous.effective_classes = computed.effective_classes; + reset(); // Accomodate for different tile size. // TODO: Make this per-layer once individual layers have a specific tile size. z += std::log(util::tileSize / 256.0f) / M_LN2; - // Recalculate style - // Basic cascading + // Recalculate style with basic cascading. Also store the last applied class + // for each property to assist in determining transitions. for (const auto& class_pair : classes) { const std::string& class_name = class_pair.first; const ClassDescription& sheetClass = class_pair.second; - // Not enabled + // Skip if not enabled. if (appliedClasses.find(class_name) == appliedClasses.end()) continue; - // Cascade fill classes + // Cascade fill classes. for (const auto& fill_pair : sheetClass.fill) { const std::string& layer_name = fill_pair.first; const llmr::FillClass& layer = fill_pair.second; - // TODO: This should be restricted to fill styles that have actual - // values so as to not override with default values. llmr::FillProperties& fill = computed.fills[layer_name]; - fill.enabled = layer.enabled.evaluate<bool>(z); - fill.translate = {{ layer.translate[0].evaluate<float>(z), - layer.translate[1].evaluate<float>(z) }}; - fill.translateAnchor = layer.translateAnchor; - fill.winding = layer.winding; - fill.antialias = layer.antialias.evaluate<bool>(z); - fill.fill_color = layer.fill_color; - fill.stroke_color = layer.stroke_color; - fill.opacity = layer.opacity.evaluate<float>(z); - fill.image = layer.image; - } - - // Cascade line classes + + if (layer.specifiers.count("enabled")) { + fill.enabled = layer.enabled.evaluate<bool>(z); + } + + if (layer.specifiers.count("translate")) { + fill.translate = {{ layer.translate[0].evaluate<float>(z), + layer.translate[1].evaluate<float>(z) }}; + computed.effective_classes[layer_name][TransitionablePropertyKey::Translate] = class_name; + if (layer.translate_transition.duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Translate] = layer.translate_transition; + } + } + + if (layer.specifiers.count("translate-anchor")) { + fill.translateAnchor = layer.translateAnchor; + } + + if (layer.specifiers.count("winding")) { + fill.winding = layer.winding; + } + + if (layer.specifiers.count("antialias")) { + fill.antialias = layer.antialias.evaluate<bool>(z); + } + + if (layer.specifiers.count("color")) { + fill.fill_color = layer.fill_color; + computed.effective_classes[layer_name][TransitionablePropertyKey::FillColor] = class_name; + if (layer.fill_color_transition.duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::FillColor] = layer.fill_color_transition; + } + } + + if (layer.specifiers.count("stroke")) { + fill.stroke_color = layer.stroke_color; + computed.effective_classes[layer_name][TransitionablePropertyKey::StrokeColor] = class_name; + if (layer.stroke_color_transition.duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::StrokeColor] = layer.stroke_color_transition; + } + } + + if (layer.specifiers.count("opacity")) { + fill.opacity = layer.opacity.evaluate<float>(z); + computed.effective_classes[layer_name][TransitionablePropertyKey::Opacity] = class_name; + if (layer.opacity_transition.duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Opacity] = layer.opacity_transition; + } + } + + if (layer.specifiers.count("image")) { + fill.image = layer.image; + } + } + + // Cascade line classes. for (const auto& line_pair : sheetClass.line) { const std::string& layer_name = line_pair.first; const llmr::LineClass& layer = line_pair.second; - // TODO: This should be restricted to line styles that have actual - // values so as to not override with default values. llmr::LineProperties& stroke = computed.lines[layer_name]; - stroke.enabled = layer.enabled.evaluate<bool>(z); - stroke.translate = {{ layer.translate[0].evaluate<float>(z), - layer.translate[1].evaluate<float>(z) }}; - stroke.translateAnchor = layer.translateAnchor; - stroke.width = layer.width.evaluate<float>(z); - stroke.offset = layer.offset.evaluate<float>(z); - stroke.color = layer.color; - stroke.dash_array = {{ layer.dash_array[0].evaluate<float>(z), - layer.dash_array[1].evaluate<float>(z) }}; - stroke.opacity = layer.opacity.evaluate<float>(z); - } - - // Cascade icon classes + + if (layer.specifiers.count("enabled")) { + stroke.enabled = layer.enabled.evaluate<bool>(z); + } + + if (layer.specifiers.count("translate")) { + stroke.translate = {{ layer.translate[0].evaluate<float>(z), + layer.translate[1].evaluate<float>(z) }}; + computed.effective_classes[layer_name][TransitionablePropertyKey::Translate] = class_name; + if (layer.translate_transition.duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Translate] = layer.translate_transition; + } + } + + if (layer.specifiers.count("translate-anchor")) { + stroke.translateAnchor = layer.translateAnchor; + } + + if (layer.specifiers.count("width")) { + stroke.width = layer.width.evaluate<float>(z); + computed.effective_classes[layer_name][TransitionablePropertyKey::Width] = class_name; + if (layer.width_transition.duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Width] = layer.width_transition; + } + + } + + if (layer.specifiers.count("offset")) { + stroke.offset = layer.offset.evaluate<float>(z); + computed.effective_classes[layer_name][TransitionablePropertyKey::Offset] = class_name; + if (layer.offset_transition.duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Offset] = layer.offset_transition; + } + } + + if (layer.specifiers.count("color")) { + stroke.color = layer.color; + computed.effective_classes[layer_name][TransitionablePropertyKey::Color] = class_name; + if (layer.color_transition.duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Color] = layer.color_transition; + } + } + + if (layer.specifiers.count("dasharray")) { + stroke.dash_array = {{ layer.dash_array[0].evaluate<float>(z), + layer.dash_array[1].evaluate<float>(z) }}; + computed.effective_classes[layer_name][TransitionablePropertyKey::DashArray] = class_name; + if (layer.dash_array_transition.duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::DashArray] = layer.dash_array_transition; + } + } + + if (layer.specifiers.count("opacity")) { + stroke.opacity = layer.opacity.evaluate<float>(z); + computed.effective_classes[layer_name][TransitionablePropertyKey::Opacity] = class_name; + if (layer.opacity_transition.duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Opacity] = layer.opacity_transition; + } + } + } + + // Cascade icon classes. for (const auto& icon_pair : sheetClass.icon) { const std::string& layer_name = icon_pair.first; const llmr::IconClass& layer = icon_pair.second; - // TODO: This should be restricted to icon styles that have actual - // values so as to not override with default values. llmr::IconProperties& icon = computed.icons[layer_name]; - icon.enabled = layer.enabled.evaluate<bool>(z); - icon.translate = {{ layer.translate[0].evaluate<float>(z), - layer.translate[1].evaluate<float>(z) }}; - icon.translateAnchor = layer.translateAnchor; - icon.color = layer.color; - icon.size = layer.size.evaluate<float>(z); - icon.opacity = layer.opacity.evaluate<float>(z); - icon.image = layer.image; - icon.radius = layer.radius.evaluate<float>(z); - icon.blur = layer.blur.evaluate<float>(z); - } - - // Cascade text classes + + if (layer.specifiers.count("enabled")) { + icon.enabled = layer.enabled.evaluate<bool>(z); + } + + if (layer.specifiers.count("translate")) { + icon.translate = {{ layer.translate[0].evaluate<float>(z), + layer.translate[1].evaluate<float>(z) }}; + computed.effective_classes[layer_name][TransitionablePropertyKey::Translate] = class_name; + if (layer.translate_transition.duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Translate] = layer.translate_transition; + } + } + + if (layer.specifiers.count("translate-anchor")) { + icon.translateAnchor = layer.translateAnchor; + } + + if (layer.specifiers.count("color")) { + icon.color = layer.color; + computed.effective_classes[layer_name][TransitionablePropertyKey::Color] = class_name; + if (layer.color_transition.duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Color] = layer.color_transition; + } + } + + if (layer.specifiers.count("size")) { + icon.size = layer.size.evaluate<float>(z); + } + + if (layer.specifiers.count("opacity")) { + icon.opacity = layer.opacity.evaluate<float>(z); + computed.effective_classes[layer_name][TransitionablePropertyKey::Opacity] = class_name; + if (layer.opacity_transition.duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Opacity] = layer.opacity_transition; + } + } + + if (layer.specifiers.count("image")) { + icon.image = layer.image; + } + + if (layer.specifiers.count("radius")) { + icon.radius = layer.radius.evaluate<float>(z); + computed.effective_classes[layer_name][TransitionablePropertyKey::Radius] = class_name; + if (layer.radius_transition.duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Radius] = layer.radius_transition; + } + } + + if (layer.specifiers.count("blur")) { + icon.blur = layer.blur.evaluate<float>(z); + computed.effective_classes[layer_name][TransitionablePropertyKey::Blur] = class_name; + if (layer.blur_transition.duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Blur] = layer.blur_transition; + } + } + } + + // Cascade text classes. for (const auto& text_pair : sheetClass.text) { const std::string& layer_name = text_pair.first; const llmr::TextClass& layer = text_pair.second; - // TODO: This should be restricted to text styles that have actual - // values so as to not override with default values. llmr::TextProperties& text = computed.texts[layer_name]; - text.enabled = layer.enabled.evaluate<bool>(z); - text.translate = {{ layer.translate[0].evaluate<float>(z), - layer.translate[1].evaluate<float>(z) }}; - text.translateAnchor = layer.translateAnchor; - text.color = layer.color; - text.size = layer.size.evaluate<float>(z); - text.halo = layer.halo; - text.halo_radius = layer.halo_radius.evaluate<float>(z); - text.halo_blur = layer.halo_blur.evaluate<float>(z); - text.rotate = layer.rotate.evaluate<float>(z); - text.alwaysVisible = layer.alwaysVisible.evaluate<bool>(z); - text.opacity = layer.opacity.evaluate<float>(z); - } - - // Cascade raster classes + + if (layer.specifiers.count("enabled")) { + text.enabled = layer.enabled.evaluate<bool>(z); + } + + if (layer.specifiers.count("translate")) { + text.translate = {{ layer.translate[0].evaluate<float>(z), + layer.translate[1].evaluate<float>(z) }}; + computed.effective_classes[layer_name][TransitionablePropertyKey::Translate] = class_name; + if (layer.translate_transition.duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Translate] = layer.translate_transition; + } + } + + if (layer.specifiers.count("translate-anchor")) { + text.translateAnchor = layer.translateAnchor; + } + + if (layer.specifiers.count("color")) { + text.color = layer.color; + computed.effective_classes[layer_name][TransitionablePropertyKey::Color] = class_name; + if (layer.color_transition.duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Color] = layer.color_transition; + } + } + + if (layer.specifiers.count("size")) { + text.size = layer.size.evaluate<float>(z); + } + + if (layer.specifiers.count("stroke")) { + text.halo = layer.halo; + computed.effective_classes[layer_name][TransitionablePropertyKey::Halo] = class_name; + if (layer.halo_transition.duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Halo] = layer.halo_transition; + } + } + + if (layer.specifiers.count("strokeWidth")) { + text.halo_radius = layer.halo_radius.evaluate<float>(z); + computed.effective_classes[layer_name][TransitionablePropertyKey::HaloRadius] = class_name; + if (layer.halo_radius_transition.duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::HaloRadius] = layer.halo_radius_transition; + } + } + + if (layer.specifiers.count("strokeBlur")) { + text.halo_blur = layer.halo_blur.evaluate<float>(z); + computed.effective_classes[layer_name][TransitionablePropertyKey::HaloBlur] = class_name; + if (layer.halo_blur_transition.duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::HaloBlur] = layer.halo_blur_transition; + } + } + + if (layer.specifiers.count("rotate")) { + text.rotate = layer.rotate.evaluate<float>(z); + } + + if (layer.specifiers.count("alwaysVisible")) { + text.always_visible = layer.always_visible.evaluate<bool>(z); + } + + if (layer.specifiers.count("opacity")) { + text.opacity = layer.opacity.evaluate<float>(z); + computed.effective_classes[layer_name][TransitionablePropertyKey::Opacity] = class_name; + if (layer.opacity_transition.duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Opacity] = layer.opacity_transition; + } + } + } + + // Cascade raster classes. for (const auto& raster_pair : sheetClass.raster) { const std::string& layer_name = raster_pair.first; const llmr::RasterClass& layer = raster_pair.second; - // TODO: This should be restricted to raster styles that have actual - // values so as to not override with default values. llmr::RasterProperties& raster = computed.rasters[layer_name]; - raster.enabled = layer.enabled.evaluate<bool>(z); - raster.opacity = layer.opacity.evaluate<float>(z); + + if (layer.specifiers.count("enabled")) { + raster.enabled = layer.enabled.evaluate<bool>(z); + } + + if (layer.specifiers.count("opacity")) { + raster.opacity = layer.opacity.evaluate<float>(z); + computed.effective_classes[layer_name][TransitionablePropertyKey::Opacity] = class_name; + if (layer.opacity_transition.duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Opacity] = layer.opacity_transition; + } + } } - // Cascade composite classes + // Cascade composite classes. for (const auto& composite_pair : sheetClass.composite) { const std::string& layer_name = composite_pair.first; const llmr::CompositeClass& layer = composite_pair.second; - // TODO: This should be restricted to composite styles that have actual - // values so as to not override with default values. llmr::CompositeProperties& composite = computed.composites[layer_name]; - composite.enabled = layer.enabled.evaluate<bool>(z); - composite.opacity = layer.opacity.evaluate<float>(z); + + if (layer.specifiers.count("enabled")) { + composite.enabled = layer.enabled.evaluate<bool>(z); + } + + if (layer.specifiers.count("opacity")) { + composite.opacity = layer.opacity.evaluate<float>(z); + computed.effective_classes[layer_name][TransitionablePropertyKey::Opacity] = class_name; + if (layer.opacity_transition.duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Opacity] = layer.opacity_transition; + } + } + } + + // Cascade background. + { + if (sheetClass.background.specifiers.count("color")) { + computed.background.color = sheetClass.background.color; + computed.effective_classes["background"][TransitionablePropertyKey::Color] = class_name; + if (sheetClass.background.color_transition.duration) { + properties_to_transition["background"][TransitionablePropertyKey::Color] = sheetClass.background.color_transition; + } + } + if (sheetClass.background.specifiers.count("opacity")) { + computed.background.opacity = sheetClass.background.opacity.evaluate<float>(z); + computed.effective_classes["background"][TransitionablePropertyKey::Opacity] = class_name; + if (sheetClass.background.opacity_transition.duration) { + properties_to_transition["background"][TransitionablePropertyKey::Opacity] = sheetClass.background.opacity_transition; + } + } + } + } + + // Apply transitions after the first time. + if (!initial_render_complete) { + initial_render_complete = true; + return; + } + + // Fills + for (const auto& fill_pair : computed.fills) { + const std::string& layer_name = fill_pair.first; + + // translate + if (transitionInProgress(layer_name, TransitionablePropertyKey::Translate)) { + + computed.fills[layer_name].translate = transitioning.fills[layer_name].translate; + + } else if (inNeedOfTransition(layer_name, TransitionablePropertyKey::Translate)) { + + transitioning.fills[layer_name].translate = previous.fills[layer_name].translate; + + transitions[layer_name][TransitionablePropertyKey::Translate] = + std::make_shared<util::ease_transition<std::array<float, 2>>>(previous.fills[layer_name].translate, + computed.fills[layer_name].translate, + transitioning.fills[layer_name].translate, + start + transitionDelay(layer_name, TransitionablePropertyKey::Translate), + transitionDuration(layer_name, TransitionablePropertyKey::Translate)); + + computed.fills[layer_name].translate = transitioning.fills[layer_name].translate; + } + + // fill color + if (transitionInProgress(layer_name, TransitionablePropertyKey::FillColor)) { + + computed.fills[layer_name].fill_color = transitioning.fills[layer_name].fill_color; + + } else if (inNeedOfTransition(layer_name, TransitionablePropertyKey::FillColor)) { + + transitioning.fills[layer_name].fill_color = previous.fills[layer_name].fill_color; + + transitions[layer_name][TransitionablePropertyKey::FillColor] = + std::make_shared<util::ease_transition<Color>>(previous.fills[layer_name].fill_color, + computed.fills[layer_name].fill_color, + transitioning.fills[layer_name].fill_color, + start + transitionDelay(layer_name, TransitionablePropertyKey::FillColor), + transitionDuration(layer_name, TransitionablePropertyKey::FillColor)); + + computed.fills[layer_name].fill_color = transitioning.fills[layer_name].fill_color; + } + + // stroke color + if (transitionInProgress(layer_name, TransitionablePropertyKey::StrokeColor)) { + + computed.fills[layer_name].stroke_color = transitioning.fills[layer_name].stroke_color; + + } else if (inNeedOfTransition(layer_name, TransitionablePropertyKey::StrokeColor)) { + + transitioning.fills[layer_name].stroke_color = previous.fills[layer_name].stroke_color; + + transitions[layer_name][TransitionablePropertyKey::StrokeColor] = + std::make_shared<util::ease_transition<Color>>(previous.fills[layer_name].stroke_color, + computed.fills[layer_name].stroke_color, + transitioning.fills[layer_name].stroke_color, + start + transitionDelay(layer_name, TransitionablePropertyKey::StrokeColor), + transitionDuration(layer_name, TransitionablePropertyKey::StrokeColor)); + + computed.fills[layer_name].stroke_color = transitioning.fills[layer_name].stroke_color; } + // opacity + if (transitionInProgress(layer_name, TransitionablePropertyKey::Opacity)) { + + computed.fills[layer_name].opacity = transitioning.fills[layer_name].opacity; + + } else if (inNeedOfTransition(layer_name, TransitionablePropertyKey::Opacity)) { - // Cascade background - computed.background.color = sheetClass.background.color; - computed.background.opacity = sheetClass.background.opacity.evaluate<float>(z); + transitioning.fills[layer_name].opacity = previous.fills[layer_name].opacity; + + transitions[layer_name][TransitionablePropertyKey::Opacity] = + std::make_shared<util::ease_transition<float>>(previous.fills[layer_name].opacity, + computed.fills[layer_name].opacity, + transitioning.fills[layer_name].opacity, + start + transitionDelay(layer_name, TransitionablePropertyKey::Opacity), + transitionDuration(layer_name, TransitionablePropertyKey::Opacity)); + + computed.fills[layer_name].opacity = transitioning.fills[layer_name].opacity; + } } + + // Lines + for (const auto& line_pair : computed.lines) { + const std::string& layer_name = line_pair.first; + + // translate + if (transitionInProgress(layer_name, TransitionablePropertyKey::Translate)) { + + computed.lines[layer_name].translate = transitioning.lines[layer_name].translate; + + } else if (inNeedOfTransition(layer_name, TransitionablePropertyKey::Translate)) { + + transitioning.lines[layer_name].translate = previous.lines[layer_name].translate; + + transitions[layer_name][TransitionablePropertyKey::Translate] = + std::make_shared<util::ease_transition<std::array<float, 2>>>(previous.lines[layer_name].translate, + computed.lines[layer_name].translate, + transitioning.lines[layer_name].translate, + start + transitionDelay(layer_name, TransitionablePropertyKey::Translate), + transitionDuration(layer_name, TransitionablePropertyKey::Translate)); + + computed.lines[layer_name].translate = transitioning.lines[layer_name].translate; + } + + // width + if (transitionInProgress(layer_name, TransitionablePropertyKey::Width)) { + + computed.lines[layer_name].width = transitioning.lines[layer_name].width; + + } else if (inNeedOfTransition(layer_name, TransitionablePropertyKey::Width)) { + + transitioning.lines[layer_name].width = previous.lines[layer_name].width; + + transitions[layer_name][TransitionablePropertyKey::Width] = + std::make_shared<util::ease_transition<float>>(previous.lines[layer_name].width, + computed.lines[layer_name].width, + transitioning.lines[layer_name].width, + start + transitionDelay(layer_name, TransitionablePropertyKey::Width), + transitionDuration(layer_name, TransitionablePropertyKey::Width)); + + computed.lines[layer_name].width = transitioning.lines[layer_name].width; + } + + // offset + if (transitionInProgress(layer_name, TransitionablePropertyKey::Offset)) { + + computed.lines[layer_name].offset = transitioning.lines[layer_name].offset; + + } else if (inNeedOfTransition(layer_name, TransitionablePropertyKey::Offset)) { + + transitioning.lines[layer_name].offset = previous.lines[layer_name].offset; + + transitions[layer_name][TransitionablePropertyKey::Offset] = + std::make_shared<util::ease_transition<float>>(previous.lines[layer_name].offset, + computed.lines[layer_name].offset, + transitioning.lines[layer_name].offset, + start + transitionDelay(layer_name, TransitionablePropertyKey::Offset), + transitionDuration(layer_name, TransitionablePropertyKey::Offset)); + + computed.lines[layer_name].offset = transitioning.lines[layer_name].offset; + } + + // color + if (transitionInProgress(layer_name, TransitionablePropertyKey::Color)) { + + computed.lines[layer_name].color = transitioning.lines[layer_name].color; + + } else if (inNeedOfTransition(layer_name, TransitionablePropertyKey::Color)) { + + transitioning.lines[layer_name].color = previous.lines[layer_name].color; + + transitions[layer_name][TransitionablePropertyKey::Color] = + std::make_shared<util::ease_transition<Color>>(previous.lines[layer_name].color, + computed.lines[layer_name].color, + transitioning.lines[layer_name].color, + start + transitionDelay(layer_name, TransitionablePropertyKey::Color), + transitionDuration(layer_name, TransitionablePropertyKey::Color)); + + computed.lines[layer_name].color = transitioning.lines[layer_name].color; + } + + // dasharray + if (transitionInProgress(layer_name, TransitionablePropertyKey::DashArray)) { + + computed.lines[layer_name].dash_array = transitioning.lines[layer_name].dash_array; + + } else if (inNeedOfTransition(layer_name, TransitionablePropertyKey::DashArray)) { + + transitioning.lines[layer_name].dash_array = previous.lines[layer_name].dash_array; + + transitions[layer_name][TransitionablePropertyKey::DashArray] = + std::make_shared<util::ease_transition<std::array<float, 2>>>(previous.lines[layer_name].dash_array, + computed.lines[layer_name].dash_array, + transitioning.lines[layer_name].dash_array, + start + transitionDelay(layer_name, TransitionablePropertyKey::DashArray), + transitionDuration(layer_name, TransitionablePropertyKey::DashArray)); + + computed.lines[layer_name].dash_array = transitioning.lines[layer_name].dash_array; + } + + // opacity + if (transitionInProgress(layer_name, TransitionablePropertyKey::Opacity)) { + + computed.lines[layer_name].opacity = transitioning.lines[layer_name].opacity; + + } else if (inNeedOfTransition(layer_name, TransitionablePropertyKey::Opacity)) { + + transitioning.lines[layer_name].opacity = previous.lines[layer_name].opacity; + + transitions[layer_name][TransitionablePropertyKey::Opacity] = + std::make_shared<util::ease_transition<float>>(previous.lines[layer_name].opacity, + computed.lines[layer_name].opacity, + transitioning.lines[layer_name].opacity, + start + transitionDelay(layer_name, TransitionablePropertyKey::Opacity), + transitionDuration(layer_name, TransitionablePropertyKey::Opacity)); + + computed.lines[layer_name].opacity = transitioning.lines[layer_name].opacity; + } + } + + // Icons + for (const auto& icon_pair : computed.icons) { + const std::string& layer_name = icon_pair.first; + + // translate + if (transitionInProgress(layer_name, TransitionablePropertyKey::Translate)) { + + computed.icons[layer_name].translate = transitioning.icons[layer_name].translate; + + } else if (inNeedOfTransition(layer_name, TransitionablePropertyKey::Translate)) { + + transitioning.icons[layer_name].translate = previous.icons[layer_name].translate; + + transitions[layer_name][TransitionablePropertyKey::Translate] = + std::make_shared<util::ease_transition<std::array<float, 2>>>(previous.icons[layer_name].translate, + computed.icons[layer_name].translate, + transitioning.icons[layer_name].translate, + start + transitionDelay(layer_name, TransitionablePropertyKey::Translate), + transitionDuration(layer_name, TransitionablePropertyKey::Translate)); + + computed.icons[layer_name].translate = transitioning.icons[layer_name].translate; + } + + // color + if (transitionInProgress(layer_name, TransitionablePropertyKey::Color)) { + + computed.icons[layer_name].color = transitioning.icons[layer_name].color; + + } else if (inNeedOfTransition(layer_name, TransitionablePropertyKey::Color)) { + + transitioning.icons[layer_name].color = previous.icons[layer_name].color; + + transitions[layer_name][TransitionablePropertyKey::Color] = + std::make_shared<util::ease_transition<Color>>(previous.icons[layer_name].color, + computed.icons[layer_name].color, + transitioning.icons[layer_name].color, + start + transitionDelay(layer_name, TransitionablePropertyKey::Color), + transitionDuration(layer_name, TransitionablePropertyKey::Color)); + + computed.icons[layer_name].color = transitioning.icons[layer_name].color; + } + + // opacity + if (transitionInProgress(layer_name, TransitionablePropertyKey::Opacity)) { + + computed.icons[layer_name].opacity = transitioning.icons[layer_name].opacity; + + } else if (inNeedOfTransition(layer_name, TransitionablePropertyKey::Opacity)) { + + transitioning.icons[layer_name].opacity = previous.icons[layer_name].opacity; + + transitions[layer_name][TransitionablePropertyKey::Opacity] = + std::make_shared<util::ease_transition<float>>(previous.icons[layer_name].opacity, + computed.icons[layer_name].opacity, + transitioning.icons[layer_name].opacity, + start + transitionDelay(layer_name, TransitionablePropertyKey::Opacity), + transitionDuration(layer_name, TransitionablePropertyKey::Opacity)); + + computed.icons[layer_name].opacity = transitioning.icons[layer_name].opacity; + } + + // radius + if (transitionInProgress(layer_name, TransitionablePropertyKey::Radius)) { + + computed.icons[layer_name].radius = transitioning.icons[layer_name].radius; + + } else if (inNeedOfTransition(layer_name, TransitionablePropertyKey::Radius)) { + + transitioning.icons[layer_name].radius = previous.icons[layer_name].radius; + + transitions[layer_name][TransitionablePropertyKey::Radius] = + std::make_shared<util::ease_transition<float>>(previous.icons[layer_name].radius, + computed.icons[layer_name].radius, + transitioning.icons[layer_name].radius, + start + transitionDelay(layer_name, TransitionablePropertyKey::Radius), + transitionDuration(layer_name, TransitionablePropertyKey::Radius)); + + computed.icons[layer_name].radius = transitioning.icons[layer_name].radius; + } + + // blur + if (transitionInProgress(layer_name, TransitionablePropertyKey::Blur)) { + + computed.icons[layer_name].blur = transitioning.icons[layer_name].blur; + + } else if (inNeedOfTransition(layer_name, TransitionablePropertyKey::Blur)) { + + transitioning.icons[layer_name].blur = previous.icons[layer_name].blur; + + transitions[layer_name][TransitionablePropertyKey::Blur] = + std::make_shared<util::ease_transition<float>>(previous.icons[layer_name].blur, + computed.icons[layer_name].blur, + transitioning.icons[layer_name].blur, + start + transitionDelay(layer_name, TransitionablePropertyKey::Blur), + transitionDuration(layer_name, TransitionablePropertyKey::Blur)); + + computed.icons[layer_name].blur = transitioning.icons[layer_name].blur; + } + } + + // Text + for (const auto& text_pair : computed.texts) { + const std::string& layer_name = text_pair.first; + + // translate + if (transitionInProgress(layer_name, TransitionablePropertyKey::Translate)) { + + computed.texts[layer_name].translate = transitioning.texts[layer_name].translate; + + } else if (inNeedOfTransition(layer_name, TransitionablePropertyKey::Translate)) { + + transitioning.texts[layer_name].translate = previous.texts[layer_name].translate; + + transitions[layer_name][TransitionablePropertyKey::Translate] = + std::make_shared<util::ease_transition<std::array<float, 2>>>(previous.texts[layer_name].translate, + computed.texts[layer_name].translate, + transitioning.texts[layer_name].translate, + start + transitionDelay(layer_name, TransitionablePropertyKey::Translate), + transitionDuration(layer_name, TransitionablePropertyKey::Translate)); + + computed.texts[layer_name].translate = transitioning.texts[layer_name].translate; + } + + // color + if (transitionInProgress(layer_name, TransitionablePropertyKey::Color)) { + + computed.texts[layer_name].color = transitioning.texts[layer_name].color; + + } else if (inNeedOfTransition(layer_name, TransitionablePropertyKey::Color)) { + + transitioning.texts[layer_name].color = previous.texts[layer_name].color; + + transitions[layer_name][TransitionablePropertyKey::Color] = + std::make_shared<util::ease_transition<Color>>(previous.texts[layer_name].color, + computed.texts[layer_name].color, + transitioning.texts[layer_name].color, + start + transitionDelay(layer_name, TransitionablePropertyKey::Color), + transitionDuration(layer_name, TransitionablePropertyKey::Color)); + + computed.texts[layer_name].color = transitioning.texts[layer_name].color; + } + + // halo + if (transitionInProgress(layer_name, TransitionablePropertyKey::Halo)) { + + computed.texts[layer_name].halo = transitioning.texts[layer_name].halo; + + } else if (inNeedOfTransition(layer_name, TransitionablePropertyKey::Halo)) { + + transitioning.texts[layer_name].halo = previous.texts[layer_name].halo; + + transitions[layer_name][TransitionablePropertyKey::Halo] = + std::make_shared<util::ease_transition<Color>>(previous.texts[layer_name].halo, + computed.texts[layer_name].halo, + transitioning.texts[layer_name].halo, + start + transitionDelay(layer_name, TransitionablePropertyKey::Halo), + transitionDuration(layer_name, TransitionablePropertyKey::Halo)); + + computed.texts[layer_name].halo = transitioning.texts[layer_name].halo; + } + + // halo radius + if (transitionInProgress(layer_name, TransitionablePropertyKey::HaloRadius)) { + + computed.texts[layer_name].halo_radius = transitioning.texts[layer_name].halo_radius; + + } else if (inNeedOfTransition(layer_name, TransitionablePropertyKey::HaloRadius)) { + + transitioning.texts[layer_name].halo_radius = previous.texts[layer_name].halo_radius; + + transitions[layer_name][TransitionablePropertyKey::HaloRadius] = + std::make_shared<util::ease_transition<float>>(previous.texts[layer_name].halo_radius, + computed.texts[layer_name].halo_radius, + transitioning.texts[layer_name].halo_radius, + start + transitionDelay(layer_name, TransitionablePropertyKey::HaloRadius), + transitionDuration(layer_name, TransitionablePropertyKey::HaloRadius)); + + computed.texts[layer_name].halo_radius = transitioning.texts[layer_name].halo_radius; + } + + // halo blur + if (transitionInProgress(layer_name, TransitionablePropertyKey::HaloBlur)) { + + computed.texts[layer_name].halo_blur = transitioning.texts[layer_name].halo_blur; + + } else if (inNeedOfTransition(layer_name, TransitionablePropertyKey::HaloBlur)) { + + transitioning.texts[layer_name].halo_blur = previous.texts[layer_name].halo_blur; + + transitions[layer_name][TransitionablePropertyKey::HaloBlur] = + std::make_shared<util::ease_transition<float>>(previous.texts[layer_name].halo_blur, + computed.texts[layer_name].halo_blur, + transitioning.texts[layer_name].halo_blur, + start + transitionDelay(layer_name, TransitionablePropertyKey::HaloBlur), + transitionDuration(layer_name, TransitionablePropertyKey::HaloBlur)); + + computed.texts[layer_name].halo_blur = transitioning.texts[layer_name].halo_blur; + } + + // opacity + if (transitionInProgress(layer_name, TransitionablePropertyKey::Opacity)) { + + computed.texts[layer_name].opacity = transitioning.texts[layer_name].opacity; + + } else if (inNeedOfTransition(layer_name, TransitionablePropertyKey::Opacity)) { + + transitioning.texts[layer_name].opacity = previous.texts[layer_name].opacity; + + transitions[layer_name][TransitionablePropertyKey::Opacity] = + std::make_shared<util::ease_transition<float>>(previous.texts[layer_name].opacity, + computed.texts[layer_name].opacity, + transitioning.texts[layer_name].opacity, + start + transitionDelay(layer_name, TransitionablePropertyKey::Opacity), + transitionDuration(layer_name, TransitionablePropertyKey::Opacity)); + + computed.texts[layer_name].opacity = transitioning.texts[layer_name].opacity; + } + } + + // Rasters + for (const auto& raster_pair : computed.rasters) { + const std::string& layer_name = raster_pair.first; + + // opacity + if (transitionInProgress(layer_name, TransitionablePropertyKey::Opacity)) { + + computed.rasters[layer_name].opacity = transitioning.rasters[layer_name].opacity; + + } else if (inNeedOfTransition(layer_name, TransitionablePropertyKey::Opacity)) { + + transitioning.rasters[layer_name].opacity = previous.rasters[layer_name].opacity; + + transitions[layer_name][TransitionablePropertyKey::Opacity] = + std::make_shared<util::ease_transition<float>>(previous.rasters[layer_name].opacity, + computed.rasters[layer_name].opacity, + transitioning.rasters[layer_name].opacity, + start + transitionDelay(layer_name, TransitionablePropertyKey::Opacity), + transitionDuration(layer_name, TransitionablePropertyKey::Opacity)); + + computed.rasters[layer_name].opacity = transitioning.rasters[layer_name].opacity; + } + } + + // Composites + for (const auto& composite_pair : computed.composites) { + const std::string& layer_name = composite_pair.first; + + // opacity + if (transitionInProgress(layer_name, TransitionablePropertyKey::Opacity)) { + + computed.composites[layer_name].opacity = transitioning.composites[layer_name].opacity; + + } else if (inNeedOfTransition(layer_name, TransitionablePropertyKey::Opacity)) { + + transitioning.composites[layer_name].opacity = previous.composites[layer_name].opacity; + + transitions[layer_name][TransitionablePropertyKey::Opacity] = + std::make_shared<util::ease_transition<float>>(previous.composites[layer_name].opacity, + computed.composites[layer_name].opacity, + transitioning.composites[layer_name].opacity, + start + transitionDelay(layer_name, TransitionablePropertyKey::Opacity), + transitionDuration(layer_name, TransitionablePropertyKey::Opacity)); + + computed.composites[layer_name].opacity = transitioning.composites[layer_name].opacity; + } + } + + // Background + { + // color + if (transitionInProgress("background", TransitionablePropertyKey::Color)) { + + computed.background.color = transitioning.background.color; + + } else if (inNeedOfTransition("background", TransitionablePropertyKey::Color)) { + + transitioning.background.color = previous.background.color; + + transitions["background"][TransitionablePropertyKey::Color] = + std::make_shared<util::ease_transition<Color>>(previous.background.color, + computed.background.color, + transitioning.background.color, + start + transitionDelay("background", TransitionablePropertyKey::Color), + transitionDuration("background", TransitionablePropertyKey::Color)); + + computed.background.color = transitioning.background.color; + } + + // opacity + if (transitionInProgress("background", TransitionablePropertyKey::Opacity)) { + + computed.background.opacity = transitioning.background.opacity; + + } else if (inNeedOfTransition("background", TransitionablePropertyKey::Opacity)) { + + transitioning.background.opacity = previous.background.opacity; + + transitions["background"][TransitionablePropertyKey::Opacity] = + std::make_shared<util::ease_transition<float>>(previous.background.opacity, + computed.background.opacity, + transitioning.background.opacity, + start + transitionDelay("background", TransitionablePropertyKey::Opacity), + transitionDuration("background", TransitionablePropertyKey::Opacity)); + + computed.background.opacity = transitioning.background.opacity; + } + } +} + +bool Style::transitionInProgress(std::string layer_name, TransitionablePropertyKey key) { + if (!transitionExists(layer_name, key)) return false; + + return (transitions[layer_name].find(key)->second->update(util::now()) != util::transition::complete); +} + +bool Style::transitionExists(std::string layer_name, TransitionablePropertyKey key) { + return (transitions[layer_name].count(key) != 0); +} + +bool Style::inNeedOfTransition(std::string layer_name, TransitionablePropertyKey key) { + if (!transitionDuration(layer_name, key)) return false; + if (transitionExists(layer_name, key)) return false; + + return (computed.effective_classes[layer_name][key] != previous.effective_classes[layer_name][key]); +} + +uint64_t Style::transitionDuration(std::string layer_name, TransitionablePropertyKey key) { + return (properties_to_transition[layer_name].count(key) ? + properties_to_transition[layer_name][key].duration : + default_transition_duration) * 1_millisecond; +} + +uint64_t Style::transitionDelay(std::string layer_name, TransitionablePropertyKey key) { + return (properties_to_transition[layer_name][key].delay * 1_millisecond); +} + +bool Style::needsTransition() const { + uv::readlock lock(mtx); + + for (auto layer_it = transitions.begin(); layer_it != transitions.end(); layer_it++) { + auto& layer_transitions = layer_it->second; + if (layer_transitions.size()) { + return true; + } + } + + return false; +} + +void Style::updateTransitions(time now) { + uv::writelock lock(mtx); + + for (auto layer_it = transitions.begin(); layer_it != transitions.end(); layer_it++) { + auto& layer_transitions = layer_it->second; + for (auto prop_it = layer_transitions.begin(); prop_it != layer_transitions.end();/**/) { + auto& transition = prop_it->second; + if (transition->update(now) == util::transition::complete) { + prop_it = layer_transitions.erase(prop_it); + } else { + prop_it++; + } + } + } +} + +void Style::cancelTransitions() { + uv::writelock lock(mtx); + + transitions.clear(); } size_t Style::layerCount() const { @@ -158,6 +968,10 @@ size_t Style::layerCount() const { return count; } +void Style::setDefaultTransitionDuration(uint64_t duration) { + default_transition_duration = duration; +} + void Style::loadJSON(const uint8_t *const data, size_t bytes) { rapidjson::Document doc; diff --git a/src/style/style_parser.cpp b/src/style/style_parser.cpp index 2198d5c369..feef6e3e15 100644 --- a/src/style/style_parser.cpp +++ b/src/style/style_parser.cpp @@ -477,42 +477,82 @@ FunctionProperty StyleParser::parseFunction(JSVal value) { return property; } +PropertyTransition StyleParser::parseTransition(JSVal value, std::string property_name) { + uint16_t duration = 0, delay = 0; + std::string transition_property = std::string("transition-").append(property_name); + if (value.HasMember(transition_property.c_str())) { + JSVal elements = value[transition_property.c_str()]; + if (elements.IsObject()) { + if (elements.HasMember("duration") && elements["duration"].IsNumber()) { + duration = elements["duration"].GetUint(); + } + if (elements.HasMember("delay") && elements["delay"].IsNumber()) { + delay = elements["delay"].GetUint(); + } + } + } + + PropertyTransition transition; + + transition.duration = duration; + transition.delay = delay; + + return transition; +} + FillClass StyleParser::parseFillClass(JSVal value) { FillClass klass; if (value.HasMember("enabled")) { klass.enabled = parseFunction(value["enabled"]); + klass.specifiers.insert("enabled"); } if (value.HasMember("translate")) { std::vector<FunctionProperty> values = parseArray(value["translate"], 2); klass.translate = std::array<FunctionProperty, 2> {{ values[0], values[1] }}; + klass.translate_transition = parseTransition(value, "translate"); + klass.specifiers.insert("translate"); } if (value.HasMember("translate-anchor")) { klass.translateAnchor = parseTranslateAnchor(value["translate-anchor"]); + klass.specifiers.insert("translate-anchor"); + } + + if (value.HasMember("winding")) { + throw std::runtime_error("winding in stylesheets not yet supported"); } if (value.HasMember("color")) { klass.fill_color = parseColor(value["color"]); + klass.fill_color_transition = parseTransition(value, "color"); + klass.specifiers.insert("color"); } if (value.HasMember("stroke")) { klass.stroke_color = parseColor(value["stroke"]); + klass.stroke_color_transition = parseTransition(value, "stroke"); + klass.specifiers.insert("stroke"); } else { klass.stroke_color = klass.fill_color; + klass.specifiers.insert("stroke"); } if (value.HasMember("antialias")) { klass.antialias = parseBoolean(value["antialias"]); + klass.specifiers.insert("antialias"); } if (value.HasMember("image")) { klass.image = parseString(value["image"]); + klass.specifiers.insert("image"); } if (value.HasMember("opacity")) { klass.opacity = parseFunction(value["opacity"]); + klass.opacity_transition = parseTransition(value, "opacity"); + klass.specifiers.insert("opacity"); } return klass; @@ -523,32 +563,44 @@ LineClass StyleParser::parseLineClass(JSVal value) { if (value.HasMember("enabled")) { klass.enabled = parseFunction(value["enabled"]); + klass.specifiers.insert("enabled"); } if (value.HasMember("translate")) { std::vector<FunctionProperty> values = parseArray(value["translate"], 2); klass.translate = std::array<FunctionProperty, 2> {{ values[0], values[1] }}; + klass.translate_transition = parseTransition(value, "translate"); + klass.specifiers.insert("translate"); } if (value.HasMember("translate-anchor")) { klass.translateAnchor = parseTranslateAnchor(value["translate-anchor"]); + klass.specifiers.insert("translate-anchor"); } if (value.HasMember("color")) { klass.color = parseColor(value["color"]); + klass.color_transition = parseTransition(value, "color"); + klass.specifiers.insert("color"); } if (value.HasMember("width")) { klass.width = parseFunction(value["width"]); + klass.width_transition = parseTransition(value, "width"); + klass.specifiers.insert("width"); } if (value.HasMember("opacity")) { klass.opacity = parseFunction(value["opacity"]); + klass.opacity_transition = parseTransition(value, "opacity"); + klass.specifiers.insert("opacity"); } if (value.HasMember("dasharray")) { std::vector<FunctionProperty> values = parseArray(value["dasharray"], 2); klass.dash_array = std::array<FunctionProperty, 2> {{ values[0], values[1] }}; + klass.dash_array_transition = parseTransition(value, "dasharray"); + klass.specifiers.insert("dasharray"); } return klass; @@ -559,39 +611,53 @@ IconClass StyleParser::parseIconClass(JSVal value) { if (value.HasMember("enabled")) { klass.enabled = parseFunction(value["enabled"]); + klass.specifiers.insert("enabled"); } if (value.HasMember("translate")) { std::vector<FunctionProperty> values = parseArray(value["translate"], 2); klass.translate = std::array<FunctionProperty, 2> {{ values[0], values[1] }}; + klass.translate_transition = parseTransition(value, "translate"); + klass.specifiers.insert("translate"); } if (value.HasMember("translate-anchor")) { klass.translateAnchor = parseTranslateAnchor(value["translate-anchor"]); + klass.specifiers.insert("translate-anchor"); } if (value.HasMember("color")) { klass.color = parseColor(value["color"]); + klass.color_transition = parseTransition(value, "color"); + klass.specifiers.insert("color"); } if (value.HasMember("opacity")) { klass.opacity = parseFunction(value["opacity"]); + klass.opacity_transition = parseTransition(value, "opacity"); + klass.specifiers.insert("opacity"); } if (value.HasMember("image")) { klass.image = parseString(value["image"]); + klass.specifiers.insert("image"); } if (value.HasMember("size")) { klass.size = parseFunction(value["size"]); + klass.specifiers.insert("size"); } if (value.HasMember("radius")) { klass.radius = parseFunction(value["radius"]); + klass.radius_transition = parseTransition(value, "radius"); + klass.specifiers.insert("radius"); } if (value.HasMember("blur")) { klass.blur = parseFunction(value["blur"]); + klass.blur_transition = parseTransition(value, "blur"); + klass.specifiers.insert("blur"); } return klass; @@ -602,43 +668,64 @@ TextClass StyleParser::parseTextClass(JSVal value) { if (value.HasMember("enabled")) { klass.enabled = parseFunction(value["enabled"]); + klass.specifiers.insert("enabled"); } if (value.HasMember("translate")) { std::vector<FunctionProperty> values = parseArray(value["translate"], 2); klass.translate = std::array<FunctionProperty, 2> {{ values[0], values[1] }}; + klass.translate_transition = parseTransition(value, "translate"); + klass.specifiers.insert("translate"); } if (value.HasMember("translate-anchor")) { klass.translateAnchor = parseTranslateAnchor(value["translate-anchor"]); + klass.specifiers.insert("translate-anchor"); } if (value.HasMember("color")) { klass.color = parseColor(value["color"]); + klass.color_transition = parseTransition(value, "color"); + klass.specifiers.insert("color"); } if (value.HasMember("stroke")) { klass.halo = parseColor(value["stroke"]); + klass.halo_transition = parseTransition(value, "stroke"); + klass.specifiers.insert("stroke"); } if (value.HasMember("strokeWidth")) { klass.halo_radius = parseFunction(value["strokeWidth"]); + klass.halo_radius_transition = parseTransition(value, "strokeWidth"); + klass.specifiers.insert("strokeWidth"); } if (value.HasMember("strokeBlur")) { klass.halo_blur = parseFunction(value["strokeBlur"]); + klass.halo_blur_transition = parseTransition(value, "strokeBlur"); + klass.specifiers.insert("strokeBlur"); } if (value.HasMember("size")) { klass.size = parseFunction(value["size"]); + klass.specifiers.insert("size"); } if (value.HasMember("rotate")) { klass.rotate = parseFunction(value["rotate"]); + klass.specifiers.insert("rotate"); } if (value.HasMember("alwaysVisible")) { - klass.alwaysVisible = parseFunction(value["alwaysVisible"]); + klass.always_visible = parseFunction(value["alwaysVisible"]); + klass.specifiers.insert("alwaysVisible"); + } + + if (value.HasMember("opacity")) { + klass.opacity = parseFunction(value["opacity"]); + klass.opacity_transition = parseTransition(value, "opacity"); + klass.specifiers.insert("opacity"); } return klass; @@ -649,10 +736,13 @@ RasterClass StyleParser::parseRasterClass(JSVal value) { if (value.HasMember("enabled")) { klass.enabled = parseFunction(value["enabled"]); + klass.specifiers.insert("enabled"); } if (value.HasMember("opacity")) { klass.opacity = parseFunction(value["opacity"]); + klass.opacity_transition = parseTransition(value, "opacity"); + klass.specifiers.insert("opacity"); } return klass; @@ -663,10 +753,13 @@ CompositeClass StyleParser::parseCompositeClass(JSVal value) { if (value.HasMember("enabled")) { klass.enabled = parseFunction(value["enabled"]); + klass.specifiers.insert("enabled"); } if (value.HasMember("opacity")) { klass.opacity = parseFunction(value["opacity"]); + klass.opacity_transition = parseTransition(value, "opacity"); + klass.specifiers.insert("opacity"); } return klass; @@ -677,10 +770,14 @@ BackgroundClass StyleParser::parseBackgroundClass(JSVal value) { if (value.HasMember("color")) { klass.color = parseColor(value["color"]); + klass.color_transition = parseTransition(value, "color"); + klass.specifiers.insert("color"); } if (value.HasMember("opacity")) { klass.opacity = parseFunction(value["opacity"]); + klass.opacity_transition = parseTransition(value, "opacity"); + klass.specifiers.insert("opacity"); } return klass; diff --git a/src/util/raster.cpp b/src/util/raster.cpp index 4f26f8b558..e08145231f 100644 --- a/src/util/raster.cpp +++ b/src/util/raster.cpp @@ -70,7 +70,7 @@ void Raster::bind(bool linear) { void Raster::beginFadeInTransition() { time start = util::now(); - fade_transition = std::make_shared<util::ease_transition>(opacity, 1.0, opacity, start, 250_milliseconds); + fade_transition = std::make_shared<util::ease_transition<double>>(opacity, 1.0, opacity, start, 250_milliseconds); } bool Raster::needsTransition() const { diff --git a/src/util/transition.cpp b/src/util/transition.cpp index af5b5a70ac..d15e3ef66e 100644 --- a/src/util/transition.cpp +++ b/src/util/transition.cpp @@ -8,20 +8,69 @@ UnitBezier ease(0.25, 0.1, 0.25, 1); transition::~transition() {} -ease_transition::ease_transition(double from, double to, double &value, time start, time duration) - : transition(start, duration), - from(from), - to(to), - value(value) { +double transition::interpolateDouble(double from, double to, double t) const { + return from + (to - from) * t; } -transition::state ease_transition::update(time now) const { +float transition::interpolateFloat(float from, float to, double t) const { + return from + (to - from) * (float)t; +} + +llmr::Color transition::interpolateColor(llmr::Color from, llmr::Color to, double t) const { + return {{ interpolateFloat(from[0], to[0], t), + interpolateFloat(from[1], to[1], t), + interpolateFloat(from[2], to[2], t), + interpolateFloat(from[3], to[3], t) }}; +} + +std::array<float, 2> transition::interpolateFloatArray(std::array<float, 2> from, std::array<float, 2> to, double t) const { + return {{ interpolateFloat(from[0], to[0], t), interpolateFloat(from[1], to[1], t) }}; +} + +template <> +transition::state ease_transition<double>::update(llmr::time now) const { + float t = progress(now); + if (t >= 1) { + value = to; + return complete; + } else { + value = interpolateDouble(from, to, ease.solve(t, 0.001)); + return running; + } +} + +template <> +transition::state ease_transition<llmr::Color>::update(llmr::time now) const { + float t = progress(now); + if (t >= 1) { + value = to; + return complete; + } else { + value = interpolateColor(from, to, ease.solve(t, 0.001)); + return running; + } +} + +template <> +transition::state ease_transition<float>::update(llmr::time now) const { + float t = progress(now); + if (t >= 1) { + value = to; + return complete; + } else { + value = interpolateFloat(from, to, ease.solve(t, 0.001)); + return running; + } +} + +template <> +transition::state ease_transition<std::array<float, 2>>::update(llmr::time now) const { float t = progress(now); if (t >= 1) { value = to; return complete; } else { - value = from + (to - from) * ease.solve(t, 0.001); + value = interpolateFloatArray(from, to, ease.solve(t, 0.001)); return running; } } |