Application Architecture

When building applications using owlkettle, it is good practice to separate the model and view of the application. The aim is to encapsulate and separate the application logic from the graphical user interface. Lets look at a contrived example of using this pattern in a simple counter application.

The model implements the functionality of the application. In this case it stores the current value of the counter and provides functions for incrementing and displaying the current value.

type CounterModel* = ref object
  counter: int

proc increment*(model: CounterModel) =
  model.counter += 1

proc `$`*(model: CounterModel): string =
  result = $model.counter

proc newCounterModel*(): CounterModel =
  result = CounterModel(counter: 1)

The view displays the current state of the model to the user. It is also responsible for forwarding user input to the model.

import owlkettle

const APP_NAME = "Counter"

viewable App:
  model: CounterModel = newCounterModel()

method view(app: AppState): Widget =
  result = gui:
    Window:
      title = APP_NAME
      defaultSize = (200, 100)
      
      Box:
        margin = 12
        spacing = 6
        orient = OrientX
        
        Label:
          text = $app.model
        
        Button {.expand: false.}:
          text = "+"
          style = [ButtonSuggested]
          
          proc clicked() =
            app.model.increment()

when not defined(owlkettleNimiDocs):
  brew(gui(App()))

Using Viewables

As your application grows, you may want to separate the view into multiple components. Let's introduce a CounterView viewable to encapsulate the view logic for displaying the counter.

viewable CounterView:
  model: CounterModel

method view(view: CounterViewState): Widget =
  result = gui:
    Box:
      margin = 12
      spacing = 6
      orient = OrientX
      
      Label:
        text = $view.model
      
      Button {.expand: false.}:
        text = "+"
        style = [ButtonSuggested]
        
        proc clicked() =
          view.model.increment()

We can then use CounterView to display the counter inside the main application. The model needs to be passed to the CounterView.

viewable CounterApp:
  model: CounterModel = newCounterModel()

method view(app: CounterAppState): Widget =
  result = gui:
    Window:
      title = APP_NAME
      defaultSize = (200, 100)
      CounterView:
        model = app.model

when not defined(owlkettleNimiDocs):
  brew(gui(CounterApp()))

Larger Examples

Check out the owlkettle-crud example for a larger example of using the model/view architecture with owlkettle.

Graphing also uses this pattern: The model is represented by the Project type, which contains multiple Graph objects. The Project and its individual Graphs are then passed to viewables such as GraphView for displaying them to the user.