From 5e25011690115a6787232c47c1d8558d1af82426 Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Wed, 20 May 2020 15:13:08 +0200 Subject: Support for jQuery animations (#154) --- src/main/kotlin/pl/treksoft/kvision/core/Types.kt | 1 - src/main/kotlin/pl/treksoft/kvision/core/Widget.kt | 199 ++++++++++++++++++++- src/main/kotlin/pl/treksoft/kvision/utils/Utils.kt | 23 +++ 3 files changed, 213 insertions(+), 10 deletions(-) (limited to 'src/main/kotlin/pl/treksoft/kvision') diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Types.kt b/src/main/kotlin/pl/treksoft/kvision/core/Types.kt index 9248020c..e59a5eeb 100644 --- a/src/main/kotlin/pl/treksoft/kvision/core/Types.kt +++ b/src/main/kotlin/pl/treksoft/kvision/core/Types.kt @@ -42,4 +42,3 @@ typealias StringBoolPair = Pair * This type is used for defining CSS dimensions (width, heights, margins, paddings, etc.). */ typealias CssSize = Pair - diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt index c0e684ba..ba4db0cc 100644 --- a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt +++ b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt @@ -35,17 +35,14 @@ import pl.treksoft.kvision.i18n.I18n import pl.treksoft.kvision.i18n.I18n.trans import pl.treksoft.kvision.panel.Root import pl.treksoft.kvision.state.ObservableState -import pl.treksoft.kvision.utils.SnOn -import pl.treksoft.kvision.utils.emptyOn -import pl.treksoft.kvision.utils.hooks -import pl.treksoft.kvision.utils.on -import pl.treksoft.kvision.utils.set -import pl.treksoft.kvision.utils.snAttrs -import pl.treksoft.kvision.utils.snClasses -import pl.treksoft.kvision.utils.snOpt -import pl.treksoft.kvision.utils.snStyle +import pl.treksoft.kvision.utils.* import kotlin.reflect.KProperty +enum class Easing(internal val easing: String) { + SWING("swing"), + LINEAR("linear") +} + /** * Base widget class. The parent of all component classes. * @@ -438,6 +435,162 @@ open class Widget(classes: Set = setOf()) : StyledComponent(), Component return this } + /** + * Shows current widget with animation effect. + * @param duration a duration of the animation + * @param easing an easing function to use + * @param complete a callback function called after the animation completes + * @return current widget + */ + open fun showAnim( + duration: Int = 400, + easing: Easing = Easing.SWING, + complete: (() -> Unit)? = null + ): Widget { + this.display = Display.NONE + this.visible = true + val jq = getElementJQuery() + if (jq != null) { + jq.show(duration, easing.easing) { + this.display = null + complete?.invoke() + } + } else { + this.display = null + complete?.invoke() + } + return this + } + + /** + * Hides current widget with animation effect. + * @param duration a duration of the animation + * @param easing an easing function to use + * @param complete a callback function called after the animation completes + * @return current widget + */ + open fun hideAnim( + duration: Int = 400, + easing: Easing = Easing.SWING, + complete: (() -> Unit)? = null + ): Widget { + val jq = getElementJQuery() + if (jq != null) { + jq.hide(duration, easing.easing) { + this.visible = false + complete?.invoke() + } + } else { + this.visible = false + complete?.invoke() + } + return this + } + + /** + * Shows current widget with slide down effect. + * @param duration a duration of the animation + * @param easing an easing function to use + * @param complete a callback function called after the animation completes + * @return current widget + */ + open fun slideDown( + duration: Int = 400, + easing: Easing = Easing.SWING, + complete: (() -> Unit)? = null + ): Widget { + this.display = Display.NONE + this.visible = true + val jq = getElementJQuery() + if (jq != null) { + jq.slideDown(duration, easing.easing) { + this.display = null + complete?.invoke() + } + } else { + this.display = null + complete?.invoke() + } + return this + } + + /** + * Hides current widget with slide up effect. + * @param duration a duration of the animation + * @param easing an easing function to use + * @param complete a callback function called after the animation completes + * @return current widget + */ + open fun slideUp( + duration: Int = 400, + easing: Easing = Easing.SWING, + complete: (() -> Unit)? = null + ): Widget { + val jq = getElementJQuery() + if (jq != null) { + jq.slideUp(duration, easing.easing) { + this.visible = false + complete?.invoke() + } + } else { + this.visible = false + complete?.invoke() + } + return this + } + + /** + * Shows current widget with fade in effect. + * @param duration a duration of the animation + * @param easing an easing function to use + * @param complete a callback function called after the animation completes + * @return current widget + */ + open fun fadeIn( + duration: Int = 400, + easing: Easing = Easing.SWING, + complete: (() -> Unit)? = null + ): Widget { + this.display = Display.NONE + this.visible = true + val jq = getElementJQuery() + if (jq != null) { + jq.fadeIn(duration, easing.easing) { + this.display = null + complete?.invoke() + } + } else { + this.display = null + complete?.invoke() + } + return this + } + + /** + * Hides current widget with fade out effect. + * @param duration a duration of the animation + * @param easing an easing function to use + * @param complete a callback function called after the animation completes + * @return current widget + */ + open fun fadeOut( + duration: Int = 400, + easing: Easing = Easing.SWING, + complete: (() -> Unit)? = null + ): Widget { + val jq = getElementJQuery() + if (jq != null) { + jq.fadeOut(duration, easing.easing) { + this.visible = false + complete?.invoke() + } + } else { + this.visible = false + complete?.invoke() + } + return this + } + /** * Enables tooltip for the current widget. * @param options tooltip options @@ -754,6 +907,34 @@ open class Widget(classes: Set = setOf()) : StyledComponent(), Component } } + /** + * Animate the widget changing CSS properties. + * @param duration a duration of the animation + * @param easing an easing function to use + * @param complete a callback function called after the animation completes + * @param styles changing properties values + */ + open fun animate( + duration: Int = 400, + easing: Easing = Easing.SWING, + complete: (() -> Unit)? = null, + styles: StyledComponent.() -> Unit + ) { + val widget = Widget() + widget.styles() + val stylesList = widget.getSnStyle() + val obj = js("{}") + stylesList.forEach { (key, value) -> + obj[key.toCamelCase()] = value + } + @Suppress("UnsafeCastFromDynamic") + getElementJQuery()?.animate(obj, duration, easing.easing) { + widget.dispose() + this.styles() + complete?.invoke() + } + } + /** * @suppress * Internal function diff --git a/src/main/kotlin/pl/treksoft/kvision/utils/Utils.kt b/src/main/kotlin/pl/treksoft/kvision/utils/Utils.kt index e2833630..995b0466 100644 --- a/src/main/kotlin/pl/treksoft/kvision/utils/Utils.kt +++ b/src/main/kotlin/pl/treksoft/kvision/utils/Utils.kt @@ -161,6 +161,20 @@ fun CssSize.asString(): String { } } +/** + * Extension operator to increase CssSize units. + */ +operator fun CssSize?.plus(i: Number): CssSize { + return this?.let { CssSize(it.first.toDouble() + i.toDouble(), it.second) } ?: CssSize(i, UNIT.px) +} + +/** + * Extension operator to decrease CssSize units. + */ +operator fun CssSize?.minus(i: Number): CssSize { + return this?.let { CssSize(it.first.toDouble() - i.toDouble(), it.second) } ?: CssSize(i, UNIT.px) +} + private val hex = arrayOf("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f") /** @@ -208,3 +222,12 @@ val String?.set: Set get() { return this?.split(Regex("\\s+"))?.toSet() ?: setOf() } + +/** + * Utility extension function to convert string from kebab-case to camelCase. + */ +fun String.toCamelCase(): String { + return this.replace(Regex("(\\-\\w)")) { + it.value.drop(1).toUpperCase() + } +} -- cgit