Johannes Wirde
04/24/2024, 8:02 AMAlessandro Marcolini
04/27/2024, 9:38 PMTextField
in a modal, but it only works on web. On desktop it renders fine, but I'm unable to interact with it. I've made sure it's enabled.Alessandro Marcolini
04/27/2024, 9:40 PMAlessandro Marcolini
04/28/2024, 8:09 PMpendingBoundsChange
list, but they have been rendered and their size isn't empty, causing an infinite loop.Nick
05/03/2024, 3:50 PMNikky
06/11/2024, 1:43 PMNick
06/21/2024, 3:04 AM0.10.2-SNAPSHOT
. Please try it out (and provide feedback) by pointing to maven { url = uri("<https://oss.sonatype.org/content/repositories/snapshots>") }
.
Here's what's included so far...
Animation Chaining
Animations can now be chained within an animation block using the new then
method. This makes it easier to have sequential animations and avoids the need to explicitly track secondary animations for cancellation, since these are tied to their "parent" animation.
// val animate: Animator
val animation = animate {
0f to 1f using (tweenFloat(easing, duration)) { // (1)
// ...
} then {
0f to 1f using (after(delay, tweenFloat(easing, duration))) { // (2)
} then { // (3)
// ...
}
} then { // (4)
// ...
}
}
animation.completed += { /* ... */ } // applies to entire chain
animation.pause () // applies to entire chain
animation.cancel() // applies to entire chain
Desktop Accessibility Support
Doodle's web apps have had accessibility support for some time. Now those capabilities are available for desktop apps as well. You simply include the AccessibilityModule
in your app and follow the guidelines of how to add roles, labels, etc. to your Views.
SpinButton Accessibility
`SpinButton`s now use the new SpinButtonRole
that allows assistive tools to better read them. This role exposes the currently selected value based on a new valueAccessibilityLabeler
function that converts the value to a String
.
Improved Sliders
Sliders can now represent values of any Comparable
type T
between two start
and end
values. This is possible for `T`s that have some interpolation between a start
and end
based on some value between 0
and 1
. This is done via a new TypeConverter<T>
that defines the interpolation (and its inverse).
This means you can now create sliders for numeric types like Measure<T>
directly and their value
will by of the right type.
val charSlider = Slider('A' .. 'Z')
val velocitySlider = Slider(10 * meters / seconds .. 100 * miles / hours)
You can also create Sliders for any type T
, as long as it is Comparable
and you can create an Interpolator
for it.
fun <T: Comparable<T>> customSlider(model: ConfinedValueModel<T>, interpolator: Interpolator<T>) {
val slider: Slider<T> = Slider(model, interpolator = interpolator)
}
These, more flexible Sliders can also be used in forms as expected.
Form {this(
+ slider('A' .. 'Z'),
+ slider(10 * meters/seconds .. 10 * miles/hours),
+ slider(model, interpolator = interpolator),
onInvalid = {}
) { _: Char, _: Measure<Velocity>, _: T ->
}}
Non-linear Sliders
Sliders are linear by default, which means a change in their position translates to a linear change in their value. There are cases however, when it makes sense to have a slider's value change in a non-linear way. You can do this by providing a function that maps values between the slider's input and output spaces. These values are all within the [0-1] domain, and work very similarly to easing functions used for animations. The big difference is they have two forms: f(x) and f^-1(x).
import io.nacular.doodle.controls.range.InvertibleFunction
import io.nacular.doodle.controls.range.Slider
import kotlin.math.log
import kotlin.math.pow
/**
* Logarithmic function and inverse <https://www.desmos.com/calculator/qq59ey0bub>
*/
private object LogFunction: InvertibleFunction {
override fun invoke (value: Float) = log((10f - 1) * value + 1, 10f)
override fun inverse(value: Float) = (10f.pow(value) - 1)/(10 - 1)
}
val logarithmicSlider = Slider(0.0 .. 1.0, function = LogFunction)
APIs
• General
◦ New after
animation function that allows a delay before executing an AnimationPlan
.
◦ Made Scene
a public type since it is part of the public API for Theme
◦ New builders for creating Sliders for Char
and Measure<T>
◦ New increment
and decrement
methods for sliders
◦ New methods for incrementing/decrementing start/end for RangeValueSlider
◦ Renamed Spinner
to SpinButton
(and related classes) and deprecated all old uses.
◦ New SpinButtonRole
for accessibility.
◦ ThemePicker
now allows customization of accessible value labels via new valueAccessibilityLabeler
property.
◦ ListItemRole
now has a selected
state. ListItem
(BasicTheme) now keeps this value up-to-date.
◦ New circumference
extension for Circle
◦ New helper for calculating the interior angle between two Vector3D
instances.
◦ New form methods for creating SpinButton
form controls from a List
of values or IntProgression
.
◦ New functions for creating ease[In/Out]Bounce EasingFunctions with an `initialBounceFraction`: easeIn(0.15f)
.
◦ New convenience methods for working with PathBuilders using x,y instead of Point.
• Deprecations
◦ All animation functions that take a delay, since there is a new after
function.
◦ Types and functions related to Spinner
, which was renamed to SpinButton
.
◦ Types and functions related to Dropdown
, which was renamed to SelectBox
.
Fixes | Improvements
• General
◦ Issue where item could be stuck in render loop if it requires a layout, but cannot render b/c it is not recursively visible (it and all ancestors visible).
• Browser
◦ Fixed issue with Display pointer exit not being properly handled.
◦ Work-around for Safari giving incorrect clientX/Y values when the browser window is zoomed, which broke pointer location.
◦ Fixed edge case where old rendered vectors aren't cleaned up if a sub-frame happens where one wasn't before.
◦ Fixed bug in reading items from DataTransferItemList that broke file drag-drop
Versions
• Kotlin -> 1.9.23
• Kover -> 0.8.1
• Dokka -> 1.9.20Cherrio LLC
06/21/2024, 6:10 PMsize
idealSize
minimumSize
. Like what role do they play and how do they affect each other?Nick
06/28/2024, 2:59 PMComparable
type T
between two start
and end
values.
• Non-linear Sliders
Check out the docs for more details.Cherrio LLC
06/30/2024, 4:58 PMKev Thompson
08/02/2024, 9:52 AMNorris
08/03/2024, 5:37 PMNorris
08/03/2024, 6:05 PMNorris
08/04/2024, 12:39 AMNick
08/04/2024, 12:58 AMNorris
08/04/2024, 2:57 PMval styledLabel = Label(
bold("Lorem Ipsum").." is simply "..Yellow("dummy text", Background)..
" of the printing and typesetting industry. It has been the industry's standard dummy text "..
TextDecoration(setOf(Under), Red, Wavy) ("ever since the 1500s")..
", when an unknown printer took a galley of type and scrambled it to make a type specimen book."
).apply {
width = 250.0
fitText = setOf(Height)
wrapsWords = true
lineSpacing = 1f
textAlignment = Start
letterSpacing = 0.0
}
I think I am running into this issue because I am trying to build objects imperatively not declaratively. Is it possible to do this?Norris
08/04/2024, 2:58 PMval label = Label("Some Text")
label.size = Size(200, 100)
label.behavior = ??
Cherrio LLC
08/14/2024, 11:54 AMCherrio LLC
08/14/2024, 6:23 PMTextFieldBehaviour
using NativeModifier, is there away to add padding to the text? It’s too close to the TextField border.snowe
08/15/2024, 10:20 PMconstrain
function also doesn’t seem to work. I have to set up the layout manually with rectangles.snowe
08/17/2024, 1:09 AMCherrio LLC
08/17/2024, 7:33 PMNick
09/11/2024, 7:04 PMNick
11/12/2024, 5:28 AMNick
11/29/2024, 7:33 AMNick
02/01/2025, 7:21 AMview {
+ Label("Hello") // Label fits its text by default
layout = constrain(children.first(), fill) // :x: infinite loop
}
Doodle 0.11.0
view {
+ Label("Hello") // Label fits its text by default
layout = constrain(children.first(), fill) // :white_check_mark: works as expected
}
Now it just works as expected since View’s cannot override the Layout
they are managed by.
More specifically, a View is given a min
and max
size it can take by its parent’s Layout
. It then picks a size it would like to take in that range and reports back to the Layout
, which uses that information to position/size it and the other Views it manages.
This means you cannot directly change a View’s bounds like before. However, you can still suggest changes that the View may use to determine its final size.
This means code like this no longer works, and cannot be directly converted to the new suggestion system. That’s because bounds suggestions are not guaranteed in the way a normal setter would be.
Doodle 0.10.x
val view1 = view {}.apply { size = Size(10, 50) }
val view2 = view {}.apply { size = view1.size } // view2.size == Size(10, 50)
Doodle 0.11.0
val view1 = view {}.apply { suggestSize(10, 50) }
val view2 = view {}.apply { size = view1.size } // view2.size == Size.Empty since suggestion usually async
Frosted Glass Paint
New Paint
that lets you create glass like material that blurs the underlying content. This, like all paints, can be used to fill any shape, stroke, or text.
rect(
rectangle = bounds.atOrigin.inset(borderThickness / 2),
radius = cardRadius,
fill = FrostedGlassPaint(Color(0x09008bu) opacity 0.2f, blurRadius = 10.0)
)
Text Outlining
You can now outline text using `Stroke`s like other shapes. This includes StyledText
, which now supports strokes for styled segments.
render = {
text(
text = "Hello Doodle!",
at = Origin,
fill = Transparent.paint,
stroke = Stroke()
)
}
val stroke = Stroke()
render = {
text(
text = "Hello " .. stroke { "Doodle!" },
at = Origin,
)
}
Inline Constraints for Forms
Forms have api.Layout/s that you can specify explicitly. But now you can also define constraint-based layouts declaratively when defining a Form.
Form { this (
"Bob" to labeled("Name" ) { textField ( ) },
21 to labeled("Age" ) { spinButton(1..120 ) },
Green to labeled("Color") { colorStrip(Red, Green, Blue, BlueColor, Black) },
layout = { name, age, color ->
name.top eq parent.insets.top
name.left eq parent.insets.left
name.right eq age.left - 12 strength Strong
name.height eq name.idealHeight
age.top eq name.top
age.width eq 80
age.right eq parent.right - parent.insets.right
age.height eq age.idealHeight
color.left eq name.top
color.top eq name.bottom + 12
color.right eq age.right strength Strong
color.height eq color.preferredSize(
min = Empty,
max = Size(parent.width.readOnly, POSITIVE_INFINITY)
).height
parent.bottom eq color.bottom + parent.insets.bottom
},
onInvalid = {}
) { name: String, color: Int, age: Color ->
// ...
} }
APIs
• General
• ScrollPanelVisualizer
now takes a config that lets you modify the resulting panel
• ScrollPanel’s contentWidthConstraints
and contentHeightConstraints
now provide IdealSizedProperty
values, which have an idealValue
field. This lets you constrain the property using its ideal value.
• Layout now has preferredSize method, which returns the preferred size for the set of Views given the current context.
• lerp
for HsvColor
• parent insets
now available within constraint blocks
• underline
and lineThrough
helpers for creating `TextDecoration`s
• New Encoder
utility types for Strings
• New methods for scrolling a View horizontally and vertically
• New infix strength
method for constraint DSL (removed rangeTo
operator option)
• Removed Label.fitText
• `Path`s can now be created by “extending” an existing one. This produces a builder based on the given path
• PathBuilder
has a new method to append a Path
• CarouselItem now has a displayIndex
property which indicates the item’s display order relative to nearestItem
.
• New MonthPanel.showMaxRows
property that controls whether all 6 potential rows are shown for the panel.
• TreeBehavior.RowPositioner
now gets tree bounds info as an input for rowBounds
and contentBounds
• New Theme.selected
and Theme.deselected
events to indicate when a Theme has been selected/deselected.
• MenuBehavior.SubMenuConfig now allows configuration of the menu’s anchor point, insets from the Display’s edges, and horizontal/vertical offsets from the parent menu.
• TileLayout
now takes an Orientation
• New anchor property for SlicerPresenter
that controls which part of the stack moves first.
• ScrollPanelVisualizer
now takes a config that lets you modify the resulting panel
• ScrollPanel’s contentWidthConstraints
and contentHeightConstraints
now provide IdealSizedProperty
values, which have an idealValue
field. This lets you constrain the property using its ideal value.
Fixes | Improvements
• General
• bug in interiorAngle
function for 2 vectors
• spinButton
form field now adopts initial value set for it.
• IntSpinButtonModel
now clamps initial value
• Issue in Resizer
where pointer release was consumed (to avoid issues on touch devices) which caused strange behavior w/ other handlers. Changed to preventOsHandling
instead.
• Bugs in ConstrainedSizePolicy
• Form switch layout
• Label only re-measures text if text is actually changed
• Some areas where wordSpacing and letterSpacing couldn’t be 0
• GridLayout issue due to new layout model
• Issue where ConstraintLayoutImpl would unregister context for Views that were unconstrained even if they were still constrained in other blocks.
• Issue where ScrollPanel
scroll didn’t take scroll bar sizes into account
• Selection bug in Lists, Tables, Tress, TreeTables related to selecting the previous item after a select all
• Bug where MultiSelectionModel
wouldn’t have correct last
when selecting all while last item
• Fixed sizing for many form controls that were incorrect under new layout scheme
• Issue where List
could reset size incorrectly if it’s parent has no layout
• Label
default lineSpacing
• Label
preferredSize now tracks bounds changes when wrapsWords
set to true
• The way offset/size-inset constraints work
• BasicSelectBoxBehavior
rendering
• BasicSpinButtonBehavior
rendering
• Issue where Label.text
wouldn’t update if changing from a StyledText
with same text value
• Edge case where Carousel wouldn’t transition if it only contained 1 item with wrapping
• Menu selection issue caused by bad Kotlin compilation: https://youtrack.jetbrains.com/issue/KT-73130.
• Edge case where items could be selected in a Menu if they were all disabled.
• Issue where Carousel would incorrectly handle manual move cancellation if no movement had occurred
• Bug in they way Views are selected, which caused disabled Views to get pointer events.
• Render issues in CubePresenter
• Issue where Carousel would incorrectly handle manual move cancellation if no movement had occurred
• Issue where Carousel
wouldn’t cancel skips properly and this resulted in janky rendering.
• SlicerPresenter
now caches bounds data to improve frame rate.
• Issue with CubePresenter
positioning of cube cap supplemental view.
• BasicSelectBoxBehavior
popup list size
• Updated Dynamic Behaviors so they work w/ List<BehaviorResolver>
instead of Set to ensure proper ordering
• bug in ProportionalSizePolicy
that led to incorrect column sizing
• Popup ordering in some edge cases
• Stroke rendering with some paints
• But where PatternPaint
transform not updated in some edge cases
• Render issue with some brushes when the content they render is clipped
• Issue in native ScrollPanel behavior that prevented proper install/uninstall cycles
• No longer returning 0 for empty string height
• Issue with paint caching
• Using browser’s default line-height instead of assuming a value of 1.
• Incorrectly treating “simple” brushes as complex in some edge cases, which means they’d render using SVG when they didn’t need to
• Ensure single line text has proper line height set
• Various fixes for foreign object paints
• Issue where some paints could fail to draw both strokes and fill when used at the same time
• Issue where some paints would prevent fill from working when used as a Stroke
• Issue in stroke rendering with SweepGradientPaint
• Incorrect text indent for wrapped styled text in some cases.
• Vertical alignment of SVG text so it matches HTML text.
• SVG text backgrounds
• Bug where touch events resulted in pointer enter being called after exit. This meant Views that were touched would always remain in the “pointer entered” state, which breaks many app interactions.
• Browser
• Text color not defaulting to black in some cases
• Pointer up event consumed even if down event was not sent to the app
• Bug in logic that decides when to use plain css for text
• Bug in text width calculation for StyledText
• Render bug where StyledText background would cover an entire line of text instead of the specific words.
• Work-around for Safari’s CanvasRenderingContext2D not having proper Font support
• Desktop
• Default line height handling
• Default line height handling
• Opacity not used for image and image paints
• Updating where skiko properties are set to ensure they take effect, this includes better integration of windows w/ Mac system appearance.
• Issue with outer shadows not rendering properly
• Issue where text could wrap incorrectly at small widths
• Issue when rendering indent for wrapped, plain text.
Build
• Type-safe project accessors
• Cleanup deprecations in buildSrc
• Remove node version specification
Versions
• Kotlin -> 2.1.10
• Kover -> 0.9.1
• Mockk -> 1.13.13
• Skiko -> 0.8.19Michal Landsman
03/03/2025, 3:28 PMNick
03/03/2025, 8:39 PMNick
03/15/2025, 7:01 PMNick
03/30/2025, 4:29 AM