App building blocks
View
s are the building blocks of Doodle apps. They encapsulate state, display content on the screen and respond to user input. Apps typically contain many View
instances at runtime. And they often use a wide range of View
types that provide specialized rendering and user interactions.
You create a new View
by instantiating a custom type that extends the View
base class, instantiating an inline object directly or through the view
DSL.
package viewcreation
import io.nacular.doodle.core.View
import io.nacular.doodle.core.view
//sampleStart
class MyView: View() {
// ...
}
val view1 = MyView()
val view2 = object: View() {
// ...
}
val view3 = view {
// ...
}
//sampleEnd
You will see how to get your View
s onto the screen when we discuss the Display.
State and rendering
Below is an example of a View
that holds some user data (name and age). It tracks this state and repaints itself whenever any of these properties is changed. This is done using renderProperty
, which automatically triggers render
on any value change.
Notice the render
method in this class. It is responsible for all paint operations for a View, and it gets called whenever a View should be repainted. There are a number of reasons why render
can be called, including: the first time a View
is displayed, any time its size
changes, or if there is an explicit call to rerender
.
import io.nacular.doodle.core.View
import io.nacular.doodle.core.renderProperty
import io.nacular.doodle.drawing.Canvas
import io.nacular.doodle.drawing.Color.Companion.Black
import io.nacular.doodle.drawing.TextMetrics
import io.nacular.doodle.drawing.text
import io.nacular.doodle.geometry.Point
//sampleStart
class UserInfo(textMetrics: TextMetrics, name: String, age: Int): View() {
var name by renderProperty(name) // causes repaint whenever changed
var age by renderProperty(age )
private val nameHeight = textMetrics.height(name)
override fun render(canvas: Canvas) {
canvas.text("name: $name", color = Black)
canvas.text("age : $age", at = Point(y = nameHeight), color = Black)
}
}
//sampleEnd
Learn more about rendering.
View hierarchies
Apps generally have one or more top-level View with other Views nested in them. Doodle allow you to do the same. You specify which Views will represent your top-level items and add them to the Display. These Views in turn will typically have nested Views that are either dynamically added, or part of the internal representation of their parent.
package applications
import io.nacular.doodle.application.Application
import io.nacular.doodle.core.Display
import io.nacular.doodle.core.view
//sampleStart
class MyApp(display: Display): Application {
init {
display += view {}
}
// ...
override fun shutdown() { /*...*/ }
}
//sampleEnd
All Views support nested children
. However, they protect
that list--and other traits related to being a container--by default, to improve encapsulation and API control.
Consider a split panel. It is reasonable to think about it as having a left and right child (ignoring orientation for now). However, an implementation of this concept might choose to have an additional child to represent the splitter. This choice is an internal detail of the implementation that would be leaked if the children
list were public. Worse, a caller could remove the splitter or add more children than expected and break the behavior.
Doodle helps with these design challenges by letting you selectively expose a View's internals to callers.
import io.nacular.doodle.core.View
import io.nacular.doodle.core.view
//sampleStart
class VSplitPanel: View() {
var left: View? = null
set(new) {
if (new == field) { return }
field?.let { children -= it }
field = new
field?.let { children += it }
// notify of change
}
val right: View? = null
// ...
private val handle = view {} // private View for splitter
init {
children += handle // add handle to children
}
// ...
}
//sampleEnd
This design prevents direct access to the panel's children
, which side-steps many issues. It also presents are more intuitive and reliable API. left
and right
are fairly self-documenting compared to children[n]
and children[m]
. Moreover, the panel is able to encapsulate the fact that it uses additional Views for presentation.