tauri-plugin-widgets 0.8.0

Tauri plugin for App Widgets on Android, iOS, and macOS (WidgetKit); Windows Widgets Board (Adaptive Cards) + desktop webview; Linux desktop webview with X11 DESKTOP pin.
Documentation
package git.s00d.widgets

import android.content.Context
import androidx.compose.runtime.Composable
import androidx.glance.GlanceModifier
import androidx.glance.layout.Box
import org.json.JSONObject

/** Ambient render state threaded through the element tree (host context, slot size, layout axis). */
internal data class RenderScope(
    val context: Context,
    val sizeFamily: String,
    val axis: Axis = Axis.Vertical,
    /** Element nesting level (depth guard, see [GLANCE_MAX_ELEMENT_DEPTH]). */
    val depth: Int = 0,
    /**
     * Estimated slot size in dp (0 = unknown). Glance gives no per-node constraints: the root
     * starts from LocalSize, stacks subtract their padding and a row splits its width between
     * flex children. Bitmap elements (charts) size themselves to it.
     */
    val slotW: Float = 0f,
    val slotH: Float = 0f,
) {
    enum class Axis { Vertical, Horizontal, Overlay }

    fun horizontal() = copy(axis = Axis.Horizontal)

    fun vertical() = copy(axis = Axis.Vertical)

    val inHorizontal: Boolean get() = axis == Axis.Horizontal

    /** Slot left inside `padding` (number or per-edge object, as [paddingModifier]). */
    fun insetBy(p: Any?): RenderScope {
        val (h, v) = when (p) {
            is Number -> p.toFloat() * 2 to p.toFloat() * 2
            is JSONObject -> (p.optDouble("leading", p.optDouble("start", 0.0)) + p.optDouble("trailing", p.optDouble("end", 0.0))).toFloat() to
                (p.optDouble("top", 0.0) + p.optDouble("bottom", 0.0)).toFloat()
            else -> 0f to 0f
        }
        return copy(slotW = (slotW - h).coerceAtLeast(0f), slotH = (slotH - v).coerceAtLeast(0f))
    }
}

@Composable
internal fun RenderElement(scope0: RenderScope, el: El, modifier: GlanceModifier = GlanceModifier) {
    // Glance rejects trees deeper than 50 emittables (the whole widget turns into the error
    // layout): drop the subtree instead and say so in the receipt.
    if (scope0.depth >= GLANCE_MAX_ELEMENT_DEPTH) {
        RenderTrace.skip(el.type, "depth")
        return
    }
    val scope = scope0.copy(depth = scope0.depth + 1)
    // `margin`: outer Box carries the parent's layout modifier (weight, fill) plus the
    // margin as padding; the element draws its background inside it.
    val margin = el.opt(WireField.MARGIN)
    if (margin is Number || margin is JSONObject) {
        Box(modifier = paddingModifier(modifier, margin)) {
            RenderElementBody(scope, el, GlanceModifier)
        }
    } else {
        RenderElementBody(scope, el, modifier)
    }
    // offsetX / offsetY / rotation / scale: RemoteViews have no transforms; the element
    // keeps its untransformed size and place (nothing is dropped). accentable: Apple only.
}

@Composable
private fun RenderElementBody(scope: RenderScope, el: El, modifier: GlanceModifier) {
    RenderTrace.type(el.type)
    val m = applyCommonStyle(scope.context, modifier, el)
    when (el.type) {
        "vstack", "hstack", "zstack", "grid", "container" -> RenderLayout(scope.insetBy(el.opt("padding")), el, m)
        "text", "label", "date", "timer" -> RenderText(scope, el, m)
        "image", "shape", "canvas" -> RenderMedia(scope, el, m)
        "progress", "gauge", "chart", "list" -> RenderData(scope, el, m)
        "button", "toggle", "link" -> RenderInteractive(scope, el, m)
        "spacer", "divider" -> RenderSpacing(scope, el, m)
        else -> {
            if (el.type.isNotBlank()) RenderTrace.skip(el.type, "unsupported")
            renderElementText(scope.context, el, el.type, m)
        }
    }
}

@Composable
internal fun RenderElement(
    context: Context,
    el: JSONObject,
    modifier: GlanceModifier = GlanceModifier,
    sizeFamily: String = "small",
    inHorizontal: Boolean = false,
) {
    val axis = if (inHorizontal) RenderScope.Axis.Horizontal else RenderScope.Axis.Vertical
    RenderElement(RenderScope(context, sizeFamily, axis), El(el), modifier)
}