All notable changes to DrawBox are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
The 2.0.x line is the Kotlin Multiplatform rewrite. The 1.x line was an Android-only library and is now in maintenance mode. See Migration: 1.x → 2.x at the bottom of this file.
2.1.0 — 2026-07-14
First stable release of the 2.1 line, promoting the two 2.1.0-alpha
pre-releases plus in-place text editing, configurable selection chrome, and
the web font-differentiation fix. The new 2.1 surfaces (image / text
elements, io.ak1.drawbox.input, pen-pressure sample fields, Kotlin/JS
publishing) are now stable; see the earlier alpha entries for their details
and the migration note for the one Element.Path
change.
- In-place text editing gestures (#83). Double-tapping an
Element.TextinSELECTmode — or tapping an already-selected one again — now opens the host editor via the newEvent.TextEditRequested(id)(backed by the newIntent.RequestTextEditAt). See RFC 0001. - Multi-text style editing (#83.3). The context bar's text chips now apply
to every selected text element and show a dimmed "mixed" state
(
ContextBarState.fontSizeMixed/textAlignmentMixed/fontFamilyMixed) when the selection disagrees on a property. - Configurable selection chrome (
SelectionChromeStyle).DrawBoxtakes a newselectionStyleparameter for tuning the accent-colored selection box:paddinggrows/shrinks the gap between an element and its box, plushandleSize,cornerRadius,strokeWidth, andaccent. The box now draws with rounded corners and hollow rounded-square handles, and the box outline is clipped so it never shows through a handle; resize hit-testing tracks the padded box so grab targets stay aligned.
- Sample: mid-edit styling and editor commit semantics (#83.1, #83.5).
Tapping a style chip while the inline editor is open now restyles the element
instead of committing;
Esccancels without committing and losing focus commits. FontRegistrynow warns once per key on web targets when a built-in key (sans/serif/mono) resolves to a genericFontFamily— the case Skia-WASM cannot differentiate. Warning is a one-shotprintlnguarded byexpect fun isWebTarget(), so non-web builds pay nothing.FontRegistry.register(...)resets the guard for the affected key so a re-registration will re-warn if the host swaps back to a generic family. KDoc onFontRegistryandBuiltinFontFamilyKeysnow documents the Skia-WASM bundled-font behavior. Addresses #89 SDK-side.
2.1.0-alpha02 — 2026-07-07
Second 2.1 pre-release. Two additive seams for hosts that want to layer on
top of DrawBoxController (an intents SharedFlow and an overlay slot on
DrawBox), plus a fix for finger-drawn strokes on WASM/browser and for
translucent pen colours rendering as beaded dots.
No breaking changes on top of 2.1.0-alpha01. New surfaces stay under the
alpha stability bucket described in that release.
DrawBoxController.intents: SharedFlow<Intent>— observable stream of processed intents. Emission happens after the reducer runs andstatehas been updated, so subscribers can readstate.valueat emission time and see the resulting state. Unblocks layered controllers (brush), collaboration transports, and analytics recorders without subclassing or state-diffing. Buffered so slow subscribers don't back-pressure the drawing loop. (#99)overlayslot onDrawBox— newoverlay: @Composable BoxScope.() -> Unitparameter, composed inside the canvasBoxabove strokes and selection chrome. Renders in screen space; hosts anchor to world coordinates viastate.viewport. Default is a no-op, so existing call sites are unchanged. (#99)
- Finger strokes halved on WASM/browser (#98). Pointer pressure is now
trusted only from
StylusandEraserinput types. WASM/browser touch reports the W3C default0.5when there is no pressure sensor, which was silently halving finger strokes. Mouse and capacitive touch now fall back to unit pressure. - Translucent pen strokes beading into dots (#97). The same touch
pressure jitter was pushing uniform strokes into the variable-width
branch, where per-segment round caps stacked alpha and beaded
translucent colours. Also composites the variable-width path through
canvas.saveLayerwhenalpha < 1, so a legitimate pen stroke with a translucent colour renders as a single translucent line instead of a chain of overlapping dots. (#101)
2.1.0-alpha01 — 2026-06-28
First pre-release of the 2.1 line. New element types (image, text), a
fifth published target (Kotlin/JS browser), cross-platform image input
plumbing (OS drag-drop on Desktop; clipboard paste on Desktop, Web,
Android, iOS; iOS PHPicker), pen-pressure sampling on supported
hardware, and independent fill / stroke for shapes.
Alpha because the new surfaces (image / text elements, io.ak1.drawbox.input,
pen-pressure sample fields) may iterate based on integration feedback. The
frozen 2.0 surfaces (Mode, Intent, Event, State, DrawBoxController
core) are extended only — see Changed for the one structural rename
inside Element.Path and the migration note at the
bottom.
- Image element (
Element.Image,Mode.IMAGE). Place, transform, and export bitmaps as first-class canvas elements; raw bytes are the source of truth and decoding is cached per element id. (#76) - Cross-platform image inputs under
io.ak1.drawbox.input:pasteImageFromClipboard(...)— JVM (AWT), Web (Async Clipboard API), Android (ClipboardManager+ContentResolver), iOS (UIPasteboard.general.image→ PNG). (#91, #93)Modifier.imageDragAndDropTarget(...)— full implementation on JVM viaawtTransferable+DataFlavor.javaFileListFlavor; iOS / Web targets are intentional no-ops blocked on Compose Multiplatform 1.11 not exposingDragData/nativeEventon those targets (tracked as residual on #78). (#92)iOS PHPickerwired intoImageSaver.ios.ktfor the native insertion flow on iOS / iPadOS. (#91)
- Text element (
Element.Text,Mode.TEXT) with inline editor,TextAlignmentenum, font-family keys viaBuiltinFontFamilyKeys, per-element wrap width / measured height, and SVG export. (#84) - Pen pressure sampling on PEN and ERASER modes. New
Element.PathSamplecarriesposition,width, and optionaltilt/azimuth(degrees) reported by Apple Pencil / S Pen. Mouse / capacitive touch defaults to unit pressure with zero overhead.Intent.UpdateLatestPathnow takes an optionalpressure: Float = 1f. (#75) - Kotlin/JS browser target. DrawBox now publishes a
js(browser)variant alongside Android / iOS / JVM / WASM. Sample app'swebAppwires the JS distribution end-to-end. (#86) - Independent fill + stroke on
Element.Shape. NewstrokeEnabledfield (defaultstrue);Intent.SetSelectedFillColorandIntent.SetSelectedStrokeEnabled;DrawBoxController.setSelectionFillColor/setSelectionStrokeEnabled. ContextBar gains stroke + fill swatch dropdowns with explicit "none" states. SVG export emitsstroke="none"when disabled. LINE / ARROW remain stroke-only and ignore both flags. (#82) - Connector endpoint side-anchoring. Connector endpoints snap to the midpoint of the facing side of the target element instead of the nearest corner, and respect rotation. (#71)
- Roborazzi snapshot harness covering a complex multi-element scene; SVG export correctness asserted against the rendered baseline. (#88)
Element.Path.points: List<Offset>→Element.Path.samples: List<PathSample>. Pen-pressure work moved per-sample width / tilt / azimuth onto the path itself instead of carrying a parallel widths list. A compatibility extensionElement.Path.positions: List<Offset>is provided (note: new name, not a rename ofpoints). This breaks direct construction and field access onElement.Path— see Migration: 2.0.x → 2.1.x.- Async + downsampled image decode with a mip-level cache. Image
rendering no longer blocks the UI thread; the decoded bitmap is sized
to viewport resolution and cached per
(id, zoom level)so panning a large image stays smooth. Internal; no API change. (#87) - iOS image saving routed through PHPicker / Files for a native feel. (#91)
- SVG export correctness fixes surfaced by the new snapshot harness (stroke caps, fill defaults, rotation transforms). (#88)
- Misc. SVG export edge cases caught by the new complex-scene baseline
(see commit
431d5d2). (#88)
- New surfaces in this release that may iterate before
2.1.0stable:Element.Image,Element.Text, theio.ak1.drawbox.inputpackage, the pen-pressure sample fields, and Kotlin/JS publishing coordinates. Feedback on these surfaces is the point of the alpha — file issues againstakshay2211/DrawBox. - The 2.0 frozen surfaces are not loosened by the alpha label.
2.0.0 — 2026-06-24
First stable release of the Kotlin Multiplatform line. Public API across
DrawBoxController, Mode, Intent, Event, and State is now frozen
under semver; incubating surfaces opt in via explicit annotations.
- Object eraser tool (
Mode.ERASER). Tap to remove the element under the pointer; drag to sweep across multiple elements. Hit detection reusesElement.hitTest, so stroke width is automatically respected. - Lazy history snapshot for the eraser. Taps and drags through empty space consume no undo slot; the snapshot is taken once on the first actual removal of a gesture, so a single undo reverts the whole sweep.
- World-space eraser cursor overlay that scales with zoom, with auto-contrast against light/dark backgrounds.
DrawBoxController.eraseAt(point, radius)andsetEraserSize(size)for programmatic erasing.BeginErase,EraseAt,EndErase,SetEraserSizeintents and theeraserdrawable resource.- LWW timestamps on
Element. Every element now carriescreatedAtandmodifiedAtepoch-ms fields, bumped on every mutation viaElement.touched(). Foundational plumbing for the collab work on the roadmap; consumers can ignore it. - Roborazzi-based snapshot testing harness for canvas rendering.
Mode.PANremoved from the bottom controls bar and replaced with the eraser slot. Pan navigation itself stays — Space-bar (hold for temp-pan), middle-mouse drag, two-finger touch, and the scroll wheel all still pan.- UI/UX restructure of the sample application and control bar.
- Toolbar visual polish and small interaction fixes.
- Internal path-cache / static-layer optimizations to keep the render cache fresh across erase ticks without thrash.
- Misc. bug fixes around element serialization and Android image saving rolled in from the pre-stable cleanup.
- Connectors between elements.
- Infinite canvas with zoom and pan.
- Shape selection with drag and scale.
- Stroke style and corner-radius options.
- Tileable SVG background pattern with optional tint.
- JSON import/export in the sample application.
- SVG export crash on
wasmJstarget. - Image storing on Android and iOS.
- iOS sample UI rendering issues.
- Release script issues for Maven publishing.
- Dokka v2 HTML documentation generation.
- Live WASM sample published under
/sample/and linked from the README. - Contributor Covenant text updated from 2.0 to 3.0.
2.0.0-alpha01 — 2026-05-22
- Kotlin Multiplatform rewrite. Targets Android, iOS, Desktop/JVM, and Web (WASM) from one shared Kotlin codebase.
- MVI architecture. Reducer-based state management with sealed
Intent,State, andEventtypes. - Multiple drawing modes: pen, rectangle, circle, triangle, arrow, line.
- SVG export of vector drawings.
- JSON export/import of drawing state.
- Bitmap export for raster output.
- Dokka and Spotless integration.
- mkdocs documentation site with auto-deploy.
- Maven publishing via
vanniktech-maven-publish. - Release script for tagged Maven Central publishing.
- Package layout reorganised under
io.ak1.drawboxwithdomain,presentation, anddataseparation. - Public API surfaces a
DrawBoxControllerand aDrawBoxcomposable; the oldDrawControllerfrom1.xis replaced — see migration notes.
- Android-only
View/Canvasinternals from1.x. The new core uses Compose drawing primitives shared across platforms.
1.0.2 — 2022-01-24
- Removed stray log statements.
- Reverted
refreshStatebehaviour to match documented contract.
- Launcher icons in the sample app.
1.0.1 — 2022-01-24
- Initial public release on Maven Central as
io.ak1:drawbox(Android-only). DrawControllerfor programmatic control of undo/redo, color, stroke, and bitmap capture.- Sample application demonstrating the controller API.
2.1.0-alpha01 is additive across the frozen 2.0 surfaces with one
exception inside Element.Path to make room for pen-pressure data.
Pen-pressure work moved per-sample width / tilt / azimuth onto the path
itself. The points: List<Offset> field is gone; samples: List<PathSample>
takes its place. Each sample carries its own width, so uniform strokes
simply set every sample's width to the active strokeWidth.
Before (2.0.x):
val path = Element.Path(
points = listOf(Offset(10f, 10f), Offset(20f, 20f), Offset(30f, 25f)),
strokeColor = Color.Black,
strokeWidth = 4f,
alpha = 1f,
)
path.points.forEach { /* ... */ }After (2.1.x):
val path = Element.Path(
samples = listOf(
Element.PathSample(position = Offset(10f, 10f), width = 4f),
Element.PathSample(position = Offset(20f, 20f), width = 4f),
Element.PathSample(position = Offset(30f, 25f), width = 4f),
),
strokeColor = Color.Black,
strokeWidth = 4f,
alpha = 1f,
)
// Read-only positions accessor for code that just needs the geometry:
path.positions.forEach { /* ... */ } // List<Offset>
// Full sample stream for pressure-aware consumers:
path.samples.forEach { /* sample.position, sample.width, sample.tilt, sample.azimuth */ }Notes:
positionsis an extension, not a constructor field — it's read-only. Mutating the path's geometry means producing a newsampleslist.tilt/azimuthare nullable;nullmeans "no signal" (mouse / capacitive touch). Renderers that don't care can ignore them.- The active stroke's
strokeWidthonElement.Pathis still meaningful: it's the default applied to newly-appended samples while drawing. Per-sample widths override it during the stroke; setters that resize the whole stroke (SetSelectedStrokeWidth) rewrite every sample's width.
UpdateLatestPath now takes an optional pressure: Float = 1f.
Existing dispatch sites that pass only newPoint continue to compile
and behave identically (unit pressure = uniform stroke). Hosts that
want to wire pressure through pass the per-sample value.
These are pure additions; ignore them unless you want the feature:
Mode.IMAGE/Element.Image— image insertion + transform.Mode.TEXT/Element.Text— text element with inline editor.Element.Shape.strokeEnabled: Boolean = true— independent fill / stroke. Existing serialized shapes default totrue(matches the pre-2.1 implicit "always stroked" behaviour).io.ak1.drawbox.input.pasteImageFromClipboard(...)— opt-in clipboard paste entry point. Wire it from your host's key handler / toolbar.io.ak1.drawbox.input.Modifier.imageDragAndDropTarget(...)— opt-in drag-drop modifier. Attach to the canvas composable.
If you weren't already targeting Kotlin/JS browser, nothing changes.
If you want to consume the new JS variant, add the standard
js(browser) target in your kotlin {} block — DrawBox publishes a
matching klib alongside the existing Android / iOS / JVM / WASM ones.
Unchanged from 2.0.x: JDK 21 to build, Kotlin 2.4.x, Compose
Multiplatform 1.11.x. Consuming Android apps still need
compileSdk 36+ and minSdk 24+.
DrawBox 2.0.x is a Kotlin Multiplatform rewrite. The public API has changed; the artifact coordinates (io.ak1:drawbox) are unchanged.
// 1.x (Android only)
implementation("io.ak1:drawbox:1.0.2")
// 2.x (Android + iOS + JVM + WASM)
implementation("io.ak1:drawbox:2.0.0")1.x:
val controller = rememberDrawController()
DrawBox(
drawController = controller,
backgroundColor = Color.White,
bitmapCallback = { bitmap, error -> /* ... */ }
)2.x:
val controller = rememberDrawBoxController()
val state by controller.state.collectAsState()
DrawBox(
state = state,
onIntent = controller::onIntent,
modifier = Modifier.fillMaxSize()
)1.x returned bitmaps via callback. 2.x exposes a typed Flow<Event>:
controller.events.collect { event ->
when (event) {
is Event.SvgExported -> saveFile(event.svg)
is Event.PngSaved -> processImage(event.bitmap)
is Event.Error -> showError(event.message)
else -> Unit
}
}1.x |
2.x |
|---|---|
rememberDrawController() |
rememberDrawBoxController() |
controller.changeColor(c) |
controller.setColor(c) |
controller.changeStrokeWidth(w) |
controller.setStrokeWidth(w) |
controller.changeOpacity(a) |
controller.setOpacity(a) |
controller.changeBgColor(c) |
controller.setBgColor(c) |
controller.unDo() / reDo() |
controller.undo() / redo() |
controller.reset() |
controller.reset() |
controller.saveBitmap() |
controller.saveBitmap() (now emits Event.PngSaved) |
| not available | controller.setMode(Mode.RECTANGLE) etc. |
| not available | controller.exportSvg() |
| not available | controller.exportPath() / importPath(json) |
| Platform | 1.x |
2.x |
|---|---|---|
| Android | ✅ | ✅ |
| iOS | ❌ | ✅ |
| Desktop/JVM | ❌ | ✅ |
| Web (WASM) | ❌ | ✅ |
2.xrequires JDK 21 to build, Kotlin2.4.x, and Compose Multiplatform1.11.x.- Consuming apps on Android need
compileSdk 36+andminSdk 24+.
2.0.0 is the stable Kotlin Multiplatform release. The public surfaces
DrawBoxController, Mode, Intent, Event, and State are frozen
under semver. Incubating surfaces (collab plumbing, advanced eraser
modes, etc.) are gated behind opt-in annotations and may evolve in
minor versions.