Displays
Back on the Applications page, HelloApp took a Display as a constructor parameter and put it straight to work showing some text:
package moreusefulapp
import io.nacular.doodle.application.Application
import io.nacular.doodle.core.Display
import io.nacular.doodle.core.view
import io.nacular.doodle.drawing.Color.Companion.Black
import io.nacular.doodle.drawing.text
//sampleStart
class HelloApp(display: Display): Application {
init {
display += view {
render = {
text("Hello, world!", color = Black)
}
}
}
override fun shutdown() {}
}
//sampleEnd
Let's find out what that display object actually is, and what it's capable of.
What is a Display?
The Display is your app's root container — it represents the window (Desktop) or page (Web) your app is drawing into, and it holds every View your app shows, directly or indirectly.
Here's the thing that trips almost everyone up the first time: the Display is not itself a View. It plays the part convincingly — it holds children, and you can apply a Layout to it — but it's missing a few things a real View has. In practice, that means:
- The
Displayhas noparent. It's the top of the hierarchy — there's nothing above it. - Views added directly to the
Displayare called top-level Views, and they have noparenteither (more on this below).
Getting a Display
Good news: you've already done this. Back on Applications, we added Display as a constructor parameter and supplied it with instance() at launch time.
package moreusefulapp
import io.nacular.doodle.application.application
import org.kodein.di.instance
//sampleStart
fun main() {
application {
HelloApp(display = instance())
}
}
//sampleEnd
A Display is available for injection by default — you don't need any extra modules to get one.
Adding and removing top-level Views
Your app isn't limited to one top-level View — add and remove as many as you like with += and -= on the Display:
package display
import io.nacular.doodle.application.Application
import io.nacular.doodle.core.Display
import io.nacular.doodle.core.view
//sampleStart
class SampleApp(display: Display): Application {
init {
val header = view {} // these are empty for now
val sidebar = view {} // see Views and Rendering for how to draw into them
// add top-level views
display += header
display += sidebar
// remove a top-level view later, e.g. in response to something the user does
display -= sidebar
}
override fun shutdown() { /*...*/ }
}
//sampleEnd
"Top-level" is just a name for "added straight to the Display, rather than tucked inside another View." That's also why top-level Views have no parent — the Display sits above them, sure, but the Display isn't a View, so there's nothing to point to.
Where does the Display actually live?
This part depends on which platform you're aiming at, and you don't need it to get moving — feel free to skip ahead to Views and circle back later.
On the Web: the whole page, or an element
By default, a top-level Web app's Display claims the entire page — it's tied to the page's <body>. Launch your app inside a specific HTML element however (see hosting in HTML elements), and the Display ties itself to that element instead.
package display
import io.nacular.doodle.application.application
import org.kodein.di.DI
import org.kodein.di.instance
import org.w3c.dom.HTMLElement
fun topLevelLaunch(element: HTMLElement, moduleA: DI.Module, moduleB: DI.Module) {
//sampleStart
// Launch top-level app (Display fills the page body)
application(modules = listOf(moduleA, moduleB)) {
MyApp(display = instance())
}
// Launch app within a specific element (Display fills that element)
application(element, modules = listOf(moduleA, moduleB)) {
MyApp(display = instance())
}
//sampleEnd
}
Your app genuinely can't tell the difference from the inside — either way, it just has a Display to draw into. The same holds for nested apps: their Display fills whatever View is hosting them, and resizes right along with it.
On Desktop: one Display per window
Single-window Desktop apps are no different from Web apps — inject Display, and you're off. But hand your app multiple windows to manage, and each one gets its own Display:
package display
import io.nacular.doodle.application.Application
import io.nacular.doodle.application.application
import io.nacular.doodle.core.WindowGroup
import io.nacular.doodle.core.view
import org.kodein.di.instance
//sampleStart
class MultiWindowApp(windows: WindowGroup): Application {
init {
// the main window's Display
windows.main.display += view {}
// a new window, with its own separate Display
windows {
title = "New Window"
display += view {}
}
}
override fun shutdown() {}
}
fun main() {
application {
MultiWindowApp(windows = instance())
}
}
//sampleEnd
Each Display still plays by all the same rules laid out above — it's just scoped to the one window it's attached to. Views added to a window's Display show up only in that window.
Recap
- The
Displayis the root of your app'sViewhierarchy — but it isn't aViewitself. - Add or remove top-level Views with
display += viewanddisplay -= view. - Top-level Views have no
parent, since theDisplayisn't aView. - A
Displayalways fills whatever it's attached to — the page body, a hosting element, or a window — and your app doesn't need to know which. - Multi-window Desktop apps get one
Displayper window.
Next: you can add empty Views to the Display now — so let's find out what a View actually is, and how the whole hierarchy fits together, on Views.