mirror of
https://github.com/JakeWharton/mosaic.git
synced 2025-11-01 20:20:19 +08:00
Create new static logging system (#777)
`Static` function is now called `StaticEffect` to better indicate that it only renders its content once. `LocalStaticLogger` composition local provides access to `StaticLogger` which allows logging plain strings at arbitrary points for inclusion in the next frame. This can be used from effects, callback, state classes, etc.
This commit is contained in:
@ -7,10 +7,12 @@ New:
|
||||
- Add `focused` and `darkTheme` booleans to `Terminal` (available through `LocalTerminal`). These default to true and false, respectively, but will be updated if the terminal supports sending change notifications.
|
||||
- Bind `Terminal.focused` to a `Lifecycle` and expose into the composition as `LocalLifecycleOwner`. This allows using Compose lifecycle helpers such as `LifecycleResumeEffect` and others.
|
||||
- Underline styles (single, double, dashed, dotted, curved) and colors can now be specified for text and annotated string spans.
|
||||
- `LocalStaticLogger` composition local provides access to `StaticLogger` which allows logging plain strings at arbitrary points for inclusion in the next frame. This can be used from effects, callback, state classes, etc.
|
||||
|
||||
Changed:
|
||||
- Switched to our own terminal integration library. Report any issues with keyboard input, incorrect size reporting, or garbled output.
|
||||
- Only disable the cursor and emit synchronized rendering markers if the terminal reports support for those features.
|
||||
- `Static` function is now called `StaticEffect` to better indicate that it only renders its content once.
|
||||
|
||||
Fixed:
|
||||
- Prevent final character from being erased when a row writes into the last column of the terminal.
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
public abstract interface class com/jakewharton/mosaic/Mosaic {
|
||||
public abstract fun awaitComplete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun cancel ()V
|
||||
public abstract fun dump ()Ljava/lang/String;
|
||||
public abstract fun paint ()Lcom/jakewharton/mosaic/TextCanvas;
|
||||
public fun paintStatics ()Ljava/util/List;
|
||||
public abstract fun paintStaticsTo (Landroidx/collection/MutableObjectList;)V
|
||||
public abstract fun draw ()Lcom/jakewharton/mosaic/TextCanvas;
|
||||
public abstract fun dumpNodes ()Ljava/lang/String;
|
||||
public abstract fun setContent (Lkotlin/jvm/functions/Function2;)V
|
||||
public abstract fun static ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class com/jakewharton/mosaic/MosaicKt {
|
||||
@ -604,8 +603,16 @@ public final class com/jakewharton/mosaic/ui/SpacerKt {
|
||||
}
|
||||
|
||||
public final class com/jakewharton/mosaic/ui/Static {
|
||||
public static final fun Static (Landroidx/compose/runtime/snapshots/SnapshotStateList;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;I)V
|
||||
public static final fun Static (Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V
|
||||
public static final fun StaticEffect (Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V
|
||||
public static final fun getLocalStaticLogger ()Landroidx/compose/runtime/ProvidableCompositionLocal;
|
||||
}
|
||||
|
||||
public final class com/jakewharton/mosaic/ui/StaticLogger {
|
||||
public static final field $stable I
|
||||
public final fun log (Lcom/jakewharton/mosaic/TextCanvas;)V
|
||||
public final fun log (Ljava/lang/String;)V
|
||||
public final fun plusAssign (Lcom/jakewharton/mosaic/TextCanvas;)V
|
||||
public final fun plusAssign (Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class com/jakewharton/mosaic/ui/Text {
|
||||
|
||||
@ -198,12 +198,11 @@ abstract interface com.jakewharton.mosaic.ui/RowScope { // com.jakewharton.mosai
|
||||
|
||||
abstract interface com.jakewharton.mosaic/Mosaic { // com.jakewharton.mosaic/Mosaic|null[0]
|
||||
abstract fun cancel() // com.jakewharton.mosaic/Mosaic.cancel|cancel(){}[0]
|
||||
abstract fun dump(): kotlin/String // com.jakewharton.mosaic/Mosaic.dump|dump(){}[0]
|
||||
abstract fun paint(): com.jakewharton.mosaic/TextCanvas // com.jakewharton.mosaic/Mosaic.paint|paint(){}[0]
|
||||
abstract fun paintStaticsTo(androidx.collection/MutableObjectList<com.jakewharton.mosaic/TextCanvas>) // com.jakewharton.mosaic/Mosaic.paintStaticsTo|paintStaticsTo(androidx.collection.MutableObjectList<com.jakewharton.mosaic.TextCanvas>){}[0]
|
||||
abstract fun draw(): com.jakewharton.mosaic/TextCanvas // com.jakewharton.mosaic/Mosaic.draw|draw(){}[0]
|
||||
abstract fun dumpNodes(): kotlin/String // com.jakewharton.mosaic/Mosaic.dumpNodes|dumpNodes(){}[0]
|
||||
abstract fun setContent(kotlin/Function2<androidx.compose.runtime/Composer, kotlin/Int, kotlin/Unit>) // com.jakewharton.mosaic/Mosaic.setContent|setContent(kotlin.Function2<androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>){}[0]
|
||||
abstract fun static(): kotlin/String? // com.jakewharton.mosaic/Mosaic.static|static(){}[0]
|
||||
abstract suspend fun awaitComplete() // com.jakewharton.mosaic/Mosaic.awaitComplete|awaitComplete(){}[0]
|
||||
open fun paintStatics(): kotlin.collections/List<com.jakewharton.mosaic/TextCanvas> // com.jakewharton.mosaic/Mosaic.paintStatics|paintStatics(){}[0]
|
||||
}
|
||||
|
||||
abstract interface com.jakewharton.mosaic/TextCanvas { // com.jakewharton.mosaic/TextCanvas|null[0]
|
||||
@ -394,6 +393,13 @@ final class com.jakewharton.mosaic.ui/BiasAlignment : com.jakewharton.mosaic.ui/
|
||||
}
|
||||
}
|
||||
|
||||
final class com.jakewharton.mosaic.ui/StaticLogger { // com.jakewharton.mosaic.ui/StaticLogger|null[0]
|
||||
final fun log(com.jakewharton.mosaic/TextCanvas) // com.jakewharton.mosaic.ui/StaticLogger.log|log(com.jakewharton.mosaic.TextCanvas){}[0]
|
||||
final fun log(kotlin/String) // com.jakewharton.mosaic.ui/StaticLogger.log|log(kotlin.String){}[0]
|
||||
final inline fun plusAssign(com.jakewharton.mosaic/TextCanvas) // com.jakewharton.mosaic.ui/StaticLogger.plusAssign|plusAssign(com.jakewharton.mosaic.TextCanvas){}[0]
|
||||
final inline fun plusAssign(kotlin/String) // com.jakewharton.mosaic.ui/StaticLogger.plusAssign|plusAssign(kotlin.String){}[0]
|
||||
}
|
||||
|
||||
final class com.jakewharton.mosaic/Terminal { // com.jakewharton.mosaic/Terminal|null[0]
|
||||
constructor <init>(kotlin/Boolean, kotlin/Boolean, com.jakewharton.mosaic.ui.unit/IntSize) // com.jakewharton.mosaic/Terminal.<init>|<init>(kotlin.Boolean;kotlin.Boolean;com.jakewharton.mosaic.ui.unit.IntSize){}[0]
|
||||
|
||||
@ -689,6 +695,8 @@ final val com.jakewharton.mosaic.text/com_jakewharton_mosaic_text_AnnotatedStrin
|
||||
final val com.jakewharton.mosaic.text/com_jakewharton_mosaic_text_SpanStyle$stableprop // com.jakewharton.mosaic.text/com_jakewharton_mosaic_text_SpanStyle$stableprop|#static{}com_jakewharton_mosaic_text_SpanStyle$stableprop[0]
|
||||
final val com.jakewharton.mosaic.text/com_jakewharton_mosaic_text_StringTextLayout$stableprop // com.jakewharton.mosaic.text/com_jakewharton_mosaic_text_StringTextLayout$stableprop|#static{}com_jakewharton_mosaic_text_StringTextLayout$stableprop[0]
|
||||
final val com.jakewharton.mosaic.text/com_jakewharton_mosaic_text_TextLayout$stableprop // com.jakewharton.mosaic.text/com_jakewharton_mosaic_text_TextLayout$stableprop|#static{}com_jakewharton_mosaic_text_TextLayout$stableprop[0]
|
||||
final val com.jakewharton.mosaic.ui/LocalStaticLogger // com.jakewharton.mosaic.ui/LocalStaticLogger|{}LocalStaticLogger[0]
|
||||
final fun <get-LocalStaticLogger>(): androidx.compose.runtime/ProvidableCompositionLocal<com.jakewharton.mosaic.ui/StaticLogger> // com.jakewharton.mosaic.ui/LocalStaticLogger.<get-LocalStaticLogger>|<get-LocalStaticLogger>(){}[0]
|
||||
final val com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_Arrangement$stableprop // com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_Arrangement$stableprop|#static{}com_jakewharton_mosaic_ui_Arrangement$stableprop[0]
|
||||
final val com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_Arrangement_Absolute$stableprop // com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_Arrangement_Absolute$stableprop|#static{}com_jakewharton_mosaic_ui_Arrangement_Absolute$stableprop[0]
|
||||
final val com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_Arrangement_SpacedAligned$stableprop // com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_Arrangement_SpacedAligned$stableprop|#static{}com_jakewharton_mosaic_ui_Arrangement_SpacedAligned$stableprop[0]
|
||||
@ -708,7 +716,7 @@ final val com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_RowColumnMeasurePo
|
||||
final val com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_RowColumnMeasurementHelper$stableprop // com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_RowColumnMeasurementHelper$stableprop|#static{}com_jakewharton_mosaic_ui_RowColumnMeasurementHelper$stableprop[0]
|
||||
final val com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_RowColumnParentData$stableprop // com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_RowColumnParentData$stableprop|#static{}com_jakewharton_mosaic_ui_RowColumnParentData$stableprop[0]
|
||||
final val com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_RowScopeInstance$stableprop // com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_RowScopeInstance$stableprop|#static{}com_jakewharton_mosaic_ui_RowScopeInstance$stableprop[0]
|
||||
final val com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_StaticState$stableprop // com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_StaticState$stableprop|#static{}com_jakewharton_mosaic_ui_StaticState$stableprop[0]
|
||||
final val com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_StaticLogger$stableprop // com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_StaticLogger$stableprop|#static{}com_jakewharton_mosaic_ui_StaticLogger$stableprop[0]
|
||||
final val com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_VerticalAlignModifier$stableprop // com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_VerticalAlignModifier$stableprop|#static{}com_jakewharton_mosaic_ui_VerticalAlignModifier$stableprop[0]
|
||||
final val com.jakewharton.mosaic.ui/isEmptyTextStyle // com.jakewharton.mosaic.ui/isEmptyTextStyle|@com.jakewharton.mosaic.ui.TextStyle{}isEmptyTextStyle[0]
|
||||
final inline fun (com.jakewharton.mosaic.ui/TextStyle).<get-isEmptyTextStyle>(): kotlin/Boolean // com.jakewharton.mosaic.ui/isEmptyTextStyle.<get-isEmptyTextStyle>|<get-isEmptyTextStyle>@com.jakewharton.mosaic.ui.TextStyle(){}[0]
|
||||
@ -788,7 +796,6 @@ final fun (com.jakewharton.mosaic.ui.unit/Constraints).com.jakewharton.mosaic.ui
|
||||
final fun (com.jakewharton.mosaic.ui.unit/Constraints).com.jakewharton.mosaic.ui.unit/constrainWidth(kotlin/Int): kotlin/Int // com.jakewharton.mosaic.ui.unit/constrainWidth|constrainWidth@com.jakewharton.mosaic.ui.unit.Constraints(kotlin.Int){}[0]
|
||||
final fun (com.jakewharton.mosaic.ui.unit/Constraints).com.jakewharton.mosaic.ui.unit/isSatisfiedBy(com.jakewharton.mosaic.ui.unit/IntSize): kotlin/Boolean // com.jakewharton.mosaic.ui.unit/isSatisfiedBy|isSatisfiedBy@com.jakewharton.mosaic.ui.unit.Constraints(com.jakewharton.mosaic.ui.unit.IntSize){}[0]
|
||||
final fun (com.jakewharton.mosaic.ui.unit/Constraints).com.jakewharton.mosaic.ui.unit/offset(kotlin/Int = ..., kotlin/Int = ...): com.jakewharton.mosaic.ui.unit/Constraints // com.jakewharton.mosaic.ui.unit/offset|offset@com.jakewharton.mosaic.ui.unit.Constraints(kotlin.Int;kotlin.Int){}[0]
|
||||
final fun <#A: kotlin/Any?> com.jakewharton.mosaic.ui/Static(androidx.compose.runtime.snapshots/SnapshotStateList<#A>, kotlin/Function3<#A, androidx.compose.runtime/Composer, kotlin/Int, kotlin/Unit>, androidx.compose.runtime/Composer?, kotlin/Int) // com.jakewharton.mosaic.ui/Static|Static(androidx.compose.runtime.snapshots.SnapshotStateList<0:0>;kotlin.Function3<0:0,androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>;androidx.compose.runtime.Composer?;kotlin.Int){0§<kotlin.Any?>}[0]
|
||||
final fun com.jakewharton.mosaic.layout/com_jakewharton_mosaic_layout_DrawStyle_Fill$stableprop_getter(): kotlin/Int // com.jakewharton.mosaic.layout/com_jakewharton_mosaic_layout_DrawStyle_Fill$stableprop_getter|com_jakewharton_mosaic_layout_DrawStyle_Fill$stableprop_getter(){}[0]
|
||||
final fun com.jakewharton.mosaic.layout/com_jakewharton_mosaic_layout_DrawStyle_Stroke$stableprop_getter(): kotlin/Int // com.jakewharton.mosaic.layout/com_jakewharton_mosaic_layout_DrawStyle_Stroke$stableprop_getter|com_jakewharton_mosaic_layout_DrawStyle_Stroke$stableprop_getter(){}[0]
|
||||
final fun com.jakewharton.mosaic.layout/com_jakewharton_mosaic_layout_KeyEvent$stableprop_getter(): kotlin/Int // com.jakewharton.mosaic.layout/com_jakewharton_mosaic_layout_KeyEvent$stableprop_getter|com_jakewharton_mosaic_layout_KeyEvent$stableprop_getter(){}[0]
|
||||
@ -820,7 +827,7 @@ final fun com.jakewharton.mosaic.ui/Filler(kotlin/Int, com.jakewharton.mosaic.mo
|
||||
final fun com.jakewharton.mosaic.ui/Layout(kotlin/Function2<androidx.compose.runtime/Composer, kotlin/Int, kotlin/Unit>, com.jakewharton.mosaic.modifier/Modifier?, kotlin/Function0<kotlin/String>?, com.jakewharton.mosaic.layout/MeasurePolicy, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.jakewharton.mosaic.ui/Layout|Layout(kotlin.Function2<androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>;com.jakewharton.mosaic.modifier.Modifier?;kotlin.Function0<kotlin.String>?;com.jakewharton.mosaic.layout.MeasurePolicy;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0]
|
||||
final fun com.jakewharton.mosaic.ui/Row(com.jakewharton.mosaic.modifier/Modifier?, com.jakewharton.mosaic.ui/Arrangement.Horizontal?, com.jakewharton.mosaic.ui/Alignment.Vertical?, kotlin/Function3<com.jakewharton.mosaic.ui/RowScope, androidx.compose.runtime/Composer, kotlin/Int, kotlin/Unit>, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.jakewharton.mosaic.ui/Row|Row(com.jakewharton.mosaic.modifier.Modifier?;com.jakewharton.mosaic.ui.Arrangement.Horizontal?;com.jakewharton.mosaic.ui.Alignment.Vertical?;kotlin.Function3<com.jakewharton.mosaic.ui.RowScope,androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0]
|
||||
final fun com.jakewharton.mosaic.ui/Spacer(com.jakewharton.mosaic.modifier/Modifier?, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.jakewharton.mosaic.ui/Spacer|Spacer(com.jakewharton.mosaic.modifier.Modifier?;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0]
|
||||
final fun com.jakewharton.mosaic.ui/Static(kotlin/Function2<androidx.compose.runtime/Composer, kotlin/Int, kotlin/Unit>, androidx.compose.runtime/Composer?, kotlin/Int) // com.jakewharton.mosaic.ui/Static|Static(kotlin.Function2<androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>;androidx.compose.runtime.Composer?;kotlin.Int){}[0]
|
||||
final fun com.jakewharton.mosaic.ui/StaticEffect(kotlin/Function2<androidx.compose.runtime/Composer, kotlin/Int, kotlin/Unit>, androidx.compose.runtime/Composer?, kotlin/Int) // com.jakewharton.mosaic.ui/StaticEffect|StaticEffect(kotlin.Function2<androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>;androidx.compose.runtime.Composer?;kotlin.Int){}[0]
|
||||
final fun com.jakewharton.mosaic.ui/Text(com.jakewharton.mosaic.text/AnnotatedString, com.jakewharton.mosaic.modifier/Modifier?, com.jakewharton.mosaic.ui/Color, com.jakewharton.mosaic.ui/Color, com.jakewharton.mosaic.ui/TextStyle, com.jakewharton.mosaic.ui/UnderlineStyle, com.jakewharton.mosaic.ui/Color, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.jakewharton.mosaic.ui/Text|Text(com.jakewharton.mosaic.text.AnnotatedString;com.jakewharton.mosaic.modifier.Modifier?;com.jakewharton.mosaic.ui.Color;com.jakewharton.mosaic.ui.Color;com.jakewharton.mosaic.ui.TextStyle;com.jakewharton.mosaic.ui.UnderlineStyle;com.jakewharton.mosaic.ui.Color;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0]
|
||||
final fun com.jakewharton.mosaic.ui/Text(kotlin/String, com.jakewharton.mosaic.modifier/Modifier?, com.jakewharton.mosaic.ui/Color, com.jakewharton.mosaic.ui/Color, com.jakewharton.mosaic.ui/TextStyle, com.jakewharton.mosaic.ui/UnderlineStyle, com.jakewharton.mosaic.ui/Color, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.jakewharton.mosaic.ui/Text|Text(kotlin.String;com.jakewharton.mosaic.modifier.Modifier?;com.jakewharton.mosaic.ui.Color;com.jakewharton.mosaic.ui.Color;com.jakewharton.mosaic.ui.TextStyle;com.jakewharton.mosaic.ui.UnderlineStyle;com.jakewharton.mosaic.ui.Color;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0]
|
||||
final fun com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_Arrangement$stableprop_getter(): kotlin/Int // com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_Arrangement$stableprop_getter|com_jakewharton_mosaic_ui_Arrangement$stableprop_getter(){}[0]
|
||||
@ -842,7 +849,7 @@ final fun com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_RowColumnMeasurePo
|
||||
final fun com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_RowColumnMeasurementHelper$stableprop_getter(): kotlin/Int // com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_RowColumnMeasurementHelper$stableprop_getter|com_jakewharton_mosaic_ui_RowColumnMeasurementHelper$stableprop_getter(){}[0]
|
||||
final fun com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_RowColumnParentData$stableprop_getter(): kotlin/Int // com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_RowColumnParentData$stableprop_getter|com_jakewharton_mosaic_ui_RowColumnParentData$stableprop_getter(){}[0]
|
||||
final fun com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_RowScopeInstance$stableprop_getter(): kotlin/Int // com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_RowScopeInstance$stableprop_getter|com_jakewharton_mosaic_ui_RowScopeInstance$stableprop_getter(){}[0]
|
||||
final fun com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_StaticState$stableprop_getter(): kotlin/Int // com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_StaticState$stableprop_getter|com_jakewharton_mosaic_ui_StaticState$stableprop_getter(){}[0]
|
||||
final fun com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_StaticLogger$stableprop_getter(): kotlin/Int // com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_StaticLogger$stableprop_getter|com_jakewharton_mosaic_ui_StaticLogger$stableprop_getter(){}[0]
|
||||
final fun com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_VerticalAlignModifier$stableprop_getter(): kotlin/Int // com.jakewharton.mosaic.ui/com_jakewharton_mosaic_ui_VerticalAlignModifier$stableprop_getter|com_jakewharton_mosaic_ui_VerticalAlignModifier$stableprop_getter(){}[0]
|
||||
final fun com.jakewharton.mosaic/Mosaic(kotlin.coroutines/CoroutineContext, kotlin/Function1<com.jakewharton.mosaic/Mosaic, kotlin/Unit>, kotlinx.coroutines.channels/Channel<com.jakewharton.mosaic.layout/KeyEvent>, androidx.compose.runtime/State<com.jakewharton.mosaic/Terminal>): com.jakewharton.mosaic/Mosaic // com.jakewharton.mosaic/Mosaic|Mosaic(kotlin.coroutines.CoroutineContext;kotlin.Function1<com.jakewharton.mosaic.Mosaic,kotlin.Unit>;kotlinx.coroutines.channels.Channel<com.jakewharton.mosaic.layout.KeyEvent>;androidx.compose.runtime.State<com.jakewharton.mosaic.Terminal>){}[0]
|
||||
final fun com.jakewharton.mosaic/com_jakewharton_mosaic_AnsiRendering$stableprop_getter(): kotlin/Int // com.jakewharton.mosaic/com_jakewharton_mosaic_AnsiRendering$stableprop_getter|com_jakewharton_mosaic_AnsiRendering$stableprop_getter(){}[0]
|
||||
|
||||
@ -1,20 +1,17 @@
|
||||
package com.jakewharton.mosaic.layout
|
||||
|
||||
import androidx.collection.MutableObjectList
|
||||
import com.jakewharton.mosaic.TextCanvas
|
||||
import com.jakewharton.mosaic.TextSurface
|
||||
import com.jakewharton.mosaic.layout.Placeable.PlacementScope
|
||||
import com.jakewharton.mosaic.modifier.Modifier
|
||||
import com.jakewharton.mosaic.ui.StaticState
|
||||
import com.jakewharton.mosaic.ui.unit.Constraints
|
||||
|
||||
internal fun interface DebugPolicy {
|
||||
fun MosaicNode.renderDebug(): String
|
||||
}
|
||||
|
||||
internal abstract class MosaicNodeLayer(
|
||||
private val isStatic: Boolean,
|
||||
) : Placeable(),
|
||||
internal abstract class MosaicNodeLayer :
|
||||
Placeable(),
|
||||
Measurable,
|
||||
PlacementScope,
|
||||
MeasureScope {
|
||||
@ -49,13 +46,8 @@ internal abstract class MosaicNodeLayer(
|
||||
private set
|
||||
|
||||
final override fun placeAt(x: Int, y: Int) {
|
||||
// If this layer belongs to a static node, ignore the placement coordinates from the parent.
|
||||
// We reset the coordinate system to draw at 0,0 since static drawing will be on a canvas
|
||||
// sized to this node's width and height.
|
||||
if (!isStatic) {
|
||||
this.x = x
|
||||
this.y = y
|
||||
}
|
||||
this.x = x
|
||||
this.y = y
|
||||
measureResult.placeChildren()
|
||||
}
|
||||
|
||||
@ -96,7 +88,6 @@ internal class MosaicNode(
|
||||
val isStatic: Boolean,
|
||||
) : Measurable {
|
||||
val children = ArrayList<MosaicNode>()
|
||||
var staticState: StaticState? = null
|
||||
|
||||
private val bottomLayer: MosaicNodeLayer = BottomLayer(this)
|
||||
var topLayer: MosaicNodeLayer = bottomLayer
|
||||
@ -149,34 +140,12 @@ internal class MosaicNode(
|
||||
* Draw this node to a [TextSurface].
|
||||
* A call to [measureAndPlace] must precede calls to this function.
|
||||
*/
|
||||
fun paint(): TextCanvas {
|
||||
fun draw(): TextCanvas {
|
||||
val surface = TextSurface(width, height)
|
||||
topLayer.drawTo(surface)
|
||||
return surface
|
||||
}
|
||||
|
||||
/**
|
||||
* Append any static [TextSurfaces][TextSurface] to [statics].
|
||||
* A call to [measureAndPlace] must precede calls to this function.
|
||||
*/
|
||||
fun paintStaticsTo(statics: MutableObjectList<TextCanvas>) {
|
||||
if (!isStatic) {
|
||||
for (index in children.indices) {
|
||||
children[index].paintStaticsTo(statics)
|
||||
}
|
||||
return
|
||||
}
|
||||
staticState?.let { staticState ->
|
||||
for (index in children.indices) {
|
||||
val child = children[index]
|
||||
statics += child.paint()
|
||||
child.paintStaticsTo(statics)
|
||||
}
|
||||
children.clear()
|
||||
this.staticState = null
|
||||
}
|
||||
}
|
||||
|
||||
fun sendKeyEvent(keyEvent: KeyEvent): Boolean {
|
||||
return topLayer.sendKeyEvent(keyEvent)
|
||||
}
|
||||
@ -202,7 +171,7 @@ internal class MosaicNode(
|
||||
|
||||
private class BottomLayer(
|
||||
private val node: MosaicNode,
|
||||
) : MosaicNodeLayer(node.isStatic) {
|
||||
) : MosaicNodeLayer() {
|
||||
override val next: MosaicNodeLayer? get() = null
|
||||
|
||||
override fun doMeasure(constraints: Constraints): MeasureResult {
|
||||
@ -246,7 +215,7 @@ private class BottomLayer(
|
||||
private class LayoutLayer(
|
||||
private val element: LayoutModifier,
|
||||
override val next: MosaicNodeLayer,
|
||||
) : MosaicNodeLayer(false) {
|
||||
) : MosaicNodeLayer() {
|
||||
override fun doMeasure(constraints: Constraints): MeasureResult {
|
||||
return element.run { measure(next, constraints) }
|
||||
}
|
||||
@ -271,7 +240,7 @@ private class LayoutLayer(
|
||||
private class DrawLayer(
|
||||
private val element: DrawModifier,
|
||||
override val next: MosaicNodeLayer,
|
||||
) : MosaicNodeLayer(false) {
|
||||
) : MosaicNodeLayer() {
|
||||
override fun drawTo(canvas: TextSurface) {
|
||||
val oldX = canvas.translationX
|
||||
val oldY = canvas.translationY
|
||||
@ -291,7 +260,7 @@ private class DrawLayer(
|
||||
private class KeyLayer(
|
||||
private val element: KeyModifier,
|
||||
override val next: MosaicNodeLayer,
|
||||
) : MosaicNodeLayer(false) {
|
||||
) : MosaicNodeLayer() {
|
||||
override fun sendKeyEvent(keyEvent: KeyEvent) =
|
||||
element.onPreKeyEvent(keyEvent) ||
|
||||
next.sendKeyEvent(keyEvent) ||
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
package com.jakewharton.mosaic
|
||||
|
||||
import androidx.collection.MutableObjectList
|
||||
import androidx.collection.mutableObjectListOf
|
||||
import androidx.collection.mutableScatterSetOf
|
||||
import androidx.compose.runtime.AbstractApplier
|
||||
import androidx.compose.runtime.BroadcastFrameClock
|
||||
@ -37,7 +35,10 @@ import com.jakewharton.mosaic.terminal.event.OperatingStatusResponseEvent
|
||||
import com.jakewharton.mosaic.terminal.event.PrimaryDeviceAttributesEvent
|
||||
import com.jakewharton.mosaic.terminal.event.ResizeEvent
|
||||
import com.jakewharton.mosaic.terminal.event.SystemThemeEvent
|
||||
import com.jakewharton.mosaic.ui.AnsiLevel
|
||||
import com.jakewharton.mosaic.ui.BoxMeasurePolicy
|
||||
import com.jakewharton.mosaic.ui.LocalStaticLogger
|
||||
import com.jakewharton.mosaic.ui.StaticLogger
|
||||
import com.jakewharton.mosaic.ui.unit.IntSize
|
||||
import kotlin.concurrent.Volatile
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
@ -285,7 +286,7 @@ public suspend fun runMosaic(content: @Composable () -> Unit) {
|
||||
AnsiRendering(ansiLevel, supportsSynchronizedRendering, supportsKittyUnderlines)
|
||||
}
|
||||
|
||||
runMosaicComposition(rendering, keyEvents, terminalState, content)
|
||||
runMosaicComposition(rendering, keyEvents, terminalState, ansiLevel, supportsKittyUnderlines, content)
|
||||
|
||||
eventJob.cancel()
|
||||
},
|
||||
@ -296,6 +297,8 @@ internal suspend fun runMosaicComposition(
|
||||
rendering: Rendering,
|
||||
keyEvents: Channel<KeyEvent>,
|
||||
terminalState: MutableState<Terminal>,
|
||||
ansiLevel: AnsiLevel,
|
||||
supportsKittyUnderlines: Boolean,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val clock = BroadcastFrameClock()
|
||||
@ -306,6 +309,8 @@ internal suspend fun runMosaicComposition(
|
||||
},
|
||||
keyEvents = keyEvents,
|
||||
terminalState = terminalState,
|
||||
ansiLevel = ansiLevel,
|
||||
supportsKittyUnderlines = supportsKittyUnderlines,
|
||||
)
|
||||
|
||||
mosaicComposition.setContent(content)
|
||||
@ -332,15 +337,9 @@ internal inline fun <T> MutableState<T>.update(updater: T.() -> T) {
|
||||
public interface Mosaic {
|
||||
public fun setContent(content: @Composable () -> Unit)
|
||||
|
||||
public fun paint(): TextCanvas
|
||||
public fun paintStaticsTo(list: MutableObjectList<TextCanvas>)
|
||||
public fun paintStatics(): List<TextCanvas> {
|
||||
return mutableObjectListOf<TextCanvas>()
|
||||
.apply(::paintStaticsTo)
|
||||
.asList()
|
||||
}
|
||||
|
||||
public fun dump(): String
|
||||
public fun draw(): TextCanvas
|
||||
public fun static(): String?
|
||||
public fun dumpNodes(): String
|
||||
|
||||
public suspend fun awaitComplete()
|
||||
public fun cancel()
|
||||
@ -353,7 +352,7 @@ public fun Mosaic(
|
||||
keyEvents: Channel<KeyEvent>,
|
||||
terminalState: State<Terminal>,
|
||||
): Mosaic {
|
||||
return MosaicComposition(coroutineContext, onDraw, keyEvents, terminalState)
|
||||
return MosaicComposition(coroutineContext, onDraw, keyEvents, terminalState, AnsiLevel.NONE, false)
|
||||
}
|
||||
|
||||
internal class MosaicComposition(
|
||||
@ -361,6 +360,9 @@ internal class MosaicComposition(
|
||||
private val onDraw: (Mosaic) -> Unit,
|
||||
private val keyEvents: Channel<KeyEvent>,
|
||||
private val terminalState: State<Terminal>,
|
||||
// TODO These two don't belong here!
|
||||
private val ansiLevel: AnsiLevel,
|
||||
private val supportsKittyUnderlines: Boolean,
|
||||
) : Mosaic,
|
||||
LifecycleOwner {
|
||||
private val externalClock = checkNotNull(coroutineContext[MonotonicFrameClock]) {
|
||||
@ -377,6 +379,9 @@ internal class MosaicComposition(
|
||||
private val recomposer = Recomposer(composeContext)
|
||||
private val composition = Composition(applier, recomposer)
|
||||
|
||||
private val staticLogs = Channel<Any>(UNLIMITED)
|
||||
private val staticLogger = StaticLogger(staticLogs) { needDraw = true }
|
||||
|
||||
override val lifecycle = LifecycleRegistry.createUnsafe(this).also { lifecycle ->
|
||||
scope.launch(start = UNDISPATCHED) {
|
||||
snapshotFlow { terminalState.value.focused }.collect { focused ->
|
||||
@ -421,17 +426,41 @@ internal class MosaicComposition(
|
||||
onDraw(this)
|
||||
}
|
||||
|
||||
override fun paint(): TextCanvas {
|
||||
override fun draw(): TextCanvas {
|
||||
return Snapshot.observe(readObserver = drawBlockStateReadObserver) {
|
||||
rootNode.paint()
|
||||
rootNode.draw()
|
||||
}
|
||||
}
|
||||
|
||||
override fun paintStaticsTo(list: MutableObjectList<TextCanvas>) {
|
||||
rootNode.paintStaticsTo(list)
|
||||
override fun static(): String? {
|
||||
var static = staticLogs.tryReceive().getOrNull()
|
||||
if (static == null) {
|
||||
return null
|
||||
}
|
||||
return buildString {
|
||||
do {
|
||||
when (static) {
|
||||
is String -> {
|
||||
append(static)
|
||||
append("\r\n")
|
||||
}
|
||||
is TextCanvas -> {
|
||||
for (row in 0 until static.height) {
|
||||
static.appendRowTo(this, row, ansiLevel, supportsKittyUnderlines)
|
||||
append("\r\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static = staticLogs.tryReceive().getOrNull()
|
||||
} while (static != null)
|
||||
|
||||
// Remove trailing "\r\n".
|
||||
setLength(length - 2)
|
||||
}
|
||||
}
|
||||
|
||||
override fun dump(): String {
|
||||
override fun dumpNodes(): String {
|
||||
return rootNode.toString()
|
||||
}
|
||||
|
||||
@ -494,6 +523,7 @@ internal class MosaicComposition(
|
||||
composition.setContent {
|
||||
CompositionLocalProvider(
|
||||
LocalTerminal provides terminalState.value,
|
||||
LocalStaticLogger provides staticLogger,
|
||||
LocalLifecycleOwner provides this,
|
||||
content = content,
|
||||
)
|
||||
@ -531,10 +561,9 @@ internal class MosaicComposition(
|
||||
}
|
||||
|
||||
internal class MosaicNodeApplier(
|
||||
root: MosaicNode? = null,
|
||||
private val onChanges: () -> Unit = {},
|
||||
) : AbstractApplier<MosaicNode>(
|
||||
root = root ?: MosaicNode(
|
||||
root = MosaicNode(
|
||||
measurePolicy = BoxMeasurePolicy(),
|
||||
debugPolicy = { children.joinToString(separator = "\n") },
|
||||
isStatic = false,
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package com.jakewharton.mosaic
|
||||
|
||||
import androidx.collection.mutableObjectListOf
|
||||
import com.jakewharton.mosaic.ui.AnsiLevel
|
||||
import kotlin.time.TimeMark
|
||||
import kotlin.time.TimeSource
|
||||
@ -41,18 +40,14 @@ internal class DebugRendering(
|
||||
lastRender = systemClock.markNow()
|
||||
|
||||
append("NODES:\r\n")
|
||||
append(mosaic.dump().replace("\n", "\r\n"))
|
||||
append(mosaic.dumpNodes().replace("\n", "\r\n"))
|
||||
append("\r\n\r\n")
|
||||
|
||||
val statics = mutableObjectListOf<TextCanvas>()
|
||||
try {
|
||||
mosaic.paintStaticsTo(statics)
|
||||
if (statics.isNotEmpty()) {
|
||||
mosaic.static()?.let { static ->
|
||||
append("STATIC:\r\n")
|
||||
statics.forEach { static ->
|
||||
appendSurface(static)
|
||||
}
|
||||
append("\r\n")
|
||||
append(static)
|
||||
append("\r\n\r\n")
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
failed = true
|
||||
@ -62,7 +57,7 @@ internal class DebugRendering(
|
||||
|
||||
append("OUTPUT:\r\n")
|
||||
try {
|
||||
appendSurface(mosaic.paint())
|
||||
appendSurface(mosaic.draw())
|
||||
} catch (t: Throwable) {
|
||||
failed = true
|
||||
append(t.stackTraceToString().replace("\n", "\r\n"))
|
||||
@ -81,7 +76,6 @@ internal class AnsiRendering(
|
||||
private val supportsKittyUnderlines: Boolean,
|
||||
) : Rendering {
|
||||
private val stringBuilder = StringBuilder(100)
|
||||
private val staticSurfaces = mutableObjectListOf<TextCanvas>()
|
||||
private var lastHeight = 0
|
||||
|
||||
override fun render(mosaic: Mosaic): CharSequence {
|
||||
@ -112,17 +106,20 @@ internal class AnsiRendering(
|
||||
}
|
||||
}
|
||||
|
||||
staticSurfaces.let { staticSurfaces ->
|
||||
mosaic.paintStaticsTo(staticSurfaces)
|
||||
if (staticSurfaces.isNotEmpty()) {
|
||||
staticSurfaces.forEach { staticSurface ->
|
||||
appendSurface(staticSurface)
|
||||
}
|
||||
staticSurfaces.clear()
|
||||
mosaic.static()?.let { static ->
|
||||
// We don't know the width or height of static strings, and we don't want to waste time
|
||||
// parsing them to get an accurate count of each to minimally clear only the lines it will
|
||||
// write. Instead, just clear everything to ensure we neither leave old cells nor attempt
|
||||
// a clear to end-of-line when the cursor is on the last column (thus clearing that cell).
|
||||
if (staleLines > 0) {
|
||||
append(clearDisplay)
|
||||
staleLines = 0
|
||||
}
|
||||
append(static)
|
||||
append("\r\n")
|
||||
}
|
||||
|
||||
val surface = mosaic.paint()
|
||||
val surface = mosaic.draw()
|
||||
appendSurface(surface)
|
||||
|
||||
// If the new output contains fewer lines than the last output, clear those old lines.
|
||||
|
||||
@ -1,100 +0,0 @@
|
||||
@file:JvmName("Static")
|
||||
|
||||
package com.jakewharton.mosaic.ui
|
||||
|
||||
import androidx.compose.runtime.Applier
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ComposeNode
|
||||
import androidx.compose.runtime.Composition
|
||||
import androidx.compose.runtime.CompositionContext
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCompositionContext
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import com.jakewharton.mosaic.MosaicNodeApplier
|
||||
import com.jakewharton.mosaic.layout.MosaicNode
|
||||
import kotlin.jvm.JvmName
|
||||
|
||||
/** Render each value emitted by [items] as permanent output above the regular display. */
|
||||
@Deprecated(
|
||||
"Loop over items list yourself and call Static on each item",
|
||||
ReplaceWith("items.forEach { Static { content(it) } }"),
|
||||
)
|
||||
@Composable
|
||||
public fun <T> Static(
|
||||
items: SnapshotStateList<T>,
|
||||
content: @Composable (item: T) -> Unit,
|
||||
) {
|
||||
items.forEach {
|
||||
Static {
|
||||
content(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render [content] once as permanent output above the regular display.
|
||||
*
|
||||
* The [content] function will be recomposed once and then never again.
|
||||
* Any contained [SideEffect]s or [DisposableEffect]s will run (and be disposed),
|
||||
* but [LaunchedEffect]s will not launch.
|
||||
*/
|
||||
@Composable
|
||||
public fun Static(
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val compositionContext = rememberCompositionContext()
|
||||
val state = remember {
|
||||
StaticState(compositionContext, content)
|
||||
}
|
||||
|
||||
ComposeNode<MosaicNode, Applier<Any>>(
|
||||
factory = StaticFactory,
|
||||
update = {
|
||||
set(state, BindStateToNode)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private val BindStateToNode: MosaicNode.(StaticState) -> Unit = {
|
||||
staticState = it
|
||||
it.setNode(this)
|
||||
}
|
||||
|
||||
private val StaticFactory: () -> MosaicNode = {
|
||||
MosaicNode(
|
||||
measurePolicy = { measurables, constraints ->
|
||||
val placeables = measurables.map { measurable ->
|
||||
measurable.measure(constraints)
|
||||
}
|
||||
|
||||
layout(0, 0) {
|
||||
// Despite reporting no size to our parent, we still place each child at
|
||||
// 0,0 since they will be individually rendered.
|
||||
placeables.forEach { placeable ->
|
||||
placeable.place(0, 0)
|
||||
}
|
||||
}
|
||||
},
|
||||
debugPolicy = {
|
||||
children.joinToString(prefix = "Static()") { "\n" + it.toString().prependIndent(" ") }
|
||||
},
|
||||
isStatic = true,
|
||||
)
|
||||
}
|
||||
|
||||
@Stable
|
||||
internal class StaticState(
|
||||
private val compositionContext: CompositionContext,
|
||||
private val content: @Composable () -> Unit,
|
||||
) {
|
||||
fun setNode(parent: MosaicNode) {
|
||||
val applier = MosaicNodeApplier(parent)
|
||||
val composition = Composition(applier, compositionContext)
|
||||
composition.setContent(content)
|
||||
composition.dispose()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
@file:JvmName("Static")
|
||||
|
||||
package com.jakewharton.mosaic.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Composition
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.ProvidableCompositionLocal
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.rememberCompositionContext
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import com.jakewharton.mosaic.MosaicNodeApplier
|
||||
import com.jakewharton.mosaic.TextCanvas
|
||||
import kotlin.jvm.JvmName
|
||||
import kotlinx.coroutines.channels.SendChannel
|
||||
|
||||
@Stable
|
||||
public class StaticLogger internal constructor(
|
||||
private val logs: SendChannel<Any>,
|
||||
private val onLog: () -> Unit,
|
||||
) {
|
||||
private fun send(value: Any) {
|
||||
logs.trySend(value).getOrThrow()
|
||||
onLog()
|
||||
}
|
||||
|
||||
public fun log(string: String): Unit = send(string)
|
||||
public fun log(canvas: TextCanvas): Unit = send(canvas)
|
||||
|
||||
public inline operator fun plusAssign(string: String): Unit = log(string)
|
||||
public inline operator fun plusAssign(canvas: TextCanvas): Unit = log(canvas)
|
||||
}
|
||||
|
||||
public val LocalStaticLogger: ProvidableCompositionLocal<StaticLogger> =
|
||||
staticCompositionLocalOf {
|
||||
throw AssertionError("No static logger provided")
|
||||
}
|
||||
|
||||
/**
|
||||
* Render [content] once as permanent output above the regular display.
|
||||
*
|
||||
* The [content] function will be recomposed once and then never again.
|
||||
* Any contained [SideEffect]s or [DisposableEffect]s will run (and be disposed),
|
||||
* but [LaunchedEffect]s will not launch.
|
||||
*/
|
||||
@Composable
|
||||
public fun StaticEffect(
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val compositionContext = rememberCompositionContext()
|
||||
val staticLogging = LocalStaticLogger.current
|
||||
SideEffect {
|
||||
val applier = MosaicNodeApplier()
|
||||
val composition = Composition(applier, compositionContext)
|
||||
composition.setContent(content)
|
||||
|
||||
val root = applier.root
|
||||
root.measureAndPlace()
|
||||
staticLogging += root.draw()
|
||||
|
||||
composition.dispose()
|
||||
}
|
||||
}
|
||||
@ -9,7 +9,7 @@ import com.jakewharton.mosaic.ui.AnsiLevel
|
||||
import com.jakewharton.mosaic.ui.Color
|
||||
import com.jakewharton.mosaic.ui.Column
|
||||
import com.jakewharton.mosaic.ui.Row
|
||||
import com.jakewharton.mosaic.ui.Static
|
||||
import com.jakewharton.mosaic.ui.StaticEffect
|
||||
import com.jakewharton.mosaic.ui.Text
|
||||
import kotlin.test.Test
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@ -120,7 +120,7 @@ class AnsiRenderingTest {
|
||||
runMosaicTest(RenderingSnapshots(rendering)) {
|
||||
setContent {
|
||||
Text("Hello")
|
||||
Static {
|
||||
StaticEffect {
|
||||
Text("World!")
|
||||
}
|
||||
}
|
||||
@ -138,7 +138,7 @@ class AnsiRenderingTest {
|
||||
@Test fun staticLinesNotErased() = runTest {
|
||||
runMosaicTest(RenderingSnapshots(rendering)) {
|
||||
setContent {
|
||||
Static {
|
||||
StaticEffect {
|
||||
Text("One")
|
||||
}
|
||||
Text("Two")
|
||||
@ -153,7 +153,7 @@ class AnsiRenderingTest {
|
||||
)
|
||||
|
||||
setContent {
|
||||
Static {
|
||||
StaticEffect {
|
||||
Text("Three")
|
||||
}
|
||||
Text("Four")
|
||||
@ -161,7 +161,7 @@ class AnsiRenderingTest {
|
||||
|
||||
assertThat(awaitSnapshot()).isEqualTo(
|
||||
"""
|
||||
|${cursorUp(1)}${clearLine}Three
|
||||
|${cursorUp(1)}${clearDisplay}Three
|
||||
|Four
|
||||
|
|
||||
""".trimMargin().wrapWithAnsiSynchronizedUpdate().replaceLineEndingsWithCRLF(),
|
||||
@ -172,24 +172,24 @@ class AnsiRenderingTest {
|
||||
@Test fun staticOrderingIsDfs() = runTest {
|
||||
runMosaicTest(RenderingSnapshots(rendering)) {
|
||||
setContent {
|
||||
Static {
|
||||
StaticEffect {
|
||||
Text("One")
|
||||
}
|
||||
Column {
|
||||
Static {
|
||||
StaticEffect {
|
||||
Text("Two")
|
||||
}
|
||||
Row {
|
||||
Static {
|
||||
StaticEffect {
|
||||
Text("Three")
|
||||
}
|
||||
Text("Sup")
|
||||
}
|
||||
Static {
|
||||
StaticEffect {
|
||||
Text("Four")
|
||||
}
|
||||
}
|
||||
Static {
|
||||
StaticEffect {
|
||||
Text("Five")
|
||||
}
|
||||
}
|
||||
@ -215,7 +215,7 @@ class AnsiRenderingTest {
|
||||
Text("TopTopTop")
|
||||
Row {
|
||||
Text("LeftLeft")
|
||||
Static {
|
||||
StaticEffect {
|
||||
Text("Static")
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ import com.jakewharton.mosaic.testing.runMosaicTest
|
||||
import com.jakewharton.mosaic.ui.AnsiLevel
|
||||
import com.jakewharton.mosaic.ui.Layout
|
||||
import com.jakewharton.mosaic.ui.Row
|
||||
import com.jakewharton.mosaic.ui.Static
|
||||
import com.jakewharton.mosaic.ui.StaticEffect
|
||||
import com.jakewharton.mosaic.ui.Text
|
||||
import kotlin.test.Test
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
@ -64,7 +64,7 @@ class DebugRenderingTest {
|
||||
runMosaicTest(RenderingSnapshots(rendering)) {
|
||||
setContent {
|
||||
Text("Hello")
|
||||
Static {
|
||||
StaticEffect {
|
||||
Text("Static")
|
||||
}
|
||||
}
|
||||
@ -73,8 +73,6 @@ class DebugRenderingTest {
|
||||
"""
|
||||
|NODES:
|
||||
|Text("Hello") x=0 y=0 w=5 h=1 DrawBehind
|
||||
|Static()
|
||||
| Text("Static") x=0 y=0 w=6 h=1 DrawBehind
|
||||
|
|
||||
|STATIC:
|
||||
|Static
|
||||
|
||||
@ -113,6 +113,8 @@ class MosaicTest {
|
||||
),
|
||||
keyEvents = Channel(),
|
||||
terminalState = mutableStateOf(Terminal.Default),
|
||||
ansiLevel = AnsiLevel.NONE,
|
||||
supportsKittyUnderlines = false,
|
||||
) {
|
||||
LaunchedEffect(Unit) {
|
||||
withFrameNanos { frameTimeNanos ->
|
||||
|
||||
@ -5,7 +5,7 @@ import com.jakewharton.mosaic.testing.SnapshotStrategy
|
||||
|
||||
internal object DumpSnapshots : SnapshotStrategy<String> {
|
||||
override fun create(mosaic: Mosaic): String {
|
||||
return mosaic.dump()
|
||||
return mosaic.dumpNodes()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -60,7 +60,7 @@ class BoxTest {
|
||||
assertThat(positionedChildContainerNode.size).isEqualTo(IntSize(size, size))
|
||||
assertThat(positionedChildContainerNode.position).isEqualTo(IntOffset.Zero)
|
||||
|
||||
assertThat(rootNode.paint().render(AnsiLevel.NONE, false)).isEqualTo(
|
||||
assertThat(rootNode.draw().render(AnsiLevel.NONE, false)).isEqualTo(
|
||||
"""
|
||||
|
|
||||
|
|
||||
@ -142,7 +142,7 @@ class BoxTest {
|
||||
assertThat(secondChildContainerNode.size).isEqualTo(IntSize(size, size))
|
||||
assertThat(secondChildContainerNode.position).isEqualTo(IntOffset.Zero)
|
||||
|
||||
assertThat(rootNode.paint().render(AnsiLevel.NONE, false)).isEqualTo(
|
||||
assertThat(rootNode.draw().render(AnsiLevel.NONE, false)).isEqualTo(
|
||||
"""
|
||||
|
|
||||
| $TestChar$TestChar$TestChar$TestChar$TestChar
|
||||
@ -189,7 +189,7 @@ class BoxTest {
|
||||
assertThat(secondChildContainerNode.size).isEqualTo(IntSize(size, size))
|
||||
assertThat(secondChildContainerNode.position).isEqualTo(IntOffset.Zero)
|
||||
|
||||
assertThat(rootNode.paint().render(AnsiLevel.NONE, false)).isEqualTo(
|
||||
assertThat(rootNode.draw().render(AnsiLevel.NONE, false)).isEqualTo(
|
||||
"""
|
||||
|$TestChar$TestChar$TestChar$TestChar$TestChar
|
||||
|$TestChar$TestChar$TestChar$TestChar$TestChar
|
||||
@ -236,7 +236,7 @@ class BoxTest {
|
||||
assertThat(secondChildContainerNode.size).isEqualTo(IntSize(size, size))
|
||||
assertThat(secondChildContainerNode.position).isEqualTo(IntOffset.Zero)
|
||||
|
||||
assertThat(rootNode.paint().render(AnsiLevel.NONE, false)).isEqualTo(
|
||||
assertThat(rootNode.draw().render(AnsiLevel.NONE, false)).isEqualTo(
|
||||
"""
|
||||
| $TestChar$TestChar$TestChar$TestChar
|
||||
| $TestChar$TestChar$TestChar$TestChar
|
||||
@ -283,7 +283,7 @@ class BoxTest {
|
||||
assertThat(secondChildContainerNode.size).isEqualTo(IntSize(size, size))
|
||||
assertThat(secondChildContainerNode.position).isEqualTo(IntOffset.Zero)
|
||||
|
||||
assertThat(rootNode.paint().render(AnsiLevel.NONE, false)).isEqualTo(
|
||||
assertThat(rootNode.draw().render(AnsiLevel.NONE, false)).isEqualTo(
|
||||
"""
|
||||
|
|
||||
|$TestChar$TestChar$TestChar$TestChar$TestChar$TestChar
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package com.jakewharton.mosaic.ui
|
||||
|
||||
import androidx.collection.mutableObjectListOf
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.SideEffect
|
||||
@ -9,14 +8,10 @@ import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import assertk.assertAll
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.containsExactly
|
||||
import assertk.assertions.hasSize
|
||||
import assertk.assertions.isEmpty
|
||||
import assertk.assertions.isEqualTo
|
||||
import assertk.assertions.isFalse
|
||||
import assertk.assertions.isNull
|
||||
import assertk.assertions.isTrue
|
||||
import com.jakewharton.mosaic.NodeSnapshots
|
||||
import com.jakewharton.mosaic.assertFailure
|
||||
import com.jakewharton.mosaic.render
|
||||
import com.jakewharton.mosaic.testing.MosaicSnapshots
|
||||
import com.jakewharton.mosaic.testing.runMosaicTest
|
||||
@ -31,11 +26,11 @@ class StaticTest {
|
||||
@Test fun renderingDoesNotCauseAnotherFrame() = runTest {
|
||||
runMosaicTest(MosaicSnapshots) {
|
||||
setContent {
|
||||
Static { Text("static") }
|
||||
StaticEffect { Text("static") }
|
||||
Text("content")
|
||||
}
|
||||
|
||||
assertThat(awaitSnapshot().paintStatics()).hasSize(1)
|
||||
assertThat(awaitSnapshot().static()).isEqualTo("static")
|
||||
assertFailsWith<TimeoutCancellationException> { awaitSnapshot() }
|
||||
}
|
||||
}
|
||||
@ -44,57 +39,19 @@ class StaticTest {
|
||||
runMosaicTest(MosaicSnapshots) {
|
||||
var count by mutableIntStateOf(1)
|
||||
setContent {
|
||||
Static { Text("static: $count") }
|
||||
StaticEffect { Text("static: $count") }
|
||||
Text("content: $count")
|
||||
}
|
||||
|
||||
val one = awaitSnapshot()
|
||||
assertThat(one.paint().render()).isEqualTo("content: 1")
|
||||
assertThat(one.paintStatics().render()).containsExactly("static: 1")
|
||||
assertThat(one.draw().render()).isEqualTo("content: 1")
|
||||
assertThat(one.static()).isEqualTo("static: 1")
|
||||
|
||||
count = 2
|
||||
|
||||
val two = awaitSnapshot()
|
||||
assertThat(two.paint().render()).isEqualTo("content: 2")
|
||||
assertThat(two.paintStatics().render()).isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun staticNodesRemovedAfterRenderWithoutRecomposition() = runTest {
|
||||
runMosaicTest(NodeSnapshots) {
|
||||
var count by mutableIntStateOf(1)
|
||||
setContent {
|
||||
Static { Text("static: $count") }
|
||||
Text("content: $count")
|
||||
}
|
||||
|
||||
val one = awaitSnapshot()
|
||||
assertThat(one.toString()).isEqualTo(
|
||||
"""
|
||||
|Static()
|
||||
| Text("static: 1") x=0 y=0 w=9 h=1 DrawBehind
|
||||
|Text("content: 1") x=0 y=0 w=10 h=1 DrawBehind
|
||||
""".trimMargin(),
|
||||
)
|
||||
|
||||
one.paintStaticsTo(mutableObjectListOf())
|
||||
assertThat(one.toString()).isEqualTo(
|
||||
"""
|
||||
|Static()
|
||||
|Text("content: 1") x=0 y=0 w=10 h=1 DrawBehind
|
||||
""".trimMargin(),
|
||||
)
|
||||
|
||||
assertFailure<TimeoutCancellationException> { awaitSnapshot() }
|
||||
|
||||
count = 2
|
||||
val two = awaitSnapshot()
|
||||
assertThat(two.toString()).isEqualTo(
|
||||
"""
|
||||
|Static()
|
||||
|Text("content: 2") x=0 y=0 w=10 h=1 DrawBehind
|
||||
""".trimMargin(),
|
||||
)
|
||||
assertThat(two.draw().render()).isEqualTo("content: 2")
|
||||
assertThat(two.static()).isNull()
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,7 +59,7 @@ class StaticTest {
|
||||
runMosaicTest {
|
||||
var ran = false
|
||||
setContent {
|
||||
Static {
|
||||
StaticEffect {
|
||||
SideEffect {
|
||||
ran = true
|
||||
}
|
||||
@ -116,7 +73,7 @@ class StaticTest {
|
||||
runMosaicTest {
|
||||
var ran = false
|
||||
setContent {
|
||||
Static {
|
||||
StaticEffect {
|
||||
LaunchedEffect(Unit) {
|
||||
ran = true
|
||||
}
|
||||
@ -135,7 +92,7 @@ class StaticTest {
|
||||
var effectRan = false
|
||||
var disposeRan = false
|
||||
setContent {
|
||||
Static {
|
||||
StaticEffect {
|
||||
DisposableEffect(Unit) {
|
||||
effectRan = true
|
||||
onDispose {
|
||||
@ -157,7 +114,7 @@ class StaticTest {
|
||||
var normalRecompositions = 0
|
||||
var count by mutableIntStateOf(0)
|
||||
setContentAndSnapshot {
|
||||
Static {
|
||||
StaticEffect {
|
||||
staticRecompositions++
|
||||
Text("count: $count")
|
||||
}
|
||||
@ -174,4 +131,22 @@ class StaticTest {
|
||||
assertThat(normalRecompositions).isEqualTo(2)
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun loggingCausesFrameWithoutRecomposition() = runTest {
|
||||
runMosaicTest(MosaicSnapshots) {
|
||||
var recompositionCount = 0
|
||||
lateinit var staticLogger: StaticLogger
|
||||
val initial = setContentAndSnapshot {
|
||||
staticLogger = LocalStaticLogger.current
|
||||
Text("Count: ${++recompositionCount}")
|
||||
}
|
||||
assertThat(initial.draw().render()).isEqualTo("Count: 1")
|
||||
assertThat(initial.static()).isNull()
|
||||
|
||||
staticLogger += "sup"
|
||||
val snapshot = awaitSnapshot()
|
||||
assertThat(snapshot.draw().render()).isEqualTo("Count: 1")
|
||||
assertThat(snapshot.static()).isEqualTo("sup")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,11 @@
|
||||
package com.jakewharton.mosaic.testing
|
||||
|
||||
import androidx.collection.MutableObjectList
|
||||
import androidx.compose.runtime.BroadcastFrameClock
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import com.jakewharton.mosaic.Mosaic
|
||||
import com.jakewharton.mosaic.Terminal
|
||||
import com.jakewharton.mosaic.TextCanvas
|
||||
import com.jakewharton.mosaic.layout.KeyEvent
|
||||
import com.jakewharton.mosaic.ui.AnsiLevel
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
@ -106,25 +104,16 @@ private class RealTestMosaic<T>(
|
||||
keyEvents.trySend(keyEvent)
|
||||
}
|
||||
|
||||
override fun paint() = mosaic.paint()
|
||||
override fun draw() = mosaic.draw()
|
||||
override fun static() = mosaic.static()
|
||||
override fun dumpNodes() = mosaic.dumpNodes()
|
||||
|
||||
override fun paintStaticsTo(list: MutableObjectList<TextCanvas>) {
|
||||
mosaic.paintStaticsTo(list)
|
||||
}
|
||||
|
||||
override fun dump() = mosaic.dump()
|
||||
|
||||
override suspend fun awaitComplete() {
|
||||
mosaic.awaitComplete()
|
||||
}
|
||||
|
||||
override fun cancel() {
|
||||
mosaic.cancel()
|
||||
}
|
||||
override suspend fun awaitComplete() = mosaic.awaitComplete()
|
||||
override fun cancel() = mosaic.cancel()
|
||||
}
|
||||
|
||||
internal object PlainTextSnapshots : SnapshotStrategy<String> {
|
||||
override fun create(mosaic: Mosaic): String {
|
||||
return mosaic.paint().render(AnsiLevel.NONE, false)
|
||||
return mosaic.draw().render(AnsiLevel.NONE, false)
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ import com.jakewharton.mosaic.ui.Color.Companion.Yellow
|
||||
import com.jakewharton.mosaic.ui.Column
|
||||
import com.jakewharton.mosaic.ui.Row
|
||||
import com.jakewharton.mosaic.ui.Spacer
|
||||
import com.jakewharton.mosaic.ui.Static
|
||||
import com.jakewharton.mosaic.ui.StaticEffect
|
||||
import com.jakewharton.mosaic.ui.Text
|
||||
import com.jakewharton.mosaic.ui.TextStyle.Companion.Bold
|
||||
import example.TestState.Fail
|
||||
@ -138,13 +138,15 @@ fun TestRow(test: Test) {
|
||||
@Composable
|
||||
fun Log(complete: SnapshotStateList<Test>) {
|
||||
complete.forEach { test ->
|
||||
Static {
|
||||
TestRow(test)
|
||||
if (test.failures.isNotEmpty()) {
|
||||
for (failure in test.failures) {
|
||||
Text(" ‣ $failure")
|
||||
StaticEffect {
|
||||
Column {
|
||||
TestRow(test)
|
||||
if (test.failures.isNotEmpty()) {
|
||||
for (failure in test.failures) {
|
||||
Text(" ‣ $failure")
|
||||
}
|
||||
Spacer(Modifier.height(1)) // Blank line
|
||||
}
|
||||
Spacer(Modifier.height(1)) // Blank line
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user