A pure Kotlin UI framework for the Web.

Overview
doodle

A pure Kotlin, UI framework

Kotlin 1.5.31 License: MIT Chat: on slack

Single Language

Doodle is written entirely in Kotlin and so are its apps. Doodle Web Applications do not use HTML, CSS styles or Javascript libraries. In fact, apps are not aware of the Browser (or Browser concepts) at all, and can be written entirely as common (cross-platform) code in multi-platform setups.

Multiple Platforms

Doodle supports both JS (Browser) and JVM (alpha) targets; so common code apps are able to run in the Browser and Desktop (with other platforms planned) without modification. This means you write widgets once and use them on either platform. The only difference is how apps are launched on each platform.

Expressive

Creating expressive, intuitive apps is natural with Doodle. It makes complex rendering easy with powerful, vector-oriented rendering, provides fully customizable layouts and simplifies pointer and keyboard handling.

Simply define your View hierarchy, business logic and go.

Vector Oriented

It is easy to build beautifully detailed UIs with Doodle. All rendering in Doodle is vector-oriented; so ellipses, paths, lines, gradients, affine transforms etc. are as simple to use as images and rectangles.

Precise

Doodle gives you control over all aspects of the UI presentation, including pixel-level positioning, making it easier to precisely control rendering.

Modular

Doodle has several libraries and a collection of modules. This allows selective adoption of various features and helps with bundle size. Apps written with Doodle are also dependency-injected; and there are no global objects or state to make mocking challenging.

Hello World

import io.nacular.doodle.application.Application
import io.nacular.doodle.core.Display
import io.nacular.doodle.core.plusAssign
import io.nacular.doodle.core.view
import io.nacular.doodle.drawing.Color.Companion.Black
import io.nacular.doodle.drawing.text

class HelloWorld(display: Display): Application {
    init {
        display += view {
            size   = display.size
            render = {
                text("Hello, world!", color = Black)
            }
        }
    }

    override fun shutdown() {}
}

fun main() {
    application {
        HelloWorld(display = instance())
    }
}

Documentation

Check out the documentation site for more details and examples.

Tutorials

You can find helpful tutorials as doodle-tutorials.

Feedback

Doodle is still under active development, so there are going to be gaps and bugs. Please report issues, and submit feature requests.

You can also join the discussion on the #doodle Kotlin Slack channel.

Comments
  • Forms example?

    Forms example?

    Hi, just wondering, but would you have an example with a form + layout? I'm looking for something like this, and I'm not sure if there's a formlayout or tablelayout which I could use for this?

    image

    opened by bodiam 27
  • org.jetbrains.skiko:skiko-jvm-runtime-macos-x64:0.4.7 missing

    org.jetbrains.skiko:skiko-jvm-runtime-macos-x64:0.4.7 missing

    Hi, I'm still trying to run the HelloWorld class, but I'm failing due this error:

    Execution failed for task ':compileKotlin'.
    > Error while evaluating property 'filteredArgumentsMap' of task ':compileKotlin'
       > Could not resolve all files for configuration ':compileClasspath'.
          > Could not find org.jetbrains.skiko:skiko-jvm-runtime-macos-x64:0.4.7.
            Searched in the following locations:
              - https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-jvm-runtime-macos-x64/0.4.7/skiko-jvm-runtime-macos-x64-0.4.7.pom
            If the artifact you are trying to retrieve can be found in the repository but without metadata in 'Maven POM' format, you need to adjust the 'metadataSources { ... }' of the repository declaration.
            Required by:
                project : > io.nacular.doodle:desktop:0.6.0 > io.nacular.doodle:desktop-jvm:0.6.0
    

    My build.gradle.kts looks like this:

    plugins {
        id("org.jetbrains.kotlin.jvm") version "1.5.30"
        application
    }
    
    version = "1.0.0"
    group = "com.my.cool.app"
    
    repositories {
        mavenCentral()
    }
    
    kotlin {
        target.compilations.all {
            kotlinOptions {
                jvmTarget = "11"
            }
        }
    
        val doodleVersion = "0.6.0" // <--- Latest Doodle version
    
        dependencies {
            implementation("io.nacular.doodle:core:$doodleVersion")
            implementation("io.nacular.doodle:desktop:$doodleVersion")
    
            // Optional
            implementation("io.nacular.doodle:controls:$doodleVersion")
            implementation("io.nacular.doodle:animation:$doodleVersion")
            implementation("io.nacular.doodle:themes:$doodleVersion")
        }
    }
    
    application {
        mainClass.set("HelloWorld")
    }
    

    And the only code I have, my HelloWoorld, looks like this:

    import io.nacular.doodle.application.Application
    import io.nacular.doodle.application.application
    import io.nacular.doodle.core.Display
    import io.nacular.doodle.core.plusAssign
    import io.nacular.doodle.core.view
    import io.nacular.doodle.drawing.Color.Companion.Black
    import io.nacular.doodle.drawing.text
    import org.kodein.di.instance
    
    class HelloWorld(display: Display): Application {
        init {
            display += view {
                size   = display.size
                render = {
                    text("Hello, world!", color = Black)
                }
            }
        }
    
        override fun shutdown() {}
    }
    
    fun main() {
        application {
            HelloWorld(display = instance())
        }
    }
    
    opened by bodiam 5
  • Is there a Dropdown component available?

    Is there a Dropdown component available?

    I had a look at the documentation, and I was wondering if a dropdown component was available as an input?

    I saw the list item, and input item, but no dropdown. Thanks!

    enhancement 
    opened by epragtpks 5
  • Installation issue

    Installation issue

    Hi,

    I've just found your project and I want to try it! Unfortunately I can't install it. Both maven & jcenter are configured in repositories, but your artifacts can't be found.

    I will try to install it as a third-party module and it should work.

    Regards,

    opened by Gounlaf 5
  • HelloWorld not working

    HelloWorld not working

    Hi there, I've finally started with Doodle, so I setup my project and tried the HelloWorld, but it doesn't compile. Could you please fix it?

    There are 2 issues:

    • On line 23, there's a call to application. But there's no such thing as application. Maybe the import to import io.nacular.doodle.application.application is missing? Not sure.
    • Another issue is: HelloWorld(display = instance()). What's instance()? It seems there's no reference to anything like instance(), and I can't find a suitable candidate.

    Thanks, Erik

    opened by bodiam 4
  • Native dependency missing

    Native dependency missing

    Using the sample code in the Application section of the Guide, a pure JVM Kotlin build script and the latest Intellij IDEA (IntelliJ IDEA Community Edition 2021.3.2) I get the following error when attempting to run the main class from Intellij:

    Exception in thread "main" java.lang.ExceptionInInitializerError at org.jetbrains.skiko.SkiaWindow$1.invoke(SkiaWindow.kt:7) at org.jetbrains.skiko.SkiaWindow$1.invoke(SkiaWindow.kt:7) at org.jetbrains.skiko.SkiaWindow.(SkiaWindow.kt:9) at org.jetbrains.skiko.SkiaWindow.(SkiaWindow.kt:5) at io.nacular.doodle.application.ApplicationHolderImpl.(Application.kt:86) at io.nacular.doodle.application.ApplicationHolderImpl.(Application.kt:72) at io.nacular.doodle.application.ApplicationHolderImpl$Companion.invoke(Application.kt:168) at io.nacular.doodle.application.ApplicationKt.createApplication(Application.kt:70) at MainKt.main(Main.kt:12) at MainKt.main(Main.kt) Caused by: org.jetbrains.skiko.LibraryLoadException: Cannot find skiko-windows-x64.dll.sha256, proper native dependency missing. at org.jetbrains.skiko.Library.load(Library.kt:72) at org.jetbrains.skiko.SkiaLayer.(SkiaLayer.kt:25) ... 10 more

    Reinstalling Intellij has not fixed the problem.

    opened by Krzyjan 3
  • I cannot get the doodle-tutorials working

    I cannot get the doodle-tutorials working

    I am trying to run the CalculatorRunner jvm main instance, but without luck. I keep on getting:

    Exception in thread "main" java.lang.ExceptionInInitializerError at org.jetbrains.skiko.SkiaWindow$1.invoke(SkiaWindow.kt:7) at org.jetbrains.skiko.SkiaWindow$1.invoke(SkiaWindow.kt:7) at org.jetbrains.skiko.SkiaWindow.<init>(SkiaWindow.kt:9) at org.jetbrains.skiko.SkiaWindow.<init>(SkiaWindow.kt:5) at io.nacular.doodle.application.ApplicationHolderImpl.<init>(Application.kt:86) at io.nacular.doodle.application.ApplicationHolderImpl.<init>(Application.kt:72) at io.nacular.doodle.application.ApplicationHolderImpl$Companion.invoke(Application.kt:168) at io.nacular.doodle.application.ApplicationKt.createApplication(Application.kt:70) at io.nacular.doodle.examples.MainKt.main(main.kt:21) at io.nacular.doodle.examples.MainKt.main(main.kt) Caused by: org.jetbrains.skiko.LibraryLoadException: Cannot find libskiko-linux-x64.so.sha256, proper native dependency missing. at org.jetbrains.skiko.Library.load(Library.kt:72) at org.jetbrains.skiko.SkiaLayer.<clinit>(SkiaLayer.kt:25) ... 10 more

    I saw another issue which was resolved by adding the jetbrains repo, but this one is defined in the tutorials so that is not the problem.

    My setup is Ubuntu 21.10 with IntelliJ IDEA 2021.3.1 (Community Edition). I just cloned the doodle-tutorials repo and tried to run the example app. I also tried to look for this error but could not get any good working solution for my situation.

    Really curious what I am doing wrong here.

    opened by mminke 3
  • When running demos: Unable to load '@webpack-cli/serve' command - TypeError: options.forEach is not a function

    When running demos: Unable to load '@webpack-cli/serve' command - TypeError: options.forEach is not a function

    I cloned the demos from github but I am unable to run them.

    When running any of the jsBrowser*Run targets, I get the following error:

    [webpack-cli] TypeError: options.forEach is not a function
    [webpack-cli] TypeError: options.forEach is not a function
        at WebpackCLI.makeCommand (C:\Users\pauls\projects\kforum\build\js\node_modules\webpack-cli\lib\webpack-cli.js:173:21)
        at ServeCommand.apply (C:\Users\pauls\projects\kforum\build\js\node_modules\@webpack-cli\serve\lib\index.js:41:19)
        at loadCommandByName (C:\Users\pauls\projects\kforum\build\js\node_modules\webpack-cli\lib\webpack-cli.js:907:35)
        at async Command.<anonymous> (C:\Users\pauls\projects\kforum\build\js\node_modules\webpack-cli\lib\webpack-cli.js:1462:17)
        at async Promise.all (index 0)
        at async WebpackCLI.run (C:\Users\pauls\projects\kforum\build\js\node_modules\webpack-cli\lib\webpack-cli.js:1500:9)
        at async runCLI (C:\Users\pauls\projects\kforum\build\js\node_modules\webpack-cli\lib\bootstrap.js:11:9)```
    
    I found [this stackoverflow](https://github.com/webpack/webpack/issues/14443) which seems to talk about the issue, but I don't know how to adjust the version of webpack that is being used by kotlin.
    
    Can you guys repro and do you have any suggestions?
    opened by paulseawa 3
  • Search in documentation not working well

    Search in documentation not working well

    I'm not sure how the search works in the documentation, but it seems to not work well. When I search for "Layout", I'm getting a lot of 404 errors:

    image

    (The Buttons gives a 404, and so does Defining our application, The Application, etc)

    opened by bodiam 3
  • Example gradle script from documentation is giving errors

    Example gradle script from documentation is giving errors

    I'm using the example script for multi-platform projects with Kotlin Gradle from https://nacular.github.io/doodle/#/installation.

    e: /path/to/Doodle/build.gradle.kts:19:20: Unresolved reference. None of the following candidates is applicable because of receiver type mismatch: 
    public fun Project.dependencies(configuration: DependencyHandlerScope.() -> Unit): Unit defined in org.gradle.kotlin.dsl
    

    Using

    plugins {
        id ("org.jetbrains.kotlin.multiplatform") version "1.4.21"
    }
    
    version = "1.0.0"
    group   = "com.my.cool.app"
    
    repositories {
        jcenter     ()
        mavenCentral()
    }
    
    val doodleVersion = "0.5.2" // <--- Latest Doodle version
    
    kotlin {
        js {}
    
        sourceSets {
            commonMain.dependencies {
                implementation ("org.jetbrains.kotlin:kotlin-stdlib-common")
    
                implementation ("io.nacular.doodle:core:$doodleVersion")
                implementation ("io.nacular.doodle:browser:$doodleVersion")
    
                // Optional
                // implementation ("io.nacular.doodle:controls:$doodleVersion")
                // implementation ("io.nacular.doodle:animation:$doodleVersion")
                // implementation ("io.nacular.doodle:themes:$doodleVersion")
            }
    
            jsMain.dependencies {
                implementation ("org.jetbrains.kotlin:kotlin-stdlib-js")
            }
        }
    }
    
    opened by QazCetelic 2
  • Horizontal scrollbars shouldn't appear in some examples

    Horizontal scrollbars shouldn't appear in some examples

    I noticed a consistent behavior with the scrollbars that might be not desireable in the UI Components overview (https://nacular.github.io/doodle/docs/ui_components/overview)

    The examples that contain a vertical scroll the view's width is not well adjusted and an horizontal scrollbar appears.

    On Windows 10, latest Firefox and Chrome

    scrollbars

    scrollbars2

    scrollbars3

    scrollbars4

    opened by hiperbou 1
  • Inputs not working on iPhone 6

    Inputs not working on iPhone 6

    Using an iPhone 6S 12.1.3, Chrome and Safari browsers.

    It seems like the input is not working on any of the UI Components examples: https://nacular.github.io/doodle/#/controls

    wontfix 
    opened by hiperbou 2
Releases(v0.9.0)
  • v0.9.0(Jan 4, 2023)

    Features

    New Constraint Layout Fully Adopted

    The new layout system was introduced in 0.8.2 alongside the former approach is now fully adopted and the old system has been removed. You can get more details on how the new system works in the previous release notes. This release does bring a few additional changes as well. The main difference is that parent constraints are now "constant" by default. This means constraints like the following will NOT modify the parent bounds:

    constraint(view) {
        it.width eq parent.width - 10 // parent.width is treated like a simple number and is not updated to meet the constraints
    }
    

    However, you can still make parent properties writable by being explicit as follows:

    constraint(view) {
        it.width eq parent.width.writable - 10 // now parent.width could be modified if needed to meet the constraints
    }
    

    There were also some improvements to this new layout engine. Constraints now default all parent properties to read-only. This means it is no longer possible to use the readOnly field on parent properties. Instead, each can be turned into a "writable" value using the new writable field on each.

    Revamped Animation APIs

    The Animation APIs have been updated to make them more powerful and easier to use. You can now animate a lot more data types, with built-in support for common ones like (Int, Float, Double, Size, Position, Rectangle, Color, and Measure). It is also easy to add new types when they can be converted to a numeric representation. The animation APIs also let you create animatable properties, and provides a few different types of animations, including tweens, key-frames, and repetition.

    New Diff API for ObservableList

    A new list diff algorithm (based on Google's diff-match-patch) was introduced internally in 0.8.2 to support the new constraint system. It has now been adopted as the mechanism for notifying about changes to ObservableList and related types (i.e. FilteredList). This algorithm scales better and more intuitive than what was in place before. But the APIs are very different.

    Old

    observableList.changed += { list, removed: Map<Int, T>, added: Map<Int, T>, moved: Map<Int, Pair<Int, T>> ->
        
    }
    

    New

    observableList.changed += { list, changes: Differences<T> ->
        
    }
    

    The new API indicates changes via the Differences interface. This interface lets you iterate over a series of Difference<T> instances that indicate what change was applied to the list at a given index.

    observableList.changed += { list, changes: Differences<T> ->
        changes.forEach { difference ->
            when (difference) {
                is Delete -> {}
                is Insert -> {}
                else      -> {} // Equal
            }
        }  
    }
    

    Differences also let you compute moves optionally in case they matter to your handling of changes.

    diffs.computeMoves().forEach {
        when (it) {
            is Insert -> {
                it.items.forEach { item ->
                    if (it.origin(of = item) == null) {
                        // not a move
                    }
                }
            }
            is Delete -> {
                it.items.forEach { item ->
                    when (val destination = it.destination(of = item)) {
                        null -> {
                            // not a move
                        }
                        else -> {
                           // move
                       }
                    }
                }
            }
            else -> {}
        }
    }
    

    Improved Tables and TreeTables

    • Table/TreeTable now support footers
    • Table/TreeTable now allow changing the visibility of their header (and footer)
    • Tables and their related types now handle being added to a ScrollPanel. In this case they will
    • BasicMutableTableBehavior now shows sort order via an icon
    • New EqualSizePolicy and ProportionalSizePolicy types to support different table header sizing strategies
    • New way to specify the stickiness of headers and footers within tables and their derivatives
    • New KeyValueTable to simplify showing Maps in tabular form
    • Table and its derivatives no longer prevent their first column from resizing
    • BasicTreeTableBehavior now takes an iconFactory instead of an icon color
    • TreeTable now scales the contents of its internal ScrollPanel like Table does
    • TableHeaderCell (used for basic table behaviors) now repaints when disabled/enabled to update colors correctly.
    • TableHeaderCell no longer changes color on pointer pressed if its column is not movable
    • TableHeaderCell no longer uses the Grabbing icon when dragging
    • BasicMutableTableBehavior now reflects initial sorting of its table.
    • Ideal size for Table (and derivatives)
    • TableBehavior moveColumn now provides the distance it will move. This allows for constant velocity animations

    New FileSelector Control

    Form Controls

    • New slider, rangeSlider, circularSlider and circularRangeSlider controls
    • New file and files controls

    APIs

    • Replaced constrain(videw: View, within: Rectangle) method with faster <T: Positionable> Iterable<T>.constrain(using: ConstraintDslContext.(Bounds) -> Unit, within: (Int, T) -> Rectangle)
    • new ifNull utility function
    • ScrollPanel now exposes scrollbar dimensions and notifies listeners when they change.
    • CheckBoxRadioButtonBehavior now takes an iconInset which indicates the padding around the icon (does not apply to icon-text spacing gap).
    • BasicMutableTableBehavior now takes a footerColor
    • New simpleTableCellEditor function for creating TableEditors that modify a single cell at a time.
    • New ConvexPolygon.map function to create a new polygon by transforming another
    • New DSL to use an ItemVisualizer of a different time after mapping its inputs: ItemVisualizer<R, C>.after(mapper: (T) -> R): ItemVisualizer<T, C>
    • New ExpandableItem interface for use with Tree and related views.
    • [API] MutableTreeNode's children is now a MutableList
    • Updated path(data: String) function, so it can return null
    • New cancelable delegate

    Performance

    • General
      • Minor change to AffineTransformImpl to support single Point invocations without converting to array/list
      • RenderManagerImpl now uses View.generationNumber instead of an ancestor comparison for layout order
      • ConstraintLayoutImpl handles changes in constant parts of constraints more efficiently. This makes readOnly usage faster.
    • Browser
      • Now using native JS Set/Map in some performance critical areas
      • TreeSetJs no longer uses recursion

    Fixes | Improvements

    • General
      • Bug in HorizontalFlowLayout when container is empty
      • typo in ScrollPanelConstraintDslContext property
      • optionalRadioList now allows deselection of its items
      • Layout of some Form list items
      • SplitPanel constraints no longer modify parent
      • ScrollPanel how ignores layout when its content scrolls
      • ListItem no longer replaces layout if children remain the same
      • Bug in Table that could lead to an incorrect index being used when generating a row
      • Bug in TreeTable that could lead to an incorrect index being used when generating a row
      • Bug in basic list positioning for cases when the list has now width or height
      • Bug in FilteredList when filter is null
      • Improved how Table and TreeTable handle visible scrollbars. They both now avoid showing horizontal bars and adjust their headers accordingly if the vertical bar is visible.
      • Bug in TreeColumns where items weren't being recycled
      • Bug in View.generationNumber updating
      • Issue in CheckBoxRadioButtonBehavior where icon inset was not being included in the button's idealSize.
      • MutableTable keeps itself sorted when its model changes.
      • Bug in ListItem that caused default cellAlignment to be ignored
      • No longer doing layout in TreeTable when columnSizePolicy changed before it has a behavior
      • View.scrollTo now handles nested ScrollPanels
      • List now checks the point that is 1 pixel up and left from its display rect bottom-right corner when updating visible cells. This avoids accidentally including cells that are hidden.
      • Bug in Tree that rendered rows incorrectly in some cases
      • ScrollPanel now matches content ideal size right away
    • Browser
      • Minor icon alignment issue in native button behavior
      • Native button behavior now properly updates button idealSize
      • Bug in native Slider snapping Behavior for some values of ticks
      • Default Key event behavior now prevented when event consumed.
    • Desktop
      • Bug in desktop DragManagerImpl where unsupported MimeTypes were not properly handled

    Dependencies

    • Kotlin -> 1.7.21
    • Kodein -> 7.16.0
    • Skiko -> 0.7.40
    • Kover -> 0.6.1
    • Dokka -> 1.7.20
    • Measured -> 0.3.2
    Source code(tar.gz)
    Source code(zip)
  • v0.8.2(Sep 15, 2022)

    Features

    New Cassowary based Constraint Layout

    This release includes a new layout engine that will soon replace the current constraint based layout. This new implementation is based on the well known Cassowary algorithm and is therefore much more expressive and capable. The new implementation has a very similar API to the current one, so migration is less difficult. But there are key differences that mean current constraints won't always translate exactly. The new API is located in the io.nacular.doodle.layout.constraints package.

    Basic Usage

    Constraints are created using the new constrain builder found in io.nacular.doodle.layout.constraints. It takes a list of Views and a constraint block with rules for how those Views are related to each other and their parent. Each View is mapped to a Bounds within the constraint block. These contain properties that let you manipulate the View's bounds.

    constrain(view1, view2, view3) { v1, v2, v3 ->
      // constraints defined here
    }
    

    Constraints are now specified using equations and inequalities. These are written as expressions that contain a single operator to indicate equal (eq), less-than-or-equal (lessEq), and greater-than-or-equal (greaterEq). The layout will solve the set of equations and assign values for each of the properties within each relationship.

    constrain(view1, view2, view3) { v1, v2, v3 ->
      v1.right                          eq     parent.right - 10
      v2.right                          lessEq v1.left - 10
      v1.height + v2.height + v3.height eq     parent.height
    }
    

    Expressions with eq are similar to what the legacy constraint system offers. But there are still very important differences (see below). While lessEq and greaterEq are entirely new ways of expressing relationships. They provide a much easier way to express boundary conditions than the legacy system.

    This example shows how you could keep a view's horizontal bounds confined to a region within its parent while still letting it move or scale.

    constrain(view) {
      it.left   greaterEq 10
      it.right  lessEq    parent.right - 10
    }
    

    Differences Compared to Legacy

    Constrain Blocks Are Live

    The new constraint builder looks a lot like the legacy one: a list of Views with a configuration block. But there is a major difference. The new layout will actually invoke that provided block on every layout, while the legacy implementation only calls its block once, treating it as data rather than an active handler.

    This makes the legacy layout a lot more cumbersome and less intuitive. That is because it is not clear at all that the constraint lambda does not behave like others and will only be invoked once. This fact also means legacy constraints require extra hacks to capture external variables that update over time. It is also impossible to use control logic with the legacy system to dynamically change constraints; this is no longer an issue.

    Legacy

    constrain(view) {
        it.width = parent.width - capturedValue // capturedValue is only read once when the layout is constructed
    }
    
    constrain(view) {
        it.width = parent.width - { capturedValue } // an inner lambda is needed to keep capturedValue updated
    }
    
    container.layout = constrain(view1, view2) { v1, v2 ->
        // This will only be evaluated once when the Layout is constructed,
        // which means the constraint won't flip as container.width changes
        when {
            container.width < 100 -> it.width = parent.width / 2
            else                  -> it.width = parent.width
        }
    }
    

    New

    constrain(view) {
        it.width eq parent.width - capturedValue // capturedValue is read on every layout as expected
    }
    
    container.layout = constrain(view1, view2) { v1, v2 ->
        // This will result in different constraints being applied dynamically as
        // container.width crosses 100
        when {
            container.width < 100 -> it.width eq parent.width / 2
            else                  -> it.width eq parent.width
        }
    }
    

    Views Don't Need To Be Siblings

    You cannot constrain Views that are not siblings in the legacy implementation. This is intended to avoid unintuitive behavior, but it made constraints more cumbersome to use, since it forces all Views to be in the same container before configuration. The new implementation takes a different approach. It allows you to configure any set of Views, regardless of their hierarchy. But, it only updates the Views that within the Container it is laying out. All other Views are treated as readOnly. This adjustment happens automatically as the View hierarchy changes. A key consequence is that Views outside the current parent will not conform to the constraints. This avoids the issue of a layout for one container affecting the children of another.

    Legacy

    val view1 = view {}
    val view2 = view {}
    val container1 = container{
      children += view1
      layout    = constrain(view1, view2) { v1, v2 ->
        v1.width = v2.width // <======================= Error since view2 does not share the same parent ❌
      }
    }
    

    New

    val view1 = view {}
    val view2 = view {}
    val container1 = container{
      children += view1
      layout    = constrain(view1, view2) { v1, v2 ->
        v1.width eq v2.width // <======================= v2.width treated as immutable value (i.e. v2.width.readOnly) ✅
      }
    }
    

    Relationships Are Bidirectional

    Legacy constraints are all defined using assignment, making them inherently unidirectional. In the example below, v1 would get its width from v2, but v2's width would not be modified.

    Legacy

    constrain(view1, view2) { v1, v2 ->
        v1.width = v2.width // v2.width would not be modified
    //  v2.width = v1.width is not the same as above
    }
    

    The new implementation produces bidirectional constraints by default. This means expressions can be written like mathematical equations, where the order of terms is often (but not always) unimportant. It is still possible to create unidirectional relationships using readOnly on a property or an entire expression.

    New

    constrain(view1, view2) { v1, v2 ->
        v1.width eq v2.width // both v1.width and v2.width can be changed to ensure they are equal 
    //  v2.width eq v1.width is the same as above
    }
    
    constrain(view1, view2) { v1, v2 ->
        v1.width eq v2.width.readOnly                    // v2.width won't be modified
       (parent.height - v2.height).readOnly eq v1.height // neither parent.height nor v2.height will be modified
    }
    

    Bidirectionality applies to parent constraints as well (if the View is not top-level). So the following constraint might actually update the parent's width.

    constrain(view) {
        it.width eq 100
        it.width eq parent.width // parent.width would be modified to ensure the equality
    }
    

    This can be prevented by using readOnly.

    Constraints Added/Removed Symmetrically

    The legacy constraint system allows single Views to be unconstrained. This is helpful when you need to let a View move more freely, or when it is removed from its parent. The API for this is as follows:

    Legacy

    val layout: ConstraintLayout = constrain(view1, view2) { v1, v2 ->
        // ...
    }
    
    layout.unconstrain(view2) // all associated constraints removed
    

    The new API changes this approach to avoid confusion now that constraint blocks are invoked repeatedly during layout. This avoids the case where some subset of the constraints in a block are "removed", even though the block is still being invoked. This means constraint blocks behave a lot more like event handlers.

    New

    val constraints: ConstraintDslContext.(Bounds, Bounds) -> Unit = { v1, v2 ->
      // ...
    }
    
    val layout: ConstraintLayout = constrain(view1, view2, constraints)
    
    // layout.unconstrain(view2) // no longer available
    
    layout.unconstrain(view1, view2, constraints) // remove all constraints in a block
    

    Constraints Have Strengths

    The new API allows you to define relationships that can be in conflict with each other. Such situations result in an error since they have no clear solution. API also provides a way to resolve such cases with a relative priority or strength of constraints. This allows the engine to break lower strength constraints when there are conflicts.

    All constraints have the Required strength by default. This is the highest possible strength that tells the engine to enforce such a constraint. But you can specify the strength explicitly as follows.

    constraint(view) {
       it.left  eq        0
       it.width greaterEq 100
      (it.right eq        parent.right) .. Strong // constraint will be broken if needed since the one above is Required
    }
    

    Form Controls

    • New singleChoiceList for choosing a single item like radioList offers
    • New optionalSingleChoiceList for choosing zero or one item like optionalRadioList offers

    APIs

    • Always and WhenInvalid RequiredIndicatorStyles (used for form fields) no longer require an explicit string when constructed, and will default to using "*"
    • labeled Form fields can now change the vertical spacing between the label and nested field via a new defaultLayout method that takes an optional spacing parameter.
    • Added new on helper for KeyListeners

    Render Performance

    • Avoid unnecessary List copy in View.child(at)
    • Avoid display tree traversal to determine if a View is displayed
    • No longer updating GraphicsSurface for Views with no area.
    • Made SquareMatrix application to single Point more efficient since it is the more common case.

    Fixes | Improvements

    • General
      • Bug in BasicTreeBehavior when Tree width is less than node indent
      • Bug in TreeRow that affected icon placement
      • Tree now allows its root node to expand/collapse
      • Bug in Tree height when root node visible
      • Rendering issue in Tree
      • Invalid path handling in SimpleTreeModel
      • Hover rendering for radio buttons in BasicTheme
      • Minor text inconsistency in RequiredIndicatorStyle for some form labels
      • Bug in TextInput selection updating when range deleted
      • Bug in TextInput paste updating when range deleted
      • Bug in ClosedRange.intersect
      • Bug in ClosedRange.intersects
      • Bugs in native TextField behavior text selection
    • Browser
      • ImageLoader now queues up requests for loading items and resumes/fails them correctly when it finishes/errors
      • Now using pagehide event instead of unload to shut down an app
      • Suppressing default tap highlight on Webkit.
      • Improve handling of javascript file being loaded within header.
      • Native ScrollPanel bug in edge case
    Source code(tar.gz)
    Source code(zip)
  • v0.8.1(Jul 15, 2022)

    APIs

    It is now possible to take the scrollbar size into account when specifying the width and height of a ScrollPanel.content. There are 2 new properties (scrollBarWidth and scrollBarHeight) available on the parent property when setting contentWidthConstraints and contentHeightConstraints. These values are magnitudes and can be combined with the panel's width or height to ensure the content does not go behind the scrollbars.

    These values are dynamic and will return 0.0 when the scrollbars are not visible.

    scrollPanel.apply {
      contentWidthConstraints  = { parent.width  - parent.scrollBarWidth  }
      contentHeightConstraints = { parent.height - parent.scrollBarHeight }
    }
    

    Fixes | Improvements

    • General
      • #38 ScrollPanel now exposes scrollbar dimensions via ContentConstrains that can be used when specifying contentWidthConstraints and contentHeightConstraints neddy 7/13/22, 8:21 PM
      • #37 Where BasicDropdownBehavior was not setting the Dropdown's selection to an updated index when selected.
    • Browser
      • No longer delaying native scroll panel updates neddy 32 minutes ago
    • Desktop
      • Graphics state which preventing some native control UIs (i.e. scroll panels) from rendering properly) neddy Yesterday 8:06 PM
      • Fix native slider mouse handling neddy Yesterday 8:05 PM
      • Improved cursor handling for native scrollbars neddy Yesterday 7:06 AM
      • Cursor updating neddy 7/13/22, 8:19 PM
    Source code(tar.gz)
    Source code(zip)
  • v0.8.0(Jul 9, 2022)

    Features

    3D Transforms

    Views can now have full 3D transformations via their transform property. This works because AffineTransform has been updated to support scale, rotation, and translations that involve x, y and z axes. The result is that Views can now be placed in a full 3D space by simply giving them a transform that has been modified accordingly.

    import io.nacular.doodle.drawing.AffineTransform.Companion.Identity
    import io.nacular.measured.units.Angle.Companion.degrees
    
    // Rotate this View around they y-axis (through its center) by 45°
    view.transform *= Identity.rotateY(around = view.center, by = 45 * degrees)
    

    Views can also now render in 3D, since Canvas also supports the new 3D transforms.

    view {
        render = {
            transform(Identity.rotateY(by = 45 * degrees)) {
                // ...
            }
        }
    }
    

    3D Perspective

    A realistic 3D space requires more than just affine transforms (which keep parallel lines parallel). Simulating this requires perspective transforms. Views now have a camera property, which gives them a perspective when combined with a 3D transform. The following allows the y-axis rotation to look more realistic.

    import io.nacular.doodle.drawing.AffineTransform.Companion.Identity
    import io.nacular.measured.units.Angle.Companion.degrees
    
    // Rotate this View around they y-axis (through its center) by 45°
    view.transform *= Identity.rotateY(around = view.center, by = 45 * degrees)
    
    // Position the View's camera to apply some realistic perspective warping
    view.camera = Camera(position = view.center, distance = 1000.0)
    

    Canvas also takes a Camera in its transform method to enable perspective.

    view {
        render = {
            transform(Identity.rotateY(by = 45 * degrees), camera = Camera(Origin, distance = 1000.0)) {
                // ...
            }
        }
    }
    

    Kotlin 1.7 Support

    Doodle now supports 1.7.10!!

    APIs

    • New View.intersects method to simplify hit detection. This method works on a point that has been transformed into the View's plane.
    • Improved utilities for PointerListeners and PointerMotionListeners that need to monitor a subset of the events.
    • ConvexPolygon.reversed creates a polygon with the points in reverse order
    • New AffineTransform2D type to handle cases where full 3D transforms aren't supported (like PatternPaint transforms)
    • AffineTransform now represents 3D transforms and its APIs have been updated to reflect this
    • Vector2D (alias for Point) and Vector3D types to represent 2D and 3D points
    • [Breaking] Path and PathBuilder are now sealed
    • [Breaking] AffineMatrix3D is now internal
    • [Breaking] Remove AffineTransform extensions for times/div(Number), and plus/minus(AffineTransform)
    • [Breaking] No longer exposing AffineMatrix3D
    • [Breaking] Fixed bug in squareMatrixOf where row and col were reversed

    Fixes | Improvements

    • General
      • Improved Matrix performance by switching from List to Array
      • Render issue in PathIcon
      • AffineTransform.scale when z != 1
      • Edge case in RenderManagerImpl when View becomes invisible before it is first rendered
      • Bug in FilteredList iterator implementation
    • Browser
      • UrlView not updating url value
      • Text can become blurry on Windows when transform applied to a View
      • Fixed clipping withing PatternPaint
      • Added work-around for cases (i.e. in Document) where RealGraphicsSurface is created as top-level, but is not within the display root.
    • Desktop
      • Fixed antialias on Canvas clipping

    Dependencies

    • Kotlin -> 1.7.10
    • Coroutines -> 1.6.3
    • DateTime -> 0.3.3
    • Skiko -> 0.7.22
    • Mockk -> 1.12.4
    Source code(tar.gz)
    Source code(zip)
  • v0.7.2(Mar 25, 2022)

    APIs

    The following make it simpler to create Table cells that need to operate on their row data. Unfortunately this means some existing CellVisualizers that are defined without sufficient context to determine their Table's type will need to be updated with that type.

    • [Breaking Change] Table CellVisualizer and CellInfo are now parameterized with the row as well as column data.
    • CellInfo now has an item property to provide the row item it is showing a portion of.

    Fixes | Improvements

    • General
      • Fixed issue where Table did layout before it is fully initialized if columnSizePolicy is set
      • Fixed Canvas.scale(around: ...)
      • Fixed data sync issue in DynamicList/Tree related to being removed from and re-added to the display.
      • Fixed issue in PathIcon where fill and stroke could be null
      • Fixed TreeColumns rendering
    • Browser
      • Fixed issue with child ordering when View added to parent
      • Fixed native HyperLink rendering was not setting canvas size properly
      • Font smoothing for Mac
    • Desktop
      • Fixed base64 encoded image loading
      • Fixed issues related to releasing graphics resources
    Source code(tar.gz)
    Source code(zip)
  • v0.7.1(Mar 11, 2022)

    APIs

    • BasicSplitPanelBehavior's constructor now takes background and dividerBackground

    Fixes | Improvements

    Better Scrolling Performance [Web]

    Perceived scroll performance has been dramatically improved in this release. This applies to Views that use displayRectChanged events to update themselves. List and many other controls do this to support extremely large collections. They store only what is needed to render the current viewport. The drawback to this before, was that a user could see the gaps while scrolling very quickly.

    This has now been fixed, so these views appear without any tearing as the user scrolls.

    As a result, List and similar collection controls default to caching 0 items outside the viewport. This was some non-zero value before to provide some buffer for scroll tearing.

    • General

      • CommonSplitPanelBehavior now consumes pointer pressed event when it occurs on the divider. This improves usability on mobile
      • BasicTheme now sets SplitPanel background.
      • ScrollPanel layout now uses requiresLayout to request update when content idealSize changes and the panel is set to match it.
      • List no longer does extraneous layouts while scrolling
      • MonthPanel now tries to minimize doing re-layouts.
      • Minor performance improvements
      • Rectangle.toPath now sanitizes the given radius
      • Fixed bug in List that sometimes prevented size updates when items removed.
      • Fixed issue where view was not removed from Display children if moved to another Container.
      • Fixed issue in List display rect rendering.
      • Fixed incorrect item returned from VerticalListPositioner and HorizontalListPositioner in some cases.
      • Fixed bug in IntProgressionModel size.
      • Fixed bugs in SplitPanel handling of visible divider.
    • Browser

      • Replaced use of innerHTML with textContent.
      • Fixed render edge-case
      • Fixed issue where native ScrollPanel behavior did not properly handle content being moved to another container
      • Fixed rendering issue where View canvas contents remain visible if clipCanvasToBounds is false and the View is scaled to 0 size
    • Desktop

      • Now using native look/feel for Swing "native" controls
    Source code(tar.gz)
    Source code(zip)
  • v0.7.0(Feb 25, 2022)

    Features

    New Date controls

    MonthPanel is a customizable control that shows the days of a given month in a year. The panel allows full customization of the rendering for each day as well as selection handling via an ItemVisualizer. This control is a building block that can be used to create calendars and date selectors.

    Days in the month are rendered using the ItemVisualizer. This can be set directly when creating the panel, or it can be provided via an installed MonthPanelBehavior. Selection of days within the panel is controlled by the SelectionModel installed when the panel is created.

    val today = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
    
    val panel = MonthPanel(today, /*itemVisualizer, selectionModel*/)
    

    This release also contains BasicMonthPanelBehavior, which provides a simple day visualizer. You can also install it into a Theme using basicMonthPanelBehavior(...).

    There is also a new DaysOfTheWeekPanel control for displaying days of the week intended for use along with MonthPanel. It allows fully customizable display for each day (via an ItemVisualizer) as well as control over how the days are sorted.

    New behaviors in BasicTheme for MonthPanel via basicMonthPanelBehavior(...) and DaysOfTheWeekPanel via basicDaysOfTheWeekPanelBehavior(...)

    Range Sliders

    New RangeSlider and CircularRangeSlider controls that allow the selection of a range of strongly-typed values. These are analogs to Slider and CiruclarSlider. Both also have new behaviors in Basic Theme.

    You can create these sliders in a similar fashion to the value sliders.

    val slider         = RangeSlider        (range = 0 .. 100, value = 4 .. 10)
    val circularSlider = CircularRangeSlider(range = 0 .. 100, value = 4 .. 10)
    

    Tick Marks For (BasicSliderBehavior and BasicRangeSliderBehavior)

    The basic behaviors can now display tick marks when a Slider or RangeSlider has ticks set to a value larger than 1. This is done via their showTicks parameter. Ticks can be shown either on the slider's groove, or on both the groove and range.

    More Flexible Lists

    List and its variants now have better support for items positioned in an arbitrary way. Previously, the List class itself assumed a vertical layout and only used the y-component of its viewport to determine which items to render. This has been updated so both the top-left and bottom-right points on the List are now used.

    There are now two new ItemPositioners to make laying out vertical hand horizontal lists easier. VerticalListPositioner allows you to specify the number of columns for the list, while HorizontalListPositioner lets you give a number of rows. These can be used directly with custom ListBehaviors or, they can be used with BasicListBehavior directly, or via the basicListBehavior and basicHorizontalListBehavior modules when using a Theme.

    Vertical + Horizontal List Variants

    List contents can be positioned using an ItemPositioner, which the List gets from its ListBehavior. But specifying the layout for a single list means installing a behavior on it directly, since any theme used will provide the same behavior to all lists.

    The new variants allow themes to identify lists that should be explicitly vertical or horizontal for cases when it is less convenient to use explicit behaviors.

    // ... 
    import io.nacular.doodle.theme.basic.list.basicHorizontalListBehavior
    import io.nacular.doodle.theme.basic.BasicTheme.Companion.basicHorizontalListBehavior
    
    val manualHorizontalList = List(0..10).apply {
        acceptsThemes = false                                                       // prevent behavior from being swapped if theme present
        behavior      = basicHorizontalListBehavior(itemWidth = 300.0, numRows = 2) // or some other custom behavior
    }
    
    // Will be same as above when using BasicTheme.basicHorizontalListBehavior() module
    val autoHorizontalList = HorizontalList(0..10, numRows = 2)
    

    New Platform-specific Desktop Artifacts

    Previously apps would depend on the desktop-jvm library when targeting desktop. The problem with this approach is skiko is inherently platform-specific and the published library would depend on the build machine. Therefore, apps would only run on the same OS as the library build machine.

    This release introduces new platform library artifacts to fix this issue. Apps will now depend on the new desktop-jvm-{platform} library instead.

    dependencies {
      implementation("io.nacular.doodle:desktop-jvm:$doodleVersion") // <----- Incorrect
    }
    
    dependencies {
      val osName = System.getProperty("os.name")
      val targetOs = when {
        osName == "Mac OS X"       -> "macos"
        osName.startsWith("Win")   -> "windows"
        osName.startsWith("Linux") -> "linux"
        else                       -> error("Unsupported OS: $osName")
      }
    
      val osArch = System.getProperty("os.arch")
      val targetArch = when (osArch) {
        "x86_64", "amd64" -> "x64"
        "aarch64"         -> "arm64"
        else              -> error("Unsupported arch: $osArch")
      }
    
      val target = "$targetOs-$targetArch"
    
      implementation("io.nacular.doodle:desktop-jvm-$target:$doodleVersion") // <----- CORRECT: use platform-specific version of desktop-jvm artifact
    }
    

    This approach is a bit messy, so there might be some future work on a Gradle plugin to make this simpler.

    APIs

    • Various models now return Result<T> instead of T?, which allows them to hold null and continue representing the concept of a missing or invalid value request.
    • ItemEditor<T> now returns Result<T>.
    • Various bounds properties in View are now final.
    • View's children can now be clipped by a Path.
    • Canvas now supports clipping using a Path.
    • List and its variants now take a set for fitContent that allow control over whether the list fits the width, height or both.
    • TextFit has been replaced with the more general Dimension.
    • Basic slider behaviors can now be constructed with lambdas that allow more dynamic Painting based on the state of the slider.
    • BasicCircularSliderBehavior moved to package io.nacular.doodle.theme.basic.range.
    • Basic range slider behaviors now take start and end knob paint configs.
    • New View extension scrollTo(Rectangle) that handles scrolling the most recent ScrollPanel ancestor to show a rectangle within the View.
    • Changed DragOperation.visualOffset so positive values of x and y move the drag image in the positive x and y direction relative to the cursor, instead of the negative.
    • Renamed GridPanel.verticalSpacing and GridPanel.horizontalSpacing to GridPanel.rowSpacing and GridPanel.columnSpacing.
    • Renamed io.nacular.doodle.controls.spinner.Model to io.nacular.doodle.controls.spinner.SpinnerModel. Also renamed many descendants.

    Fixes | Improvements

    • General
      • Fixed bug in form control check/switch lists where they did not properly update the form.
      • Fixed pointer event bug for disabled ScrollPanel with native behavior.
      • ValueSlider now rounds when converting floating-point values to integers.
      • Improved slider snapping behavior for basic behaviors.
      • GridPanel now updates whenever relevant properties change
      • GridPanel now supports inset
      • GridPanel now has behavior support
      • Fixed several basic behaviors so they rerender their view when installed to avoid stale styling on theme change.
      • Fixed slider support for negative range values.
      • Sliders now update their value when snapToTicks is set.
      • Improved key handling for sliders with tick snapping.
      • Layout constraints are now comparable, making comparisons like constrain(view) { it.left < parent.right - 10 } possible.
    • Browser
      • Fixed bug with corner radius of re-used element.
      • Improved handling of element.dir property so changes to it better connect to `Display.contentDirection.
      • Fixed font loading bug
      • Fixed bug that caused ScrollPanel sorting order when scrollTo called before panel with NativeScrollPanelBehavior is rendered.
      • Fixed bug where ScrollPanel.scrollTo would be ignored if called before native behavior is installed.
      • Fixed drag-drop bug on iOS where visual not rendered
      • Fixed drag-drop on iOS Safari that failed drag operations that do not contain plain text or uri-lists
      • Fixed bug where clip-path not cleared correctly for reused elements.
      • Fixed shadow rendering bug in Safari
      • NativeSliderBehavior supports Vertical orientation
      • NativeSliderBehavior supports snapping to ticks
    • Desktop
      • New platform specific desktop-jvm publications that depend on corresponding Skiko libs. This addresses the underlying issue in #28.
      • Fix native font handling that led to exceptions on some platforms.

    Dependencies

    • Dokka -> 1.6.0
    • Kotlin -> 1.6.10
    • Kotlinx Coroutines -> 1.6.0
    • Kodein -> 7.11.0
    • Measured -> 0.3.1
    • Mockk -> 1.12.2
    • Skiko -> 0.7.12
    Source code(tar.gz)
    Source code(zip)
  • v0.6.5(Jan 15, 2022)

  • v0.6.4(Dec 15, 2021)

  • v0.6.3(Dec 12, 2021)

    Features

    Strongly Typed Sliders

    ValueSlider, Slider and CircularSlider were previously tied to a Double. This meant it was not easy to force use cases where integer values would be more appropriate. This is now fixed, as these controls are now strongly typed to a numeric value instead of only supporting Double.

    val slider = Slider(10 .. 20)
    slider.value = 11.2 // will not compile since slider has a type of Int
    

    More Flexible Layouts

    Layouts are generally triggered whenever their container's size changes or a child of the container has a bounds change. But there are cases when this default behavior does not work as well. A good example is a Layout that depends on a child's idealSize. Such a Layout won't be invoked when the idealSizes change, and will be out of date in some cases.

    This is now fixed by giving Layouts a chance to act when min/ideal-size changes for children of a Container. There are actually 3 new APIs that provide a lot of flexibility in how Layouts behave.

    public fun requiresLayout(container: PositionableContainer, old: Size, new: Size): Boolean = true
    public fun requiresLayout(child: Positionable, of: PositionableContainer, old: Rectangle, new: Rectangle): Boolean = false
    public fun requiresLayout(child: Positionable, of: PositionableContainer, old: SizePreferences, new: SizePreferences): Boolean = false
    

    Form Controls

    • New switch and switchList form controls
    • New spinner form control
    • New framed form control to wrap other controls in a configurable container
    • New check form control with arbitrary view as annotation
    • radioList, checkList, named and labeled form controls now allow a custom renderer and insets for their container
    • sub-forms now allow custom Insets and behavior

    APIs

    • SplitPanel now has explicit events for orientation and content changes.
    splitPanel.orientationChanged += { panel, old, new ->
        
    }
    
    splitPanel.contentsChanged += { panel, old, new ->
    
    }
    

    Fixes | Improvements

    • Improved disabled rendering for several Basic behaviors.
    • SliderBehavior and ProgressIndicatorBehavior now re-render the view when enabled changes.
    • Improved focus handling for CommonButtonBehavior (and its implementations) when pointer pressed.
    • Fixed bug where SplitPanel did not render its children correctly when its orientation was set to horizontal.
    • CommonSplitPanelBehavior now properly handles orientation.
    • Fixed bug in ColorPicker that showed the wrong selection upon creation.
    • Improved BasicDropdownBehavior rendering when buttonWidth is 0.
    • Fixed issue with NativeTextField focus on mobile.
    • [Browser] Space bar is now suppressed properly if consumed by a View.

    Dependencies

    • Added Kover for code coverage
    • Removed Jacoco
    Source code(tar.gz)
    Source code(zip)
  • v0.6.2(Nov 22, 2021)

    Features

    Forms

    Doodle now supports Forms. They make data collection simple, while still preserving flexibility to build just the right experience. The new Form hides a lot of the complexity associated with mapping visual components to fields, state management, and validation. The result is an intuitive metaphor modeled around the idea of a constructor.

    There is also a set of helpful forms controls that cover a reasonable range of data-types. These make its easy to create forms without much hassle. Forms are also extensible, allowing full customization of the data they bind to and how each fields is visualized.

    Customizable Text Fields

    TextFields that use the NativeTextFieldBehavior can now be customized using the new NativeTextFieldBehaviorModifier. This allows custom background and foregrounds for TextFields, which allows a wide range of styling when combined with the ability to hide borders and change background/foreground color.

    APIs

    • View now exposes mirrored property that indicates whether it has been horizontally flipped due to contentDirection
    • New limitsChanged property to ProgressIndicator
    • New list-based API for constrain layout that removes the need to …
    • New math APIs for AffineTransform and underlying matrix types
    • SimpleMutableListModel's constructor is no longer protected
    • Moved Extractor to top-level util and deprecated local versions
    • Extended toString ItemVisualizer so it takes a custom string mapper
    • Encoder expanded to use Result based mappers
    • Replaced NativeHyperLinkBehaviorBuilder with NativeHyperLinkStyler and made it available via Themes library so it can be used on all platforms

    Fixes | Improvements

    General

    • ListLayout so it no longer sets ideal/minSize of container when widthSource is Parent
    • List/TreeRow, which wrap the item visualizers for each view are no longer focusable
    • Improvements to BasicCheckBoxBehavior and BasicRadioBehavior
    • ThemePicker no longer directly focusable since the spinner within it is
    • ListLayout now uses the container's width instead of idealSize.width when widthSource is set to Parent
    • Inspector.contains(Point) now delegates to behavior when present
    • BasicButtonBehavior and CheckRadioButtonBehavior now respond to mirrored state
    • Removed focusability for sub views generated by behaviors
    • Improved Path.toString
    • Improved default focus traversal
    • Fixed bug in ConfinedRangeModel that allowed range to exceed limits
    • Fixed bug where View not properly handled when moved from parent to Display
    • Fixed ambiguity with calls to Display.plusAssign(Container) and Container.plusAssign(Container)
    • Fixed bug where BasicDropdownBehavior could assign a negative height to an item
    • Fixed issue where Dropdown list not sized correctly after bounds change
    • Fixed bug where listItemVisualizer was not being used
    • Fixed visual bug in browser rendering
    • Fixed rendering for List with null items
    • Fixed focus traversal bug
    • Fixed ScrollPanel layout bug when content changes
    • Fixed empty List bug

    Browser

    • Suppress pointer for canvas rendered elements
    • Fixed rendering issue with importing data into Canvas
    • Fixed Display cleanup on shutdown
    • Fixed clicking bug in native behaviors, and AccessibilityManagerImpl

    Desktop

    • Improved focus traversal
    • Improved drag-drop support
    • Fixed text decoration bug

    Dependencies

    • Kotlin -> 1.5.31
    • Removed old repositories
    Source code(tar.gz)
    Source code(zip)
  • v0.6.1(Oct 6, 2021)

    Features

    New Dropdown Control

    New Dropdown and MutableDropdown controls to represent a list of options within a hidden popup. BasicTheme also provides Behaviors for both.

    APIs

    Fixes | Improvements

    General

    • BasicTheme now provides disabled color mapper for Labels
    • StarRater now retains value proportional to max when max changes
    • Fixed Click even bug
    • Fixed controls with asymmetry between addedToDisplay and removedFromDisplay
    • Fixing issue where View did not notify AccessibilityManager when abandoning a role
    • Fixed focus bug in nested web apps
    • Improved key handling for BasicSpinnerBehavior
    • Fixed behavior delegate so the View's Behavior is updated before afterChange is called
    • Fixed TextField so it properly updates fitText after Behavior is set
    • List now updates when cellAlignment changed

    Desktop

    • Improved drag-drop support
    • Fixed text decoration bug

    Dependencies

    • Kodein -> 7.8.0
    • Skiko -> 0.4.16
    • Jacoco -> 0.8.7
    Source code(tar.gz)
    Source code(zip)
  • v0.6.0(Sep 10, 2021)

    Features

    Desktop Support (Alpha)

    Doodle now supports Desktop and leverages Skia for fast, accurate rendering. This means apps can target desktop via the JVM. Support is still early and not ready for production. There are some missing features--like Accessibility, and others that are partially implemented (i.e. drag-drop). However, overall support is sufficiently complete to begin testing with. So please try this out and report bugs.

    A key goal for Doodle is to provide as much cross-platform code sharing as possible. That's why Web and Desktop share the same rendering model, and therefore widgets. All widgets written in common code can be used on both platforms.

    • Desktop and Web share the same rendering model and widgets
    • Apps written in common code can be fully shared between Web and Desktop

    Kotlin 1.5.0 Support

    Kotlin support has been moved from 1.4.x to 1.5.30.

    APIs

    Fixes | Improvements

    • New helper for turning rects and rounded rects into Paths
    • Fixed Web shadow bug
    • Fixed Web radial gradient rendering
    • Fixed StarRater shadow color

    Dependencies

    • Kotlin -> 1.5.30
    • Coroutines -> 1.5.2
    • Gradle -> 7.1.1
    Source code(tar.gz)
    Source code(zip)
  • v0.5.2(May 14, 2021)

    Fixes | Improvements

    • Fixed minor bug in ImageLoaderImpl related to resuming a coroutine that was already resumed (whenever the same source is loaded multiple times).
    • New helper in BasicTheme for registering BasicCircularSliderBehavior
    • ListRow no longer has a 1px inset at the top if background colors are not specified.
    • RenderManagerImpl now performs View layout if a View's size changes while it is in the middle of a layout
    Source code(tar.gz)
    Source code(zip)
  • v0.5.1(Apr 29, 2021)

    Fixes | Improvements

    • Fixed bug in ImageLoaderImpl that prevented proper handling of invalid image urls
    • Fixed minor bugs in multi-touch handling
    • Fixed bugs in Resizer related to prevented scroll on mobile in some cases

    Dependencies

    • Coroutines -> 1.4.3
    Source code(tar.gz)
    Source code(zip)
  • v0.5.0(Apr 9, 2021)

    Features

    Multi-touch Support

    Doodle now supports multiple pointers (i.e. touches) by default. So there is no additional work needed to enable this in apps.

    • PointerInputManager now tracks a collection of pointers and dispatches events to Views on a per-pointer basis.
    • PointerEvent now has information about the list of pointers (for the View and overall). This lets handlers support multi-touch.
    • Resizer now works better with multi-touch.
    • Added ability to suppress OS handling of PointerEvent. This is like consume, except it does not affect other app listeners.

    Accessibility Support

    This release stabilizes accessibility and brings a lot of new support to items in the Controls library.

    Kotlin IR compiler Support

    Apps can now choose to use either the IR or Legacy artifacts.

    APIs

    • View now has a firstRender event and a rendered property.
    • View now has an opacity property (and change event).
    • Display now exposes childrenChanged event.
    • ListObserver and SetObserver now have to specify their source. This adds more semantics for observers, since they no longer only get the data structure that changed. An observer of Tree selection for example, will now know which Tree fired the change instead of the Tree's internal set.
    • Spinner's itemVisualizer now has access to it.
    • Removed pointerLocation from PointerInputService.

    Fixes | Improvements

    • TableBehavior/TreeTableBehavior no longer uses callbacks to trigger rendering of specific parts of a Table. This approach was invalid since it imposed a 1-1 relationship between the behavior and Table. Instead, there are now APIs on Table that are visible within TableBehavior for doing this.
    • A View's children are now disabled if it is.
    • Button now disarms/resets model when it is disabled.
    • Fixed bug in z-order updating.
    • Fixed minor render bug in BasicSpinnerBehavior buttons.
    • Fixed View's children clip poly bug.
    • Bug fixes for Basic Spinner behaviors.
    • Work-around for iOS keyboard display with text-fields on single click.
    • Moved mockk dependency to jvmTest for now since mockk doesn't have JS (IR) support yet.

    Dependencies

    • Kotlin -> 1.4.31
    • Coroutines -> 1.4.2
    • Kodein -> 7.5.0
    • Mockk -> 1.11.0
    • Log4J -> 1.7.30
    Source code(tar.gz)
    Source code(zip)
  • v0.4.3(Mar 8, 2021)

    Features

    APIs

    • New selection color APIs in TextField
    • BasicSpinnerBehavior's button width can now be set (also available via basicSpinnerBehavior())
    • New AffineTransform extension for computing its angle

    Fixes | Improvements

    • Fixed bug in rendering images with shadows
    • Deprecated ImageFill in favor of ImagePaint
    • Moved BasicSpinnerBehavior into sub-package and aliased/deprecated previous location
    • Deprecated InputVerifier
    • Fixed unit tests
    • Fixed bug in BasicSpinnerBehavior related to cleaning up added children
    Source code(tar.gz)
    Source code(zip)
  • v0.4.2(Feb 16, 2021)

    Features

    val ring   : Path = ring       (center, innerRadius, outerRadius)
    val section: Path = ringSection(center, innerRadius, outerRadius, startAngle, endAngle)
    
    object: ProgressIndicator() {
        init {
            size     = Size(200, 100)
            progress = 0.25
            behavior = PathProgressIndicatorBehavior(
                pathMetrics,          // injected
                path                = path("M10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80")!!,
                foreground          = LinearGradientPaint(Black, Blue, Origin, Point(width, 0.0)),
                foregroundThickness = 5.0,
                background          = Lightgray.paint,
                backgroundThickness = 5.0
            )
        }
    }
    
    canvas.rect(rectangle = bounds.atOrigin.inset(10.0),
                radius = 10.0,
                stroke = Stroke(fill = LinearGradientPaint(Red, Green, Origin, Point(width, height)), thickness = 20.0))
    

    APIs

    • Deprecated CircularProgressBehavior (replaced by BasicCircularProgressBehavior)
    • Stroke dashes changed to DoubleArray
    • PathMetrics can now measure the bounds of a Path (in addition to size)
    • Fill renamed to Paint and old usages deprecated

    Bug Fixes / Improvements

    Docs

    • Added examples of Circular and Path ProgressIndicator behaviors to docs
    Source code(tar.gz)
    Source code(zip)
  • v0.4.1(Feb 6, 2021)

    Features

    • Spinner "constructor" functions now take optional ItemVisualizer
    • New API for scrolling List/Tree to an index/path
    • Native ScrollPanel behavior now supports smooth scrolling
    • Native TextField behavior now allows spell-check to be enabled
    • New FilteredListto support a filtered view of an ObservableList
    • New helper for creating simple text button renderers
    • Added NativeHyperLinkBehaviorBuilder to allow full customization of hyperlinks
    • New helpers for Underline and LineThrough TextDecorations

    APIs

    • Applying explicitApi compiler flag to libs
    • Marking several internal APIs that must be public for sharing with other Doodle libs with new @internal
    • Deprecating NativeTheme mModule vals and adding functions instead
    • ObservableList is now an interface

    Bug Fixes / Improvements

    • Native ScrollPanel behavior now executes scrollTo requests async to address edge case when it is called before size changes have been flushed to the DOM
    • Fixing bug where FocusManager wasn't being enabled when native component selected for the first time in nested app.
    • Fixed issue where shadows might be hidden on Safari
    • Fixed place-holder text positioning on FF
    • Work-around for smooth scrolling bug in FF
    • Fixed issue where text-decoration = "" does not reset in Safari
    • HorizontalFlowLayout now has a VerticalAlignment property
    • Updated list diffing
    Source code(tar.gz)
    Source code(zip)
  • v0.4.0(Jan 18, 2021)

    Updated Kotlin to 1.4.21!

    This release only supports the legacy backend though. Support for the IR backend will come in a future release as dependencies enable it.

    • Update to Measured 0.3.0
    • Update to Mockk 1.10.4
    • Updated to Dokka 0.10.1

    API Changes

    Improved Font support with the ability to load fonts from file urls.

    • Replaced FontDetector with FontLoader. This new interface includes the ability to load a Font via a source like a url.
    • Updated Font definition to better reflect how font-style works
    • Added basic support for oblique font styles

    Added new delegates to View to improve handling of properties that should re-render or notify of a style change.

    class MyView: View {
        var name by renderProperty("") // will cause the View to rerender when it changes
        var age  by styleProperty (56) // will cause trigger styleChanged event when changed
    }
    
    • Reduced Positionable API so that View now implements it
    • New itemsChanged event for DynamicList and DynamicTree
    • Added clicked event to PointerListener
    • Renamed events in KeyListener
    • Pointer, PointerMotion, and Key listener DSLs for more compact consumption of a single event type
    view.poinerChanged += pressed  { event -> }
    view.keyChanged    += released { event -> }
    view.pointerMotionChanged += dragged { event -> }
    
    • DSLs to simplify usage of ListEditor, Layouts that only position, and CommonButtonBehavior that just render
    • TextField now supports font/color for placeholder
    • Added itemsChanged event to DynamicList and DynamicTree
    • Added support for text decorations to StyledText
    • Reduced visibility of constructors for InputEvent and PointerEvent

    Other Improvements

    • Improved child -> row handling in List and Tree
    • MutableList now cancels existing editing when startEditing is called
    • TextMetricsImpl now uses LRUs instead of simple maps to track sizes
    • BasicTab now consumes mouse presses
    • TableHeaderCell now consumes mouse presses
    • Centralized internal ID generation used to manage DOM elements
    • Minor tweak to cursor handling to avoid setting default on root when Display has no cursor
    • Improved native HyperLink styling
    • Simplified unit tests to avoid compiler bug
    Source code(tar.gz)
    Source code(zip)
  • v0.3.4(Jan 4, 2021)

    • App JS now works when in header as well as body
    • Improved GridPanel API with remove/clear and more dynamic item spacing
    • Added simple drag-recognizer DSL
    • Reduced use of data classes
    • Fixed pointer location bug in nested apps
    Source code(tar.gz)
    Source code(zip)
  • v0.3.3(Dec 16, 2020)

    • Label are easier to use since they now rely on their Behavior (instead of a TextMetrics) to determine their text size.
    • Fixed issue where doc images were loading with zero size due to Firefox not liking svg images without explicit width/height: https://bugzilla.mozilla.org/show_bug.cgi?id=1162418
    • Fixed animation bug in BasicTabbedPanelBehavior
    • Scrolling is now disabled if mouse-down is consumed. If so, it is then re-enabled on mouse-up. This makes it easier for things like buttons and sliders to work on touch devices.
    • Now simulating pointer exit on touch up
    Source code(tar.gz)
    Source code(zip)
  • v0.3.2(Nov 15, 2020)

    • Adding link and site preview images to docs page
    • Improving docs to clarify module needed to enable drag-drop
    • README link to tutorials
    • Button no longer waits to be displayed before responding to its model
    • Improving ButtonGroup constructor so it can also take a list of buttons
    Source code(tar.gz)
    Source code(zip)
  • v0.3.1(Nov 8, 2020)

  • v0.3.0(Nov 5, 2020)

    • Files now supported in drag-drop
    • New elliptical clip path support in Canvas
    canvas.clip(ellipse) {
        // render into clipped region
    }
    
    • Scheduler is now shutdown when app is
    • New API in Behavior to control content direction mirroring
    • New behavior delegate to centralize common operations
    • Initial work on DSL: new view {} and container {} functions
    val panel = view {
        render = {
            rect(...)
            text(...)
        }
    }
    
    val container = container {
        this += view {}
    }
    
    • Improved ergonomics for adding/removing items to Display and Container
    • Changed Container from interface to concrete View, which replaces Box
    Source code(tar.gz)
    Source code(zip)
  • v0.2.0(Oct 15, 2020)

    • Initial support for content direction, which allows Views to handle right-to-left locales
      • ContentDirection can now be specified at the View or Display level
      • Views will inherit the ContentDirection from their parent, or the Display, if theirs is not set
      • Views (and the Display) also support automatically mirroring themselves when mirrorWhenRightLeft is set to true (the default)
    • Apps now lose/recover focus whenever focus leaves/re-enters their host element
    • New childrenClipPoly property allows Views to clip their children
    • New APIs for creating rounded-polygons
    • BasicTheme and NativeTheme now expose a list of Behaviors instead of a module that installs them all
    • Reworked ItemVisualizers so they are easier to use
    • KeyboardFocusManager now only consumes key events when focus is actually handled
    • ScrollPanel's ideal size now tracks its content's
    • ScrollPanel now exposes more control over content width and height
    • Label now keeps its ideal size equal to its text size
    • upgraded to Jacaco 0.8.6
    Source code(tar.gz)
    Source code(zip)
  • v0.1.0(Jun 16, 2020)

A pure Kotlin/Multiplatform implementation of group operations on Curve25519.

curve25519-kotlin A pure Kotlin/Multiplatform implementation of group operations on Curve25519. Gradle Kotlin DSL: dependencies { implementation("

Andrey Pfau 9 Dec 22, 2022
Template (pure) for KMM application with DI support

KMM di template Template (pure) for KMM application with DI support. Uses Multiplatform-DI for Dependency Injection Features Common architecture (VIP)

Anna Zharkova 8 Oct 18, 2022
Cross-platform framework for building truly native mobile apps with Java or Kotlin. Write Once Run Anywhere support for iOS, Android, Desktop & Web.

Codename One - Cross Platform Native Apps with Java or Kotlin Codename One is a mobile first cross platform environment for Java and Kotlin developers

Codename One 1.4k Jan 9, 2023
Netflix inspired OTT Home Screen, Contains implementation in Reactjs, Kotlin React Wrapper, Jetpack Compose Web

Netflix-Clone-React Practising React by building Netflix Clone Requirements TMDB api key : Add TMDB API key to AppApi.kt Learning Resourcce Build Netf

Chetan Gupta 61 Nov 13, 2022
Kotlin Multiplatform Sample - Android, iOS, Web, Desktop

KMP-Sample Kotlin Multiplatform Sample Android iOS Web (Compose for web) Desktop (Compose for desktop) ?? Structure Diagram ?? Build At least android

안홍범 14 Dec 13, 2022
A Kotlin Multiplatform Project using TMDB Api. Currently supports Android,iOS,Desktop and web platforms

A Kotlin Multiplatform Project using TMDB Api(https://www.themoviedb.org/). Currently this project is implemented in following platforms Andr

Jackson E J 11 Nov 10, 2022
A Kotlin Multiplatform and Compose template that allows you to easily set up your project targeting: Android, Desktop, and Web

A Kotlin Multiplatform and Compose template that allows you to easily set up your project targeting: Android, Desktop, and Web

Carlos Mota 3 Oct 27, 2021
Unsplash application for Android, Desktop and Web. Built using Kotlin Multiplatform and Compose

Unsplash Unsplash application for Android, Desktop and Web. Built using Kotlin Multiplatform and Compose with ❤️ ?? Presentation Set up the environmen

Carlos Mota 15 Nov 11, 2022
WordMasterKMP - WIP Kotlin Multiplatform sample inspired by Wordle and also Word Master web sample

WordMasterKMP WIP Kotlin Multiplatform sample inspired by Wordle and also Word M

John O'Reilly 56 Oct 4, 2022
Lambda-snake.kt - Snake Game Implementation for Web using Kotlin programming language compiled for Javascript

Projeto da disciplina de Linguagem de Programação Funcional 2021.1 (jan/2022) ??

Alex Candido 3 Jan 10, 2022
Spring boot web + Kotlin template project

kotpringboot-multimodule-template Table of Contents Overview How to run Overview How to run Run with profiles: Note that default spring profile would

Hwan Jo 7 Oct 30, 2022
NewsAppKt is an Android app designed for searching news using TheGuardianOpenPlatform public web service.

NewsAppKt is an updated version of NewsApp. It is written entirely in Kotlin and uses MVVM with Clean Architecture practices. The UI implementation uses Jetpack Compose.

Daniel Bedoya 2 Sep 22, 2022
Runtime Mobile Security (RMS) 📱🔥 - is a powerful web interface that helps you to manipulate Android and iOS Apps at Runtime

Runtime Mobile Security (RMS) ?? ?? by @mobilesecurity_ Runtime Mobile Security (RMS), powered by FRIDA, is a powerful web interface that helps you to

Mobile Security 2k Dec 20, 2022
Android MVVM framework write in kotlin, develop Android has never been so fun.

KBinding 中文版 Android MVVM framework write in kotlin, base on anko, simple but powerful. It depends on my another project AutoAdapter(A library for sim

Benny 413 Dec 5, 2022
A framework for writing composable parsers based on Kotlin Coroutines.

Parsus A framework for writing composable parsers based on Kotlin Coroutines. val booleanGrammar = object : Grammar<BooleanExpression>() { val ws

Aleksei Semin 28 Nov 1, 2022
Kotlin Multiplatform lifecycle-aware business logic components (aka BLoCs) with routing functionality and pluggable UI (Jetpack Compose, SwiftUI, JS React, etc.), inspired by Badoos RIBs fork of the Uber RIBs framework

Decompose Please see the project website for documentation and APIs. Decompose is a Kotlin Multiplatform library for breaking down your code into life

Arkadii Ivanov 819 Dec 29, 2022
Kotlin Multiplatform Mobile + Mobile Declarative UI Framework (Jetpack Compose and SwiftUI)

Kotlin Multiplatform Mobile + Mobile Declarative UI Framework (Jetpack Compose and SwiftUI)

Kotchaphan Muangsan 3 Nov 15, 2022
Framework for quickly creating connected applications in Kotlin with minimal effort

Ktor is an asynchronous framework for creating microservices, web applications and more. Written in Kotlin from the ground up. import io.ktor.server.n

ktor.io 10.7k Jan 9, 2023
A modular object storage framework for Kotlin multiplatform projects.

ObjectStore A modular object storage framework for Kotlin multiplatform projects. Usage ObjectStore provides a simple key/value storage interface whic

Drew Carlson 4 Nov 10, 2022