diff --git a/bin/style.js b/bin/style.js index 6a9fe09ee..886dceafa 100644 --- a/bin/style.js +++ b/bin/style.js @@ -186,11 +186,18 @@ module.exports = { "value": "rock", "type": "fill" }, + "landuse_snow": { + "source": "outdoors", + "layer": "landuse", + "field": "class", + "value": "snow", + "type": "fill" + }, "overlay_wetland": { "source": "outdoors", "layer": "landuse_overlay", "field": "class", - "value": "wetland", + "value": ["wetland", "wetland_noveg"], "type": "fill" }, "overlay_breakwater_pier": { @@ -888,6 +895,10 @@ module.exports = { "name": "landuse_rock", "bucket": "landuse_rock" }, + { + "name": "landuse_snow", + "bucket": "landuse_snow" + }, { "name": "landuse_hospital", "bucket": "landuse_hospital" @@ -1322,9 +1333,9 @@ module.exports = { "water": "#cdd", "water_dark": "#185869", "crop": "#eeeed4", - "grass": "#e7ebd1", - "scrub": "#e0e8cd", - "wood": "#d4e2c6", + "grass": "#e6e6cc", + "scrub": "#dfe5c8", + "wood": "#cee2bd", "snow": "#f4f8ff", "rock": "#ddd", "sand": "#ffd", @@ -2257,6 +2268,10 @@ module.exports = { "translate": [0, 14] }, "poi_aerodrome": { + "opacity": ["stops", + {"z": 13, "val": 0}, + {"z": 13.25, "val": 1} + ], "antialias": false } } @@ -2949,6 +2964,10 @@ module.exports = { "translate": [0, 14] }, "poi_aerodrome": { + "opacity": ["stops", + {"z": 13, "val": 0}, + {"z": 13.25, "val": 1} + ], "antialias": false } } diff --git a/include/llmr/map/transform.hpp b/include/llmr/map/transform.hpp index 550d14b14..4226ec7e4 100644 --- a/include/llmr/map/transform.hpp +++ b/include/llmr/map/transform.hpp @@ -68,6 +68,8 @@ private: void _clearRotating(); void _clearScaling(); + void constrain(double& scale, double& y); + private: mutable uv::rwlock mtx; diff --git a/include/llmr/style/properties.hpp b/include/llmr/style/properties.hpp index 04f1819b3..ba0979cb9 100644 --- a/include/llmr/style/properties.hpp +++ b/include/llmr/style/properties.hpp @@ -5,12 +5,36 @@ #include #include #include +#include + +#include namespace llmr { // Stores a premultiplied color, with all four channels ranging from 0..1 typedef std::array 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 @@ -61,15 +85,20 @@ struct FunctionProperty { }; struct IconClass { - FunctionProperty enabled = true; - std::array translate = {{ 0, 0 }}; - TranslateAnchor translateAnchor = TranslateAnchor::Map; - FunctionProperty size; - Color color = {{ 1, 1, 1, 1 }}; - FunctionProperty opacity = 1; - std::string image; - FunctionProperty radius = 0; - FunctionProperty blur = 0; + boost::optional enabled; + boost::optional> translate; + boost::optional translate_transition; + boost::optional translateAnchor; + boost::optional size; + boost::optional color; + boost::optional color_transition; + boost::optional opacity; + boost::optional opacity_transition; + boost::optional image; + boost::optional radius; + boost::optional radius_transition; + boost::optional blur; + boost::optional blur_transition; }; struct IconProperties { @@ -85,14 +114,20 @@ struct IconProperties { }; struct LineClass { - FunctionProperty enabled = true; - std::array translate = {{ 0, 0 }}; - TranslateAnchor translateAnchor = TranslateAnchor::Map; - FunctionProperty width; - FunctionProperty offset; - Color color = {{ 0, 0, 0, 1 }}; - std::array dash_array = {{ 1, -1 }}; - FunctionProperty opacity = 1; + boost::optional enabled; + boost::optional> translate; + boost::optional translate_transition; + boost::optional translateAnchor; + boost::optional width; + boost::optional width_transition; + boost::optional offset; + boost::optional offset_transition; + boost::optional color; + boost::optional color_transition; + boost::optional> dash_array; + boost::optional dash_array_transition; + boost::optional opacity; + boost::optional opacity_transition; }; struct LineProperties { @@ -107,15 +142,19 @@ struct LineProperties { }; struct FillClass { - FunctionProperty enabled = true; - std::array translate = {{ 0, 0 }}; - TranslateAnchor translateAnchor = TranslateAnchor::Map; - Winding winding = Winding::NonZero; - FunctionProperty antialias = true; - Color fill_color = {{ 0, 0, 0, 1 }}; - Color stroke_color = {{ 0, 0, 0, std::numeric_limits::infinity() }}; - FunctionProperty opacity = 1; - std::string image; + boost::optional enabled; + boost::optional> translate; + boost::optional translate_transition; + boost::optional translateAnchor; + boost::optional winding; + boost::optional antialias; + boost::optional fill_color; + boost::optional fill_color_transition; + boost::optional stroke_color; + boost::optional stroke_color_transition; + boost::optional opacity; + boost::optional opacity_transition; + boost::optional image; }; struct FillProperties { @@ -131,17 +170,23 @@ struct FillProperties { }; struct TextClass { - FunctionProperty enabled = true; - std::array translate = {{ 0, 0 }}; - TranslateAnchor translateAnchor = TranslateAnchor::Map; - Color color = {{ 0, 0, 0, 1 }}; - Color halo = {{ 1, 1, 1, 0.75 }}; - FunctionProperty halo_radius = 0.25f; - FunctionProperty halo_blur = 1.0f; - FunctionProperty size = 12.0f; - FunctionProperty rotate = 0.0f; - FunctionProperty alwaysVisible = false; - FunctionProperty opacity = 1; + boost::optional enabled; + boost::optional> translate; + boost::optional translate_transition; + boost::optional translateAnchor; + boost::optional color; + boost::optional color_transition; + boost::optional halo; + boost::optional halo_transition; + boost::optional halo_radius; + boost::optional halo_radius_transition; + boost::optional halo_blur; + boost::optional halo_blur_transition; + boost::optional size; + boost::optional rotate; + boost::optional always_visible; + boost::optional opacity; + boost::optional opacity_transition; }; struct TextProperties { @@ -154,13 +199,15 @@ 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 }}; - FunctionProperty opacity = 1; + boost::optional color; + boost::optional color_transition; + boost::optional opacity; + boost::optional opacity_transition; }; struct BackgroundProperties { @@ -169,9 +216,11 @@ struct BackgroundProperties { }; struct RasterClass { - FunctionProperty enabled = true; - std::array translate = {{ 0, 0 }}; - FunctionProperty opacity = 1; + boost::optional enabled; + boost::optional> translate; + boost::optional translate_transition; + boost::optional opacity; + boost::optional opacity_transition; }; struct RasterProperties { @@ -181,8 +230,9 @@ struct RasterProperties { }; struct CompositeClass { - FunctionProperty enabled = true; - FunctionProperty opacity = 1; + boost::optional enabled; + boost::optional opacity; + boost::optional opacity_transition; }; struct CompositeProperties { diff --git a/include/llmr/style/style.hpp b/include/llmr/style/style.hpp index 920d0c6c1..256ff4d6b 100644 --- a/include/llmr/style/style.hpp +++ b/include/llmr/style/style.hpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include #include @@ -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; @@ -40,8 +48,7 @@ public: std::vector layers; std::map classes; - - // This are applied settings. + // Currently applied settings. std::set appliedClasses; struct { BackgroundProperties background; @@ -51,7 +58,47 @@ public: std::map texts; std::map rasters; std::map composites; + std::map> 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 fills; + std::map lines; + std::map icons; + std::map texts; + std::map rasters; + std::map composites; + std::map> effective_classes; + } previous; + + // Settings values currently being transitioned. + struct { + BackgroundProperties background; + std::map fills; + std::map lines; + std::map icons; + std::map texts; + std::map rasters; + std::map composites; + } transitioning; + + std::map> properties_to_transition; + std::map>> 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 82dd7e9b5..6668048ff 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 constants; diff --git a/include/llmr/util/transition.hpp b/include/llmr/util/transition.hpp index cab73aab5..a981f6e83 100644 --- a/include/llmr/util/transition.hpp +++ b/include/llmr/util/transition.hpp @@ -3,6 +3,7 @@ #include #include +#include namespace llmr { namespace util { @@ -19,39 +20,45 @@ public: duration(duration) {} inline float progress(time now) const { + if (duration == 0) return 1; + if (start > now) return 0; + return (float)(now - start) / duration; } virtual state update(time now) const = 0; 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 interpolateFloatArray(std::array from, std::array to, double t) const; + protected: const time start, duration; }; +template class ease_transition : public transition { public: - // Disable automatic casts. - template - 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 class timeout : public transition { public: - // Disable automatic casts. - template - 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/ios/MVKMapKit b/ios/MVKMapKit index a9a7fa3ab..172218fa9 160000 --- a/ios/MVKMapKit +++ b/ios/MVKMapKit @@ -1 +1 @@ -Subproject commit a9a7fa3ab32ef14435dd22533c55db6554118e7f +Subproject commit 172218fa978dc061c31ce3a814294fd7cd5559b9 diff --git a/src/map/map.cpp b/src/map/map.cpp index 9df6ecb4b..3c0fbbcbb 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"); @@ -427,23 +425,20 @@ void Map::updateRenderState() { void Map::prepare() { view.make_active(); - // Update transitions + // Update transform transitions. animationTime = util::now(); if (transform.needsTransition()) { transform.updateTransitions(animationTime); } - const TransformState oldState = state; state = transform.currentState(); bool pixelRatioChanged = oldState.getPixelRatio() != state.getPixelRatio(); bool dimensionsChanged = oldState.getFramebufferWidth() != state.getFramebufferWidth() || oldState.getFramebufferHeight() != state.getFramebufferHeight(); - bool zoomChanged = oldState.getNormalizedZoom() != state.getNormalizedZoom(); if (pixelRatioChanged) { - // We have a pixelratio change style.sprite = std::make_shared(*this, state.getPixelRatio()); style.sprite->load(kSpriteURL); @@ -454,8 +449,13 @@ void Map::prepare() { painter.clearFramebuffers(); } - if (zoomChanged) { - style.cascade(state.getNormalizedZoom()); + style.cascade(state.getNormalizedZoom()); + + // Update style transitions. + animationTime = util::now(); + if (style.needsTransition()) { + style.updateTransitions(animationTime); + update(); } // Allow the sprite atlas to potentially pull new sprite images if needed. diff --git a/src/map/transform.cpp b/src/map/transform.cpp index fb3458ade..23e42b764 100644 --- a/src/map/transform.cpp +++ b/src/map/transform.cpp @@ -12,6 +12,7 @@ using namespace llmr; const double D2R = M_PI / 180.0; const double R2D = 180.0 / M_PI; const double M2PI = 2 * M_PI; +const double MIN_ROTATE_SCALE = 8; Transform::Transform() { setScale(current.scale); @@ -32,6 +33,8 @@ bool Transform::resize(const uint16_t w, const uint16_t h, const float ratio, current.pixelRatio = final.pixelRatio = ratio; current.framebuffer[0] = final.framebuffer[0] = fb_w; current.framebuffer[1] = final.framebuffer[1] = fb_h; + constrain(current.scale, current.y); + if (current.scale < MIN_ROTATE_SCALE && current.angle) _setAngle(0, 0); return true; } else { return false; @@ -52,6 +55,8 @@ void Transform::_moveBy(const double dx, const double dy, const time duration) { final.x = current.x + std::cos(current.angle) * dx + std::sin(current.angle) * dy; final.y = current.y + std::cos(current.angle) * dy + std::sin(-current.angle) * dx; + constrain(final.scale, final.y); + if (duration == 0) { current.x = final.x; current.y = final.y; @@ -59,9 +64,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(current.x, final.x, current.x, start, duration)); + std::make_shared>(current.x, final.x, current.x, start, duration)); transitions.emplace_front( - std::make_shared(current.y, final.y, current.y, start, duration)); + std::make_shared>(current.y, final.y, current.y, start, duration)); } } @@ -243,6 +248,11 @@ void Transform::_setScaleXY(const double new_scale, const double xn, const doubl final.x = xn; final.y = yn; + constrain(final.scale, final.y); + + // Undo rotation at low zooms. + if (final.scale < MIN_ROTATE_SCALE && current.angle) _setAngle(0, 500_milliseconds); + if (duration == 0) { current.scale = final.scale; current.x = final.x; @@ -250,12 +260,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( + transitions.emplace_front(std::make_shared>( current.scale, final.scale, current.scale, start, duration)); transitions.emplace_front( - std::make_shared(current.x, final.x, current.x, start, duration)); + std::make_shared>(current.x, final.x, current.x, start, duration)); transitions.emplace_front( - std::make_shared(current.y, final.y, current.y, start, duration)); + std::make_shared>(current.y, final.y, current.y, start, duration)); } const double s = final.scale * util::tileSize; @@ -264,6 +274,19 @@ void Transform::_setScaleXY(const double new_scale, const double xn, const doubl Cc = s / (2 * M_PI); } +#pragma mark - Constraints + +void Transform::constrain(double& scale, double& y) { + // Constrain minimum zoom to avoid zooming out far enough to show off-world areas. + if (scale < (current.height / util::tileSize)) scale = (current.height / util::tileSize); + + // Constrain min/max vertical pan to avoid showing off-world areas. + double max_y = ((scale * util::tileSize) - current.height) / 2; + + if (y > max_y) y = max_y; + if (y < -max_y) y = -max_y; +} + #pragma mark - Angle void Transform::rotateBy(const double start_x, const double start_y, const double end_x, @@ -331,11 +354,14 @@ void Transform::_setAngle(double new_angle, const time duration) { final.angle = new_angle; + // Prevent rotation at low zooms. + if (final.scale < MIN_ROTATE_SCALE) final.angle = 0; + if (duration == 0) { current.angle = final.angle; } else { time start = util::now(); - transitions.emplace_front(std::make_shared( + transitions.emplace_front(std::make_shared>( current.angle, final.angle, current.angle, start, duration)); } } diff --git a/src/style/style.cpp b/src/style/style.cpp index 54528f527..918709924 100644 --- a/src/style/style.cpp +++ b/src/style/style.cpp @@ -19,135 +19,946 @@ 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.composites = computed.composites; + 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(z); - fill.translate = {{ layer.translate[0].evaluate(z), - layer.translate[1].evaluate(z) }}; - fill.translateAnchor = layer.translateAnchor; - fill.winding = layer.winding; - fill.antialias = layer.antialias.evaluate(z); - fill.fill_color = layer.fill_color; - fill.stroke_color = layer.stroke_color; - fill.opacity = layer.opacity.evaluate(z); - fill.image = layer.image; + + if (layer.enabled) { + fill.enabled = layer.enabled.get().evaluate(z); + } + + if (layer.translate) { + fill.translate = {{ layer.translate.get()[0].evaluate(z), + layer.translate.get()[1].evaluate(z) }}; + computed.effective_classes[layer_name][TransitionablePropertyKey::Translate] = class_name; + if (layer.translate_transition && layer.translate_transition.get().duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Translate] = layer.translate_transition.get(); + } + } + + if (layer.translateAnchor) { + fill.translateAnchor = layer.translateAnchor.get(); + } + + if (layer.winding) { + fill.winding = layer.winding.get(); + } + + if (layer.antialias) { + fill.antialias = layer.antialias.get().evaluate(z); + } + + if (layer.fill_color) { + fill.fill_color = layer.fill_color.get(); + computed.effective_classes[layer_name][TransitionablePropertyKey::FillColor] = class_name; + if (layer.fill_color_transition && layer.fill_color_transition.get().duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::FillColor] = layer.fill_color_transition.get(); + } + } + + if (layer.stroke_color) { + fill.stroke_color = layer.stroke_color.get(); + computed.effective_classes[layer_name][TransitionablePropertyKey::StrokeColor] = class_name; + if (layer.stroke_color_transition && layer.stroke_color_transition.get().duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::StrokeColor] = layer.stroke_color_transition.get(); + } + } + + if (layer.opacity) { + fill.opacity = layer.opacity.get().evaluate(z); + computed.effective_classes[layer_name][TransitionablePropertyKey::Opacity] = class_name; + if (layer.opacity_transition && layer.opacity_transition.get().duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Opacity] = layer.opacity_transition.get(); + } + } + + if (layer.image) { + fill.image = layer.image.get(); + } } - // Cascade line classes + // 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(z); - stroke.translate = {{ layer.translate[0].evaluate(z), - layer.translate[1].evaluate(z) }}; - stroke.translateAnchor = layer.translateAnchor; - stroke.width = layer.width.evaluate(z); - stroke.offset = layer.offset.evaluate(z); - stroke.color = layer.color; - stroke.dash_array = {{ layer.dash_array[0].evaluate(z), - layer.dash_array[1].evaluate(z) }}; - stroke.opacity = layer.opacity.evaluate(z); + + if (layer.enabled) { + stroke.enabled = layer.enabled.get().evaluate(z); + } + + if (layer.translate) { + stroke.translate = {{ layer.translate.get()[0].evaluate(z), + layer.translate.get()[1].evaluate(z) }}; + computed.effective_classes[layer_name][TransitionablePropertyKey::Translate] = class_name; + if (layer.translate_transition && layer.translate_transition.get().duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Translate] = layer.translate_transition.get(); + } + } + + if (layer.translateAnchor) { + stroke.translateAnchor = layer.translateAnchor.get(); + } + + if (layer.width) { + stroke.width = layer.width.get().evaluate(z); + computed.effective_classes[layer_name][TransitionablePropertyKey::Width] = class_name; + if (layer.width_transition && layer.width_transition.get().duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Width] = layer.width_transition.get(); + } + + } + + if (layer.offset) { + stroke.offset = layer.offset.get().evaluate(z); + computed.effective_classes[layer_name][TransitionablePropertyKey::Offset] = class_name; + if (layer.offset_transition && layer.offset_transition.get().duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Offset] = layer.offset_transition.get(); + } + } + + if (layer.color) { + stroke.color = layer.color.get(); + computed.effective_classes[layer_name][TransitionablePropertyKey::Color] = class_name; + if (layer.color_transition && layer.color_transition.get().duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Color] = layer.color_transition.get(); + } + } + + if (layer.dash_array) { + stroke.dash_array = {{ layer.dash_array.get()[0].evaluate(z), + layer.dash_array.get()[1].evaluate(z) }}; + computed.effective_classes[layer_name][TransitionablePropertyKey::DashArray] = class_name; + if (layer.dash_array_transition && layer.dash_array_transition.get().duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::DashArray] = layer.dash_array_transition.get(); + } + } + + if (layer.opacity) { + stroke.opacity = layer.opacity.get().evaluate(z); + computed.effective_classes[layer_name][TransitionablePropertyKey::Opacity] = class_name; + if (layer.opacity_transition && layer.opacity_transition.get().duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Opacity] = layer.opacity_transition.get(); + } + } } - // Cascade icon classes + // 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(z); - icon.translate = {{ layer.translate[0].evaluate(z), - layer.translate[1].evaluate(z) }}; - icon.translateAnchor = layer.translateAnchor; - icon.color = layer.color; - icon.size = layer.size.evaluate(z); - icon.opacity = layer.opacity.evaluate(z); - icon.image = layer.image; - icon.radius = layer.radius.evaluate(z); - icon.blur = layer.blur.evaluate(z); + + if (layer.enabled) { + icon.enabled = layer.enabled.get().evaluate(z); + } + + if (layer.translate) { + icon.translate = {{ layer.translate.get()[0].evaluate(z), + layer.translate.get()[1].evaluate(z) }}; + computed.effective_classes[layer_name][TransitionablePropertyKey::Translate] = class_name; + if (layer.translate_transition && layer.translate_transition.get().duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Translate] = layer.translate_transition.get(); + } + } + + if (layer.translateAnchor) { + icon.translateAnchor = layer.translateAnchor.get(); + } + + if (layer.color) { + icon.color = layer.color.get(); + computed.effective_classes[layer_name][TransitionablePropertyKey::Color] = class_name; + if (layer.color_transition && layer.color_transition.get().duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Color] = layer.color_transition.get(); + } + } + + if (layer.size) { + icon.size = layer.size.get().evaluate(z); + } + + if (layer.opacity) { + icon.opacity = layer.opacity.get().evaluate(z); + computed.effective_classes[layer_name][TransitionablePropertyKey::Opacity] = class_name; + if (layer.opacity_transition && layer.opacity_transition.get().duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Opacity] = layer.opacity_transition.get(); + } + } + + if (layer.image) { + icon.image = layer.image.get(); + } + + if (layer.radius) { + icon.radius = layer.radius.get().evaluate(z); + computed.effective_classes[layer_name][TransitionablePropertyKey::Radius] = class_name; + if (layer.radius_transition && layer.radius_transition.get().duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Radius] = layer.radius_transition.get(); + } + } + + if (layer.blur) { + icon.blur = layer.blur.get().evaluate(z); + computed.effective_classes[layer_name][TransitionablePropertyKey::Blur] = class_name; + if (layer.blur_transition && layer.blur_transition.get().duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Blur] = layer.blur_transition.get(); + } + } } - // Cascade text classes + // 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(z); - text.translate = {{ layer.translate[0].evaluate(z), - layer.translate[1].evaluate(z) }}; - text.translateAnchor = layer.translateAnchor; - text.color = layer.color; - text.size = layer.size.evaluate(z); - text.halo = layer.halo; - text.halo_radius = layer.halo_radius.evaluate(z); - text.halo_blur = layer.halo_blur.evaluate(z); - text.rotate = layer.rotate.evaluate(z); - text.alwaysVisible = layer.alwaysVisible.evaluate(z); - text.opacity = layer.opacity.evaluate(z); + + if (layer.enabled) { + text.enabled = layer.enabled.get().evaluate(z); + } + + if (layer.translate) { + text.translate = {{ layer.translate.get()[0].evaluate(z), + layer.translate.get()[1].evaluate(z) }}; + computed.effective_classes[layer_name][TransitionablePropertyKey::Translate] = class_name; + if (layer.translate_transition && layer.translate_transition.get().duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Translate] = layer.translate_transition.get(); + } + } + + if (layer.translateAnchor) { + text.translateAnchor = layer.translateAnchor.get(); + } + + if (layer.color) { + text.color = layer.color.get(); + computed.effective_classes[layer_name][TransitionablePropertyKey::Color] = class_name; + if (layer.color_transition && layer.color_transition.get().duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Color] = layer.color_transition.get(); + } + } + + if (layer.size) { + text.size = layer.size.get().evaluate(z); + } + + if (layer.halo) { + text.halo = layer.halo.get(); + computed.effective_classes[layer_name][TransitionablePropertyKey::Halo] = class_name; + if (layer.halo_transition && layer.halo_transition.get().duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Halo] = layer.halo_transition.get(); + } + } + + if (layer.halo_radius) { + text.halo_radius = layer.halo_radius.get().evaluate(z); + computed.effective_classes[layer_name][TransitionablePropertyKey::HaloRadius] = class_name; + if (layer.halo_radius_transition && layer.halo_radius_transition.get().duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::HaloRadius] = layer.halo_radius_transition.get(); + } + } + + if (layer.halo_blur) { + text.halo_blur = layer.halo_blur.get().evaluate(z); + computed.effective_classes[layer_name][TransitionablePropertyKey::HaloBlur] = class_name; + if (layer.halo_blur_transition && layer.halo_blur_transition.get().duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::HaloBlur] = layer.halo_blur_transition.get(); + } + } + + if (layer.rotate) { + text.rotate = layer.rotate.get().evaluate(z); + } + + if (layer.always_visible) { + text.always_visible = layer.always_visible.get().evaluate(z); + } + + if (layer.opacity) { + text.opacity = layer.opacity.get().evaluate(z); + computed.effective_classes[layer_name][TransitionablePropertyKey::Opacity] = class_name; + if (layer.opacity_transition && layer.opacity_transition.get().duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Opacity] = layer.opacity_transition.get(); + } + } } - // Cascade raster classes + // 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(z); - raster.opacity = layer.opacity.evaluate(z); + + if (layer.enabled) { + raster.enabled = layer.enabled.get().evaluate(z); + } + + if (layer.opacity) { + raster.opacity = layer.opacity.get().evaluate(z); + computed.effective_classes[layer_name][TransitionablePropertyKey::Opacity] = class_name; + if (layer.opacity_transition && layer.opacity_transition.get().duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Opacity] = layer.opacity_transition.get(); + } + } } - // 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(z); - composite.opacity = layer.opacity.evaluate(z); + + if (layer.enabled) { + composite.enabled = layer.enabled.get().evaluate(z); + } + + if (layer.opacity) { + composite.opacity = layer.opacity.get().evaluate(z); + computed.effective_classes[layer_name][TransitionablePropertyKey::Opacity] = class_name; + if (layer.opacity_transition && layer.opacity_transition.get().duration) { + properties_to_transition[layer_name][TransitionablePropertyKey::Opacity] = layer.opacity_transition.get(); + } + } } - - // Cascade background - computed.background.color = sheetClass.background.color; - computed.background.opacity = sheetClass.background.opacity.evaluate(z); + // Cascade background. + { + if (sheetClass.background.color) { + computed.background.color = sheetClass.background.color.get(); + computed.effective_classes["background"][TransitionablePropertyKey::Color] = class_name; + if (sheetClass.background.color_transition && sheetClass.background.color_transition.get().duration) { + properties_to_transition["background"][TransitionablePropertyKey::Color] = sheetClass.background.color_transition.get(); + } + } + if (sheetClass.background.opacity) { + computed.background.opacity = sheetClass.background.opacity.get().evaluate(z); + computed.effective_classes["background"][TransitionablePropertyKey::Opacity] = class_name; + if (sheetClass.background.opacity_transition && sheetClass.background.opacity_transition.get().duration) { + properties_to_transition["background"][TransitionablePropertyKey::Opacity] = sheetClass.background.opacity_transition.get(); + } + } + } } + + // 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>>(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>(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>(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)) { + + transitioning.fills[layer_name].opacity = previous.fills[layer_name].opacity; + + transitions[layer_name][TransitionablePropertyKey::Opacity] = + std::make_shared>(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>>(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>(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>(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>(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>>(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>(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>>(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>(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>(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>(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>(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>>(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>(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>(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>(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>(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>(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>(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>(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>(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>(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 +969,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 2198d5c36..c299f1fd9 100644 --- a/src/style/style_parser.cpp +++ b/src/style/style_parser.cpp @@ -477,6 +477,29 @@ 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; @@ -487,18 +510,25 @@ FillClass StyleParser::parseFillClass(JSVal value) { if (value.HasMember("translate")) { std::vector values = parseArray(value["translate"], 2); klass.translate = std::array {{ values[0], values[1] }}; + klass.translate_transition = parseTransition(value, "translate"); } if (value.HasMember("translate-anchor")) { klass.translateAnchor = parseTranslateAnchor(value["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"); } if (value.HasMember("stroke")) { klass.stroke_color = parseColor(value["stroke"]); + klass.stroke_color_transition = parseTransition(value, "stroke"); } else { klass.stroke_color = klass.fill_color; } @@ -513,6 +543,7 @@ FillClass StyleParser::parseFillClass(JSVal value) { if (value.HasMember("opacity")) { klass.opacity = parseFunction(value["opacity"]); + klass.opacity_transition = parseTransition(value, "opacity"); } return klass; @@ -528,6 +559,7 @@ LineClass StyleParser::parseLineClass(JSVal value) { if (value.HasMember("translate")) { std::vector values = parseArray(value["translate"], 2); klass.translate = std::array {{ values[0], values[1] }}; + klass.translate_transition = parseTransition(value, "translate"); } if (value.HasMember("translate-anchor")) { @@ -536,19 +568,23 @@ LineClass StyleParser::parseLineClass(JSVal value) { if (value.HasMember("color")) { klass.color = parseColor(value["color"]); + klass.color_transition = parseTransition(value, "color"); } if (value.HasMember("width")) { klass.width = parseFunction(value["width"]); + klass.width_transition = parseTransition(value, "width"); } if (value.HasMember("opacity")) { klass.opacity = parseFunction(value["opacity"]); + klass.opacity_transition = parseTransition(value, "opacity"); } if (value.HasMember("dasharray")) { std::vector values = parseArray(value["dasharray"], 2); klass.dash_array = std::array {{ values[0], values[1] }}; + klass.dash_array_transition = parseTransition(value, "dasharray"); } return klass; @@ -564,6 +600,7 @@ IconClass StyleParser::parseIconClass(JSVal value) { if (value.HasMember("translate")) { std::vector values = parseArray(value["translate"], 2); klass.translate = std::array {{ values[0], values[1] }}; + klass.translate_transition = parseTransition(value, "translate"); } if (value.HasMember("translate-anchor")) { @@ -572,10 +609,12 @@ IconClass StyleParser::parseIconClass(JSVal value) { if (value.HasMember("color")) { klass.color = parseColor(value["color"]); + klass.color_transition = parseTransition(value, "color"); } if (value.HasMember("opacity")) { klass.opacity = parseFunction(value["opacity"]); + klass.opacity_transition = parseTransition(value, "opacity"); } if (value.HasMember("image")) { @@ -588,10 +627,12 @@ IconClass StyleParser::parseIconClass(JSVal value) { if (value.HasMember("radius")) { klass.radius = parseFunction(value["radius"]); + klass.radius_transition = parseTransition(value, "radius"); } if (value.HasMember("blur")) { klass.blur = parseFunction(value["blur"]); + klass.blur_transition = parseTransition(value, "blur"); } return klass; @@ -607,6 +648,7 @@ TextClass StyleParser::parseTextClass(JSVal value) { if (value.HasMember("translate")) { std::vector values = parseArray(value["translate"], 2); klass.translate = std::array {{ values[0], values[1] }}; + klass.translate_transition = parseTransition(value, "translate"); } if (value.HasMember("translate-anchor")) { @@ -615,18 +657,22 @@ TextClass StyleParser::parseTextClass(JSVal value) { if (value.HasMember("color")) { klass.color = parseColor(value["color"]); + klass.color_transition = parseTransition(value, "color"); } if (value.HasMember("stroke")) { klass.halo = parseColor(value["stroke"]); + klass.halo_transition = parseTransition(value, "stroke"); } if (value.HasMember("strokeWidth")) { klass.halo_radius = parseFunction(value["strokeWidth"]); + klass.halo_radius_transition = parseTransition(value, "strokeWidth"); } if (value.HasMember("strokeBlur")) { klass.halo_blur = parseFunction(value["strokeBlur"]); + klass.halo_blur_transition = parseTransition(value, "strokeBlur"); } if (value.HasMember("size")) { @@ -638,7 +684,12 @@ TextClass StyleParser::parseTextClass(JSVal value) { } if (value.HasMember("alwaysVisible")) { - klass.alwaysVisible = parseFunction(value["alwaysVisible"]); + klass.always_visible = parseFunction(value["alwaysVisible"]); + } + + if (value.HasMember("opacity")) { + klass.opacity = parseFunction(value["opacity"]); + klass.opacity_transition = parseTransition(value, "opacity"); } return klass; @@ -653,6 +704,7 @@ RasterClass StyleParser::parseRasterClass(JSVal value) { if (value.HasMember("opacity")) { klass.opacity = parseFunction(value["opacity"]); + klass.opacity_transition = parseTransition(value, "opacity"); } return klass; @@ -667,6 +719,7 @@ CompositeClass StyleParser::parseCompositeClass(JSVal value) { if (value.HasMember("opacity")) { klass.opacity = parseFunction(value["opacity"]); + klass.opacity_transition = parseTransition(value, "opacity"); } return klass; @@ -677,10 +730,12 @@ BackgroundClass StyleParser::parseBackgroundClass(JSVal value) { if (value.HasMember("color")) { klass.color = parseColor(value["color"]); + klass.color_transition = parseTransition(value, "color"); } if (value.HasMember("opacity")) { klass.opacity = parseFunction(value["opacity"]); + klass.opacity_transition = parseTransition(value, "opacity"); } return klass; diff --git a/src/util/raster.cpp b/src/util/raster.cpp index 4f26f8b55..e08145231 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(opacity, 1.0, opacity, start, 250_milliseconds); + fade_transition = std::make_shared>(opacity, 1.0, opacity, start, 250_milliseconds); } bool Raster::needsTransition() const { diff --git a/src/util/transition.cpp b/src/util/transition.cpp index af5b5a70a..d15e3ef66 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 transition::interpolateFloatArray(std::array from, std::array to, double t) const { + return {{ interpolateFloat(from[0], to[0], t), interpolateFloat(from[1], to[1], t) }}; +} + +template <> +transition::state ease_transition::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 = interpolateDouble(from, to, ease.solve(t, 0.001)); + return running; + } +} + +template <> +transition::state ease_transition::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::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>::update(llmr::time now) const { + float t = progress(now); + if (t >= 1) { + value = to; + return complete; + } else { + value = interpolateFloatArray(from, to, ease.solve(t, 0.001)); return running; } }