Skip to main content

Where's my view?

Coming right off Theming, let's look at what happens when that step gets skipped — the single most common "why isn't anything showing up" moment in Doodle, and how to spot it fast the next time it happens.

Here's a baseline that works exactly as you'd expect: a single top-level View that fills the Display and draws a centered circle.

package simplecircle import io.nacular.doodle.application.Application import io.nacular.doodle.application.application import io.nacular.doodle.core.Display import io.nacular.doodle.core.center import io.nacular.doodle.core.height import io.nacular.doodle.core.view import io.nacular.doodle.core.width import io.nacular.doodle.drawing.Color.Companion.Red import io.nacular.doodle.drawing.Color.Companion.White import io.nacular.doodle.drawing.paint import io.nacular.doodle.geometry.Circle import org.kodein.di.instance import kotlin.math.min //sampleStart class SimpleCircle(display: Display): Application { init { display += view { suggestSize(display.size) render = { circle( Circle( center = display.center, radius = min(display.width, display.height) / 2 - 10 ), fill = Red.paint) } } display.fill(White.paint) } override fun shutdown() {} } fun main() { application { SimpleCircle(display = instance()) } } //sampleEnd

Where is my Button?​

Now let's try the same instinct with a PushButton. This feels like it should work exactly the same way — but it doesn't:

package invisiblebutton import io.nacular.doodle.application.Application import io.nacular.doodle.application.application import io.nacular.doodle.controls.buttons.PushButton import io.nacular.doodle.core.Display import io.nacular.doodle.drawing.Color.Companion.White import io.nacular.doodle.drawing.paint import io.nacular.doodle.geometry.Size import org.kodein.di.instance //sampleStart class InvisibleButton(display: Display): Application { init { // NOTE: This does not render because the button has no // Behavior installed display += PushButton().apply { suggestSize(Size(40, 20)) } display.fill(White.paint) } override fun shutdown() {} } fun main() { application { InvisibleButton(display = instance()) } } //sampleEnd
danger

The above examples does not render anything because PushButton delegates all rendering to its behavior, and this button hasn't been given one.

If this looks familiar, it should: it's the exact thing you just read about on Theming — PushButton, like most Views in the Controls library, doesn't draw itself at all. It hands that job off to a Behavior, and with none installed, there's simply nothing telling it how to draw. Two ways to fix that, and they're worth knowing both.

Fix it directly with a Behavior​

The straightforward option: hand the button a Behavior yourself.

package buttonwithbehavior import io.nacular.doodle.application.Application import io.nacular.doodle.application.Modules.Companion.PointerModule import io.nacular.doodle.application.application import io.nacular.doodle.controls.buttons.PushButton import io.nacular.doodle.controls.theme.simpleButtonRenderer import io.nacular.doodle.core.Display import io.nacular.doodle.drawing.Color.Companion.Lightgray import io.nacular.doodle.drawing.Color.Companion.White import io.nacular.doodle.drawing.darker import io.nacular.doodle.drawing.paint import io.nacular.doodle.geometry.Size import io.nacular.doodle.layout.constraints.center import io.nacular.doodle.layout.constraints.constrain import org.kodein.di.instance //sampleStart class ButtonWithBehavior(display: Display): Application { init { display += PushButton("Hi").apply { suggestSize(Size(80, 40)) // Assign a Behavior to the button to handle rendering behavior = simpleButtonRenderer { button, canvas -> var color = Lightgray if (button.model.pressed ) color = color.darker(0.25f) if (button.model.pointerOver) color = color.darker(0.25f) canvas.rect(button.bounds.atOrigin, radius = 10.0, fill = color.paint) } // acceptsThemes = false // prevents any theme from overriding behavior } display.layout = constrain(display.first(), center) display.fill(White.paint) } override fun shutdown() {} } fun main() { // PointerModule required to support mouse/touch application(modules = listOf(PointerModule)) { ButtonWithBehavior(display = instance()) } } //sampleEnd
tip

That commented-out acceptsThemes = false is worth knowing about: set it, and this button keeps the Behavior you assigned here even if a Theme gets installed later and would otherwise try to override it.

Or let a Theme handle it​

The other option, and generally the better one once you have more than a button or two: a Theme assigns Behaviors to your whole app at once, rather than one View at a time. Here, registering the nativeButtonBehavior (Web, Desktop) module makes a Theme available that gives every button in the app a native-looking Behavior by default — injected alongside ThemeManager, which is what actually selects it:

package buttonwiththeme import io.nacular.doodle.application.Application import io.nacular.doodle.application.Modules.Companion.PointerModule import io.nacular.doodle.application.application import io.nacular.doodle.controls.buttons.PushButton import io.nacular.doodle.core.Display import io.nacular.doodle.docs.apps.ButtonWithTheme import io.nacular.doodle.drawing.Color.Companion.White import io.nacular.doodle.drawing.paint import io.nacular.doodle.geometry.Size import io.nacular.doodle.layout.constraints.center import io.nacular.doodle.layout.constraints.constrain import io.nacular.doodle.theme.Theme import io.nacular.doodle.theme.ThemeManager import io.nacular.doodle.theme.native.NativeTheme.Companion.nativeButtonBehavior import org.kodein.di.instance //sampleStart class ButtonWithTheme(display: Display, themeManager: ThemeManager, theme: Theme): Application { init { // install theme that provides a button behavior themeManager.selected = theme display += PushButton("Hi").apply { suggestSize(Size(80, 40)) } display.layout = constrain(display.first(), center) display.fill(White.paint) } override fun shutdown() {} } fun main() { application(modules = listOf(PointerModule, nativeButtonBehavior())) { ButtonWithTheme( display = instance(), themeManager = instance(), theme = instance() ) } } //sampleEnd

Same result as the manual fix — a button that delegates all its rendering to a Behavior — just applied consistently across your whole app instead of one View at a time.

Recap​

  • If a control from the Controls library renders as nothing, check for a missing Behavior first — it's the most common cause by far.
  • Assign one directly (button.behavior = ...) for a quick, local fix, or install a Theme to cover your whole app consistently.
  • acceptsThemes = false locks in a manually-assigned Behavior, so a Theme installed later can't quietly override it.

Next: with that gotcha out of the way, on to the Catalog itself.