Giter Club home page Giter Club logo

mosaic's Introduction

Mosaic

An experimental tool for building console UI in Kotlin using the Jetpack Compose compiler/runtime. Inspired by Ink.

Jump to: Introduction | Usage | Samples | FAQ | License

Introduction

The entrypoint to Mosaic is the runMosaic function. The lambda passed to this function is responsible for both output and performing work.

Output (for now) happens through the setContent function. You can call setContent multiple times, but as you'll see you probably won't need to.

fun main() = runMosaic {
  setContent {
    Text("The count is: 0")
  }
}

To change the output dynamically we can use local properties to hold state. Let's update our counter to actually count to 20.

fun main() = runMosaic {
  var count = 0

  setContent {
    Text("The count is: $count")
  }

  for (i in 1..20) {
    delay(250)
    count = i
  }
}

This will not work! Our count stays at 0 for 5 seconds instead of incrementing until 20. Instead, we have to use Compose's State objects to hold state.

-var count = 0
+var count by mutableStateOf(0)

Now, when the count value is updated, Compose will know that it needs to re-render the string.

fun main() = runMosaic {
  var count by mutableStateOf(0)

  setContent {
    Text("The count is: $count")
  }

  for (i in 1..20) {
    delay(250)
    count = i
  }
}

(Note: You may need to add imports for androidx.compose.runtime.getValue and import androidx.compose.runtime.setValue manually.)

Usage

In order to use Mosaic you must write your code in Kotlin and must apply the Compose Kotlin compiler plugin.

For Gradle users, the Mosaic Gradle plugin will take care of applying the compiler plugin.

buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22'
    classpath 'com.jakewharton.mosaic:mosaic-gradle-plugin:0.11.0'
  }
}

apply plugin: 'org.jetbrains.kotlin.jvm'
apply plugin: 'com.jakewharton.mosaic'

The runtime APIs will be made available automatically by applying the plugin. Documentation is available at jakewharton.github.io/mosaic/docs/0.x/.

Note: Any module which contains a @Composable-annotated function or lambda must apply the Mosaic plugin. While the runtime dependency will be available to downstream modules as a transitive dependency, the compiler plugin is not inherited and must be applied to every module.

Since Kotlin compiler plugins are an unstable API, certain versions of Mosaic only work with certain versions of Kotlin.

Kotlin Mosaic
1.9.22 0.11.0
1.9.20 0.10.0
1.9.10 0.9.1
1.9.0 0.8.0 - 0.9.0
1.8.22 0.7.1
1.8.21 0.7.0
1.8.20 0.6.0
1.8.10 0.5.0
1.8.0 0.3.0 - 0.4.0
1.7.10 0.2.0
1.5.10 0.1.0

Versions newer than those listed may be supported but are untested.

Snapshots of the development version are available in Sonatype's snapshots repository.

buildscript {
  repository {
    mavenCentral()
    maven {
      url 'https://oss.sonatype.org/content/repositories/snapshots/'
    }
  }
  dependencies {
    classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22'
    classpath 'com.jakewharton.mosaic:mosaic-gradle-plugin:0.12.0-SNAPSHOT'
  }
}

apply plugin: 'org.jetbrains.kotlin.jvm'
apply plugin: 'com.jakewharton.mosaic'

Snapshot documentation is available at jakewharton.github.io/mosaic/docs/latest/.

Samples

Run ./gradlew installDist to build the sample binaries.

  • Counter: A simple increasing number from 0 until 20.

    ./samples/counter/build/install/counter/bin/counter

  • Demo: A playground for demonstrating many features of Mosaic.

    ./samples/demo/build/install/demo/bin/demo

  • Jest: Example output of a test framework (such as JS's 'Jest').

    ./samples/jest/build/install/jest/bin/jest

  • Robot: An interactive, game-like program with keyboard control.

    ./samples/robot/build/install/robot/bin/robot

FAQ

I thought Jetpack Compose was a UI toolkit for Android?

Compose is, at its core, a general-purpose runtime and compiler for tree and property manipulation which is trapped inside the AndroidX monorepo and under the Jetpack marketing department. This core can be used for any tree on any platform supported by Kotlin. It's an amazing piece of technology.

Compose UI is the new UI toolkit for Android (and maybe Desktop?). The lack of differentiation between these two technologies has unfortunately caused Compose UI to overshadow the core under the single "Compose" moniker in an unforced marketing error.

If you want another example of a non-Compose UI-based Compose project checkout JetBrains' Compose for Web project.

Why doesn't work take place in a LaunchedEffect?

This is the goal. It is currently blocked by issuetracker.google.com/178904648.

When that change lands, and Mosaic is updated, the counter sample will look like this:

fun main() = runMosaic {
  var count by remember { mutableStateOf(0) }

  Text("The count is: $count")

  LaunchedEffect(Unit) {
    for (i in 1..20) {
      delay(250)
      count = i
    }
  }
}

Custom Compose Compiler

Each version of Mosaic ships with a specific JetBrains Compose compiler version which works with a single version of Kotlin (see version table above). Newer versions of the Compose compiler or alternate Compose compilers can be specified using the Gradle extension.

To use a new version of the JetBrains Compose compiler version:

mosaic {
  kotlinCompilerPlugin.set("1.4.8")
}

To use an alternate Compose compiler dependency:

mosaic {
  kotlinCompilerPlugin.set("com.example:custom-compose-compiler:1.0.0")
}

License

Copyright 2020 Jake Wharton

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

mosaic's People

Contributors

amokrane avatar bnorm avatar danherrera avatar dcampogiani avatar dependabot[bot] avatar epicdima avatar goooler avatar hfhbd avatar jakewharton avatar mrmans0n avatar renovate[bot] avatar saket avatar twisterrob avatar typfel avatar williamwebb avatar zacsweers avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mosaic's Issues

Import issues with Date

Importing Date from org.jraf.klibnotion.model.date can cause conflicts with DateTime libs or locally defined libraries, especially when defined with aliases, we should try to coalesce with the kotlinx and java.ltime library, with respect to multiplatform

Empty Column or Row causes exception in render

Example which causes exception

runMosaic {
    setContent {
        Column {}
    }
}

Exception stack trace

Exception in thread "main" java.lang.IllegalArgumentException: Row start value out of range [0,0): 0
        at com.jakewharton.mosaic.TextCanvas.get(canvas.kt:22)
        at com.jakewharton.mosaic.BoxNode.renderTo(nodes.kt:136)
        at com.jakewharton.mosaic.MosaicNode.render(nodes.kt:26)
        at com.jakewharton.mosaic.MosaicKt$runMosaic$1$2.invokeSuspend(mosaic.kt:55)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:274)
        at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
        at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
        at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
        at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
        at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
        at com.jakewharton.mosaic.MosaicKt.runMosaic(mosaic.kt:28)

This is actually fixed in #66 here: https://github.com/JakeWharton/mosaic/pull/66/files#diff-edb81a24f298abe6e7ff34ccd8691345abc86bed07218a3d05d49793406d9f7fR21. I could move that change into a separate PR if you aren't ready for the whole Static design yet.

Use effects to denote scope of work

Currently we build our own coroutine scope to encapsulate all of the work required to render. When this scope ends, rendering ends and the program exits cleanly (modulo internal nonsense to make this work).

Ideally effects inside the composition are used to scope the composition instead. Compose internals currently prevent this, which is tracked as https://issuetracker.google.com/issues/169425431.

Border Modifier

Being able to draw borders around layouts would be great.
I guess Modifier.border(BorderStyle) could be good enough?

Maybe JakeWharton/flip-tables could be used under the hood?

I imagine being able to draw single line borders, double line border, rounded corners, square corners (defined by BorderStyle?)
See https://en.wikipedia.org/wiki/Box-drawing_character#Box_Drawing

       | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F
U+250x | ─ | ━ | │ | ┃ | ┄ | ┅ | ┆ | ┇ | ┈ | ┉ | ┊ | ┋ | ┌ | ┍ | ┎ | ┏
U+251x | ┐ | ┑ | ┒ | ┓ | └ | ┕ | ┖ | ┗ | ┘ | ┙ | ┚ | ┛ | ├ | ┝ | ┞ | ┟
U+252x | ┠ | ┡ | ┢ | ┣ | ┤ | ┥ | ┦ | ┧ | ┨ | ┩ | ┪ | ┫ | ┬ | ┭ | ┮ | ┯
U+253x | ┰ | ┱ | ┲ | ┳ | ┴ | ┵ | ┶ | ┷ | ┸ | ┹ | ┺ | ┻ | ┼ | ┽ | ┾ | ┿
U+254x | ╀ | ╁ | ╂ | ╃ | ╄ | ╅ | ╆ | ╇ | ╈ | ╉ | ╊ | ╋ | ╌ | ╍ | ╎ | ╏
U+255x | ═ | ║ | ╒ | ╓ | ╔ | ╕ | ╖ | ╗ | ╘ | ╙ | ╚ | ╛ | ╜ | ╝ | ╞ | ╟
U+256x | ╠ | ╡ | ╢ | ╣ | ╤ | ╥ | ╦ | ╧ | ╨ | ╩ | ╪ | ╫ | ╬ | ╭ | ╮ | ╯
U+257x | ╰ | ╱ | ╲ | ╳ | ╴ | ╵ | ╶ | ╷ | ╸ | ╹ | ╺ | ╻ | ╼ | ╽ | ╾ | ╿

In terminal app, it's always painful to achieve this:

  • border length depending on content or requested width
  • support minWidth + dynamic width
  • pad end of each lines to draw right border

Would be a nice addition to

[HELP] Issue while Using it as a submodule.

Earlier I copy pasted , dependencies.gradle , mosaic/mosaic folder and substituted modules with projects , and all worked fine

now, I wanted to escape from this manual work , So tried to add it as a
git add submodule https://github.com/JakeWharton/mosaic
and It worked and added mosaic project as a submodule , so I did the following in my project's settings.gradle.kts

includeBuild("mosaic/mosaic") {
    dependencySubstitution {
        substitute(module("com.jakewharton.mosaic:mosaic-gradle-plugin")).with(project(":mosaic-gradle-plugin"))
        substitute(module("com.jakewharton.mosaic:mosaic-runtime")).with(project(":mosaic-runtime"))
        substitute(module("com.jakewharton.mosaic:compose-compiler")).with(project(":compose:compiler"))
    }
}

and added the plugin as following:

plugins {
    // other plugins
    id("com.jakewharton.mosaic")
}

then did a gradle sync successfully and app run (used your sample counter app's code) also successfully 😄

BUT IDE doesnt recognise code reference , however builds successfully, any help would be appreciated , If I can provide more info , I would be happy to do that...

image

Testing counter example

The counter example needs tests to show off how they work or would work.

One should start the rendering and assert 0, delay 250, and assert 1. The other should start the rendering and fast-forward to 20 and assert it exits cleanly and does not render anything else.

Kotlin 1.8.10 / JB Compose compiler 1.4.2 broke Jest sample

And probably others.

$ gw -q installDist && ./samples/jest/build/install/jest/bin/jest
 FAIL  tests/login.kt
 ‣ Failure on line 36 in tests/login.kt
 ‣ Failure on line 38 in tests/login.kt

 PASS  tests/reset-password.kt
 PASS  tests/forgot-password.kt
 FAIL  tests/signup.kt
 ‣ Failure on line 40 in tests/signup.kt

 RUNS  tests/post.kt
Time:  10s

Exception in thread "main" java.lang.IndexOutOfBoundsException: Index 3 out of bounds for length 3
	at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:100)
	at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:106)
	at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:302)
	at java.base/java.util.Objects.checkIndex(Objects.java:385)
	at java.base/java.util.ArrayList.remove(ArrayList.java:504)
	at androidx.compose.runtime.AbstractApplier.remove(Applier.kt:223)
	at com.jakewharton.mosaic.MosaicNodeApplier.remove(nodes.kt:185)
	at androidx.compose.runtime.ComposerImpl$realizeMovement$1.invoke(Composer.kt:3769)
	at androidx.compose.runtime.ComposerImpl$realizeMovement$1.invoke(Composer.kt:3769)
	at androidx.compose.runtime.CompositionImpl.applyChangesInLocked(Composition.kt:808)
	at androidx.compose.runtime.CompositionImpl.applyChanges(Composition.kt:839)
	at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:585)
	at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:503)
	at androidx.compose.runtime.BroadcastFrameClock$FrameAwaiter.resume(BroadcastFrameClock.kt:42)
	at androidx.compose.runtime.BroadcastFrameClock.sendFrame(BroadcastFrameClock.kt:71)
	at com.jakewharton.mosaic.MosaicKt$runMosaic$2$2.invokeSuspend(mosaic.kt:81)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:234)
	at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:166)
	at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:397)
	at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:431)
	at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:420)
	at kotlinx.coroutines.CancellableContinuationImpl.resumeUndispatched(CancellableContinuationImpl.kt:518)
	at kotlinx.coroutines.EventLoopImplBase$DelayedResumeTask.run(EventLoop.common.kt:500)
	at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:284)
	at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
	at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
	at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
	at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
	at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
	at com.jakewharton.mosaic.BlockingKt.runMosaicBlocking(blocking.kt:6)
	at example.JestKt.main(jest.kt:33)
	at example.JestKt.main(jest.kt)

error: has been compiled by a more recent version

Tried to run ./gradlew -p samples installDist from README and got:

> Task :mosaic:mosaic-runtime:compileKotlin FAILED
e: java.lang.UnsupportedClassVersionError: 
androidx/compose/compiler/plugins/kotlin/ComposeComponentRegistrar
has been compiled by a more recent version of the Java Runtime
(class file version 55.0), this version of the Java Runtime only recognizes
class file versions up to 52.0

Found https://stackoverflow.com/questions/47457105/class-has-been-compiled-by-a-more-recent-version-of-the-java-environment

Researching this now, will post an update here if I solve it.

Measure and place static branches with main tree

Right now we defer static measure and placement until we call drawStatics() which does all three operations. draw() performs all three operations on the main tree so it seems symmetric, but it's actually not.

Counter sample doesn't behave correctly

Here is my build.gradle.kts in an intellij project where I created the project with the gradle cmd line app wizard

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10")
        classpath("com.jakewharton.mosaic:mosaic-gradle-plugin:0.1.0")
    }
}

plugins {
    id("org.jetbrains.kotlin.jvm") version "1.5.10"
    id("org.jetbrains.kotlin.plugin.serialization") version "1.5.10"
    id("com.jakewharton.mosaic") version "0.1.0"
    application
}

group = "me.coltonidle"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()
}

dependencies {
    testImplementation(kotlin("test-junit"))
}

tasks.test {
    useJUnit()
}

tasks.withType<KotlinCompile>() {
    kotlinOptions.jvmTarget = "1.8"
}

application {
    mainClassName = "MainKt"
}

Here is my MainKt

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import com.jakewharton.mosaic.Column
import com.jakewharton.mosaic.Text
import com.jakewharton.mosaic.runMosaic
import kotlinx.coroutines.delay

fun main() = runMosaic {
    var count by mutableStateOf(0)

    setContent {
        Column {
            Text("The count is: $count")
        }
    }

    for (i in 1..20) {
        delay(250)
        count = i
    }
}

Running this gives me

Screen Shot 2021-06-29 at 10 59 35 AM

Custom layouts

Box is defined as such:

@Composable
private fun Box(isRow: Boolean, children: @Composable () -> Unit) {
	ComposeNode<BoxNode, MosaicNodeApplier>(
		factory = ::BoxNode,
		update = {
			set(isRow) {
				this.isRow = isRow
			}
		},
		content = children,
	)
}

Unfortunately MosaicNodeApplier and MosaicNode are internal.

What is the best way to implement a custom layout?

For example, if I wanted to create a Scaffold composable, like in Android. Whereby I could have a title and a content. I would like to be able to layout the Scaffold that it takes up the terminal window size.

How I intend to start it:

@Composable
fun Scaffold(
    terminal: Terminal,
    title: @Composable (TerminalScope) -> Unit,
    content: @Composable (TerminalScope) -> Unit
) {
    fun scopeFromTerminal(terminal: Terminal): TerminalScope = TerminalScope(
        width = terminal.width,
        height = terminal.height
    )

    var terminalScope by mutableStateOf(
        scopeFromTerminal(terminal)
    )

    terminal.handle(Terminal.Signal.WINCH) { signal ->
        terminalScope = scopeFromTerminal(terminal)
    }

    title.invoke(terminalScope)

    content.invoke(terminalScope)
}

Publish to MavenCentral

Hello,

I wanted to try out mosaic in my own project. Sadly, it seems like it is not available in the MavenCentral repository. Would it be possible to publish it there?
Having to build the project locally is a major barrier to people simply wanting to try it out :)

Windows support

I was playing around and I was having problems making it work out of the box, but I did it in the end. I'm opening an issue instead of a PR, because I'm not sure in what style would you apply these changes. The main fix that's absolutely necessary is patch 2 for jansi (see 4.).

Below is what I observed and learned:

  1. IntelliJ IDEA terminal view supports color and relocation of cursor
    (Gradle output is colored and workers are shown)

    • Command Prompt (cmd)
    • Windows Powershell
    • Ubuntu Bash (from WSL)
    • image
  2. Gradle :run task is JavaExec type and it messes with System.out in some way
    See spring-projects/spring-boot#24169 where they gave up supporting colors in Gradle, this seems to be because of gradle/gradle#1251, but this assumption is not conclusive as they're talking about console input there.

  3. :jar in samples doesn't produce a runnable jar file even though the application plugin is applied. (easy fix)

    Patch 1: make jar runnable

    Applied to each sample:

    jar {
    	manifest {
    		attributes(
    			'Main-Class': mainClassName,
    		)
    	}
    	from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }}
    	exclude('META-INF/versions/9/module-info.class')
    }
  4. jansi is not set up correctly in mosaic, because it detects tty, but is not rendering correctly

    Example unprocessed ansi output
    Tests: 10 total←[K
    Time:  0s      ←[K
    ←[F←[F←[30;43m RUNS ←[39;49m tests/←[1mlogin.kt←[22m                   ←[K
    ←[30;43m RUNS ←[39;49m tests/←[1msignup.kt←[22m                  ←[K
    ←[30;43m RUNS ←[39;49m tests/←[1mforgot-password.kt←[22m         ←[K
    ←[30;43m RUNS ←[39;49m tests/←[1mreset-password.kt←[22m          ←[K
                                            ←[K
    Tests: ←[33m4 running←[39m, 10 total              ←[K
    Time:  0s                               ←[K
    ←[43m                ←[100m                        ←[0m←[K
    ←[F←[F←[F←[F←[F←[F←[F←[F←[30;43m RUNS ←[39;49m tests/←[1mlogin.kt←[22m                   ←[K
    ←[30;43m RUNS ←[39;49m tests/←[1msignup.kt←[22m                  ←[K
    ←[30;43m RUNS ←[39;49m tests/←[1mforgot-password.kt←[22m         ←[K
    ←[30;43m RUNS ←[39;49m tests/←[1mreset-password.kt←[22m          ←[K
                                            ←[K
    Tests: ←[33m4 running←[39m, 10 total              ←[K
    Time:  0s                               ←[K
    ←[100m                                        ←[0m←[K
    ←[F←[F←[F←[F←[F←[F←[F←[F←[30;43m RUNS ←[39;49m tests/←[1mlogin.kt←[22m                   ←[K
    ←[30;43m RUNS ←[39;49m tests/←[1msignup.kt←[22m                  ←[K
    ←[30;43m RUNS ←[39;49m tests/←[1mforgot-password.kt←[22m         ←[K
    ←[30;43m RUNS ←[39;49m tests/←[1mreset-password.kt←[22m          ←[K
                                            ←[K
    
    Patch 2: set up jansi as documented
    diff --git a/mosaic-runtime/src/main/kotlin/com/jakewharton/mosaic/output.kt b/mosaic-runtime/src/main/kotlin/com/jakewharton/mosaic/output.kt
    --- a/mosaic-runtime/src/main/kotlin/com/jakewharton/mosaic/output.kt	(revision 95c0826ab30a226e363ee773904fb2e7b3bb6d01)
    +++ b/mosaic-runtime/src/main/kotlin/com/jakewharton/mosaic/output.kt	(date 1667899762778)
    @@ -36,6 +36,10 @@
     internal object AnsiOutput : Output {
        private var lastHeight = 0
     
    +	init {
    +		AnsiConsole.systemInstall()
    +	}
    +
        override fun display(canvas: TextCanvas) {
            val rendered = buildString {
                repeat(lastHeight) {
  5. At this point running gradlew -p samples :jest:jar and then java -jar samples\jest\build\libs\jest.jar works nicely (ignoring Unicode problems)

    • image
    • image

`TextCanvas` optimizations

  • Do not combine lines just to split them again
  • Do not render lines with a string builder just to append to another string builder
  • Do not gather a list of text canvases for static content, append to a single canvas
  • Eliminate clipping
  • Width and height should be hints, canvas should probably automatically scale to infinity

Exact steps to get started

What are the exact steps to get started with this? Do we use intelliJ, if so, what are the exact steps to get started?

Mixing console logging with mosaic

I'd like to create something similar to the rich console output of Gradle with Mosaic, where some kind of table is pinned to the bottom and log messages append above. This is currently doable within Mosaic if I keep a list of these log messages as state and display them first in the content and then the table. But this seems really inefficient as both the log messages and the table are completely cleared from the console and re-written with each update.

What I think I'm looking for is some kind of Appender which is available through MosaicScope that I can call something like println on. The internal AnsiOutput will know how to write those lines after clearing the screen of the previous output but before writing the new output, thus keeping the setContent at the bottom of the console. But this is only what I think and there's probably some better way of doing this.

I'd be happy to mess around with this myself if you like the idea and would be open to contribution.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

This repository currently has no open or pending branches.

Detected dependencies

github-actions
.github/workflows/build.yaml
  • actions/checkout v4
  • actions/setup-java v4
  • gradle/gradle-build-action v2
.github/workflows/gradle-wrapper.yaml
  • actions/checkout v4
  • gradle/wrapper-validation-action v2
.github/workflows/release.yaml
  • actions/checkout v4
  • actions/setup-java v4
  • ffurrer2/extract-release-notes v2
  • ncipollo/release-action v1
gradle
gradle.properties
addAllTargets.gradle
publish.gradle
settings.gradle
build.gradle
build-logic/settings.gradle
build-logic/build.gradle
gradle/libs.versions.toml
  • org.jetbrains.kotlin:kotlin-gradle-plugin 1.9.23
  • org.jetbrains.kotlin:kotlin-test 1.9.23
  • org.jetbrains.kotlinx:kotlinx-coroutines-core 1.8.0
  • androidx.compose.compiler:compiler 1.5.11
  • org.jetbrains.compose.compiler:compiler 1.5.10.1
  • org.jetbrains.compose.runtime:runtime 1.6.1
  • com.vanniktech:gradle-maven-publish-plugin 0.28.0
  • org.jetbrains.dokka:dokka-gradle-plugin 1.9.20
  • com.github.gmazzo.buildconfig:plugin 5.3.5
  • dev.drewhamilton.poko:poko-gradle-plugin 0.15.2
  • org.jetbrains.kotlinx:binary-compatibility-validator 0.14.0
  • com.diffplug.spotless:spotless-plugin-gradle 6.25.0
  • com.pinterest.ktlint:ktlint-cli 1.2.1
  • io.nlopez.compose.rules:ktlint 0.3.13
  • org.fusesource.jansi:jansi 2.4.1
  • com.github.ajalt.mordant:mordant 2.4.0
  • de.cketti.unicode:kotlin-codepoints 0.7.0
  • junit:junit 4.13.2
  • com.willowtreeapps.assertk:assertk 0.28.0
mosaic-gradle-plugin/gradle.properties
mosaic-gradle-plugin/build.gradle
mosaic-runtime/gradle.properties
mosaic-runtime/build.gradle
samples/counter/build.gradle
samples/demo/build.gradle
samples/jest/build.gradle
samples/robot/build.gradle
  • org.jline:jline 3.25.1
gradle-wrapper
gradle/wrapper/gradle-wrapper.properties
  • gradle 8.7

  • Check this box to trigger a request for Renovate to run again on this repository

AnnotatedString

I should be able to control styles in a single Text without needing many of them

Not able to use 0.7.0-SNAPSHOT

I am trying to use the 0.7.0-SNAPSHOT but getting the following error during gradle sync.

org.gradle.api.plugins.UnknownPluginException: Plugin [id: 'com.jakewharton.mosaic', version: '0.7.0-SNAPSHOT'] was not found in any of the following sources:
- Gradle Core Plugins (plugin is not in 'org.gradle' namespace)
- Plugin Repositories (could not resolve plugin artifact 'com.jakewharton.mosaic:com.jakewharton.mosaic.gradle.plugin:0.7.0-SNAPSHOT')

Gradle Config:

    repositories {
        mavenCentral()
        maven {
            url 'https://oss.sonatype.org/content/repositories/snapshots/'
        }
    }
    dependencies {
        classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.20'
        classpath 'com.jakewharton.mosaic:mosaic-gradle-plugin:0.7.0-SNAPSHOT'
    }
}

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.8.10'
    id 'application'
    id 'com.jakewharton.mosaic' version '0.7.0-SNAPSHOT'
}

Printing new line

Kapture.2021-08-20.at.21.09.28.mp4

Is this an IntelliJ issue or mosaic's issue? 🤔

Variable doesn't get rerendered

I'm having a project that interacts with an API using okhttp, and the project is recursively sending requests, waiting for them, then return the responses. I had the logic inside a NewConnection class that implements an interface ConnectionObserver
I was trying to get the overridden methods to update a variable that gets displayed by mosaic, but unfortunately it doesn't.
Any ideas?

Also the library seem to be really deficit in documentation. The only link referenced is mere API reference!

suspend fun TUIClient() = runMosaic {
        var result by mutableStateOf("")
        setContent {
                Text ("The count is: ${result}")
        }

        var newConnection: NewConnection = NewConnection(object: ConnectionObserver {

            override fun onConnected() {
                result = "Connected"
            }

            override fun onUserDisconnected() {
                result  = "User disconnected"
            }

            override fun onTyping() {
                result = "Typing"
            }

            override fun onGotMessage(message: String) {
                result = message
            }

            override fun onSomethingElse() {
                /* println("Response is ${response}") */
                result = "Something else happened!"
            }

        })

    // construct a new connection and pass the Connection observer argument
    newConnection.start()
}

Can't figure out how to setup with kts files

I want to start experimenting with mosaic so I did a file > new project in intellij and followed the wizard for a gradle cmd line app. I added a few lines based on the readme and ended up with this

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    kotlin("jvm") version "1.5.10"
    application
    id("com.jakewharton.mosaic")
}

group = "me.coltonidle"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()
}

dependencies {
    testImplementation(kotlin("test-junit"))
}

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10")
        classpath("com.jakewharton.mosaic:mosaic-gradle-plugin:0.1.0")
    }
}

tasks.test {
    useJUnit()
}

tasks.withType<KotlinCompile>() {
    kotlinOptions.jvmTarget = "1.8"
}

application {
    mainClassName = "MainKt"
}

when building though I get an error of Plugin [id: 'com.jakewharton.mosaic'] was not found in any of the following sources:

I've only really only ever done android development so maybe I'm doing something really wrong here

Modifiers

Padding, background color, foreground color, borders. Maybe others.

Follow-on from #7.

Robot sample problem on Windows

I have built the latest version from repository and running the robot sample in a command prompt on Windows 11 results in the following output:

D:\Project\mosaic\samples\robot\build\distributions\robot-0.4.0-SNAPSHOT\robot-0.4.0-SNAPSHOT\bin>robot.bat
Use arrow keys to move the face. Press ΓÇ£qΓÇ¥ to exit.
Position: 0, 0   World: 20, 10

^_^

Nothing happens when I press the arrow keys, q works for quit.

Box sizing modifiers

The ability to specify how large a box is explicitly.

This should clip if you exceed the size. Otherwise you're padded with spaces (as is the default anyway).

Consider alternative layout engine

Yoga was fun because it was what Ink used, but we can probably get away with using ContraintLayout's pure-Java engine. Being able to jettison the JNI/native stuff would be a huge win. We get very little from Yoga and don't plan to extract much more from it. We can surely do the same or similar with ContraintLayout.

Stretch Text

I played around a bit trying to reproduce
image image

I found two things I couldn't do:

  • stretch each item to full width
    I think this is calculatable based on item.name lengths, but it feels like that goes against having a "layout system" around you.
  • stretch the separator and top-bottom adorning box dynamically (right now it's hard-coded)

Also probably related, I managed to shrink the main menu (Left smaller, and Files right next to it, but it feels like I am hacking something mosaic (or maybe a custom layout) should be doing in some way:
image image

@Composable
fun MenuDropdown(menus: List<Menu>, focus: MenuItem) {
	Row {
		Text("  ") // screen padding
		menus.forEach { menu ->
			if (focus in menu.items) {
				ExpandedMenu(menu, focus)
			} else {
				Text(" ".repeat(2 + menu.name.length + 2)) // hack to add "just enough" padding
			}
		}
	}
}

Move projects into subdir

compose/, mosaic/, and yoga/ modules should all go under a new mosaic/ directory. This will allow a new root project to use includeBuild to pull in the Gradle plugin for the examples/ rather than hand-rolling the Compose setup.

Box/Row/Column background

Might be a different solution to the same problem (#81), but probably a capability missing:
image

This knows how large the Column is horizontally, yet I have to give every single Text() a separate background = to make it look like a box (and fail because #81).

Column {
	Text("+----------------+", color = Black, background = Cyan)
	menu.items.forEach { item ->
		if (item.separator) {
			Text("| ${item.name} |", color = Black, background = Cyan)
		} else {
			Text("| ${item.name} |", color = White, background = Cyan)
		}
        }
	Text("+----------------+", color = Black, background = Cyan)
}

I would expect to have some syntax to achieve the same (or even better fully filled box-like) UI:

Column(background = Cyan) {
	Text("+----------------+", color = Black)
	menu.items.forEach { item ->
		if (item.separator) {
			Text("| ${item.name} |", color = Black)
		} else {
			Text("| ${item.name} |", color = White)
		}
        }
	Text("+----------------+", color = Black)
}

Multiplatform Yoga JNI missing

Right now we build only for the host platform.

We need to do two things:

  • Build for each platform (fan out on CI for yoga JNI, store artifacts, fan in for Yoga core build)
  • Embed them all into the jar and add a platform-aware native library loader.

Fun times...

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.