All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.io.nacular.doodle.theme.Theme.kt Maven / Gradle / Ivy

There is a newer version: 0.10.4
Show newest version
package io.nacular.doodle.theme

import io.nacular.doodle.core.Display
import io.nacular.doodle.core.Internal
import io.nacular.doodle.core.View
import io.nacular.doodle.utils.BreadthFirstTreeIterator
import io.nacular.doodle.utils.Node
import io.nacular.doodle.utils.ObservableSet
import io.nacular.doodle.utils.PropertyObservers
import io.nacular.doodle.utils.PropertyObserversImpl
import kotlin.properties.Delegates.observable


/**
 * Themes are able to visually style [View]s within the [Display]. Installing one will trigger an update and provide the full set of [View]s
 * to the [Theme.install] method, allowing it to update any subset of [View]s it chooses.
 */
public interface Theme {
    /**
     * Called whenever a Theme is set as [ThemeManager.selected]. This allows the theme to update any of the [View]s present in the [Display].
     *
     * @param display
     * @param all the Views (recursively) within the Display
     */
    public fun install(display: Display, all: Sequence)

    // FIXME: Add uninstall once there's a clean way to support that given ad hoc behavior registration
}

/**
 * This manager keeps track of available [Theme]s and manages the application of new ones via [ThemeManager.selected].
 */
public interface ThemeManager {
    /** Convenient set of [Theme]s that an application can manage */
    public val themes: ObservableSet

    /**
     * The currently selected [Theme]. Setting this will cause the new Theme to update the [Display] and [View]s therein.
     * A theme that is set as selected is also added to the [themes] set.
     */
    public var selected: Theme?

    /**
     * Notifies of changes to [selected]
     */
    public val selectionChanged: PropertyObservers
}

@Internal
public abstract class InternalThemeManager internal constructor(): ThemeManager {
    internal abstract fun update(view: View)
}

@Internal
public class ThemeManagerImpl(private val display: Display): InternalThemeManager() {
    override val themes: ObservableSet by lazy { ObservableSet() }

    override var selected: Theme? by observable(null) { _,old,new ->
        new?.apply {
            themes += this
            install(display, allViews)
        }

        (selectionChanged as PropertyObserversImpl).forEach { it(this, old, new) }
    }

    override val selectionChanged: PropertyObservers by lazy { PropertyObserversImpl(this) }

    override fun update(view: View) {
        if (view.acceptsThemes) {
            selected?.install(display, sequenceOf(view))
        }
    }

    private val allViews: Sequence get() = Sequence { BreadthFirstTreeIterator(DummyRoot(display.children)) }.drop(1).filter { it.acceptsThemes }
}

private class DummyRoot(children: List): Node {
    override val value    = object: View() {}
    override val children = children.asSequence().map { NodeAdapter(it) }
}

private class NodeAdapter(override val value: View): Node {
    override val children get() = value.children_.asSequence().map { NodeAdapter(it) }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy