The Vedeu DSL

Coupled with the API (for interacting with the running client application), the DSL provides the mechanism to configure aspects of your application whilst using Vedeu.

Interfaces

An Interface is a basic element in the GUI. You can think of it as a container for a portion of the terminal screen. Interfaces can be set to be show or hidden, and combined into groups to form the different 'screens' of your application.

Much of the behavior of an Interface comes from child objects that are defined under the Interface. These objects are described in more detail in their respective sections below.

Here is an example of declarations for an interface block:

interface :main do
  visible true # whether to show the interface
  focus! # focus this interface
  cursor true # Show the cursor when this section is focused
  colour foreground: '#ffffff', # set interface foreground
         background: '#000033'  # and background colors
  group :general # set interface group

  geometry do
    # size and position details
  end
  border do
    # border properties
  end
  keymap do
    # keymap that is in effect when this interface is focused
  end
end

The content for an interface is handled by a 'view'. More details on these can be found here: Views

Declaring interface sub-objects

Every object in the DSL besides interface itself is defined for a particular interface. This can either be declared implicitly by defining the object inside an interface block or explicitly, by passing the interface name as a first argument to the declaration.

That is, these are equivalent ways to declare a Geometry for an existing interface

interface :main do
  geometry do
    # some geometry
  end

  # some other declarations
end

or you can say

interface :main do
  # some other declarations
end

geometry :main do
  # some geometry
end

Borders

Provides a mechanism to help configure borders in Vedeu.

Setting a title for the border

If you have you are showing a top border, you could add a title.

Vedeu.border :border_demo do
  title 'My Cool Title'
  # ... some code
end

produces, depending on other customisations:

+- My Cool Title --------------------------------+

Customising the appearance of the border

Set the character to be used to draw the bottom left corner of the border.

Vedeu.border :border_demo do
  bottom_left '+'
  # ... some code
end
Set the character to be used to draw the bottom right corner of the border.

Vedeu.border :border_demo do
  bottom_right '+'
  # ... some code
end
Set the character to be used to draw a horizontal part of the border.

Vedeu.border :border_demo do
  horizontal '-'
  # ... some code
end
Set the character to be used to draw the top left corner of the border.

Vedeu.border :border_demo do
  top_left '+'
  # ... some code
end
Set the character to be used to draw the top right corner of the border.

Vedeu.border :border_demo do
  top_right '+'
  # ... some code
end
Set the character to be used to draw a vertical part of the border.

Vedeu.border :border_demo do
  vertical '|'
  # ... some code
end

Enabling/disabling the border

Enable the border: (Borders are enabled by default when defined for an interface).

Vedeu.border :border_demo do
  enable!
  # ... some code
end
Disable the border:

Vedeu.border :border_demo do
  disable!
  # ... some other code (will be ignored)
end

Enabling/disabling an aspect of the border

Enable/disable the bottom border.

Vedeu.border :border_demo do
  bottom true  # or...
  bottom false # or...
  hide_bottom! # or...
  show_bottom!
  # ... some code
end

and false. Enable/disable the left border.

Vedeu.border :border_demo do
  left true  # or...
  left false # or...
  hide_left! # or...
  show_left!
  # ... some code
end
Enable/disable the right border.

Vedeu.border :border_demo do
  right true  # or...
  right false # or...
  hide_right! # or...
  show_right!
  # ... some code
end
Enable/disable the top border.

Vedeu.border :border_demo do
  top true  # or...
  top false # or...
  hide_top! # or...
  show_top!
  # ... some code
end

Geometry

Geometry allows the configuration of the position and size of an interface. Within Vedeu, as the same for ANSI terminals, has the origin at top-left, y = 1, x = 1. The ‘y’ coordinate is deliberately first.

The Geometry DSL can be used within the Interface DSL or standalone. Here are example of declarations for a ‘geometry` block:

A standalone geometry definition:

Vedeu.geometry :some_interface do
  height 5 # Sets the height of the view to 5
  width 20 # Sets the width of the view to 20
  x 3      # Start drawing 3 spaces from left
  y 10     # Start drawing 10 spaces from top
  xn 30    # Stop drawing 30 spaces from the left
  yn 20    # Stop drawing 20 spaces from top
end

An interface including a geometry definition:

Vedeu.interface :some_interface do
  geometry do
    height 5
    width 20
    x 3
    y 10
    xn 30
    yn 20
  end
  # ... some code here
end

If a declaration is omitted for ‘height` or `width` the full remaining space available in the terminal will be used. `x` and `y` both default to 1.

You can also make a geometry declaration dependent on another view:

Vedeu.interface :other_panel do
  # ... some code here
end

Vedeu.interface :main do
  geometry do
    height 10
    y { use(:other_panel).south }
  end
  # ... some code here
end

This view will begin just below “other_panel”.

This crude ASCII diagram represents a Geometry within Vedeu, each of the labels is a value you can access or define.

     x    north    xn           # north:  y - 1
   y +--------------+           # top:    y
     |     top      |           # west:   x - 1
     |              |           # left:   x
west | left   right | east      # right:  xn
     |              |           # east:   xn + 1
     |    bottom    |           # bottom: yn
  yn +--------------+           # south:  yn + 1
          south

Setting the interface dimensions

Align the interface/view horizontally or vertically within the terminal. Horizontally align the interface/view to the left.

Vedeu.geometry :some_interface do
  # `width` is a positive integer, e.g. 30
  align_left 30

  # this is the same as:
  # horizontal_alignment(:left, 30)

  # or you can use: (see notes)
  # align(:none, :left, 30, Vedeu.height)

  # ... some code
end
Horizontally align the interface/view centrally.

Vedeu.geometry :some_interface do
  # `width` is a positive integer, e.g. 30
  align_centre 30

  # this is the same as:
  # horizontal_alignment(:centre, 30)

  # or you can use: (see notes)
  # align(:none, :centre, 30, Vedeu.height)

  # ... some code
end

# Also allows `align_center` if preferred.
Align the interface/view to the right.

Vedeu.geometry :some_interface do
  # `width` is a positive integer, e.g. 30
  align_right 30

  # this is the same as:
  # horizontal_alignment(:right, 30)

  # or you can use: (see notes)
  # align(:none, :right, 30, Vedeu.height)

  # ... some code
end
Vertically align the interface/view to the top of the terminal.

Vedeu.geometry :some_interface do
  # `height` is a positive integer, e.g. 30
  align_top 30

  # this is the same as:
  # vertical_alignment(:top, 30)

  # or you can use: (see notes)
  # align(:top, :none, Vedeu.width, 30)

  # ... some code
end
Vertically align the interface/view to the middle of the terminal.

Vedeu.geometry :some_interface do
  # `height` is a positive integer, e.g. 30
  align_middle 30

  # this is the same as:
  # vertical_alignment(:middle, 30)

  # or you can use: (see notes)
  # align(:middle, :none, Vedeu.width, 30)

  # ... some code
end
Vertically align the interface/view to the bottom of the terminal.

Vedeu.geometry :some_interface do
  # `height` is a positive integer, e.g. 30
  align_bottom 30

  # this is the same as:
  # vertical_alignment(:bottom, 30)

  # or you can use: (see notes)
  # align(:bottom, :none, Vedeu.width, 30)

  # ... some code
end
Specify the number of characters/rows/lines tall the interface will be. This value will be ignored when ‘y` and `yn` are set.

Vedeu.geometry :some_interface do
  height 8
  # ... some code
end
Specify the number of characters/columns wide the interface will be. This value will be ignored when ‘x` and `xn` are set.

Vedeu.geometry :some_interface do
  width 25
  # ... some code
end
Returns the width in characters for the number of columns specified.

Vedeu.geometry :main_interface do
  # ... some code
  width columns(9) # Vedeu.width # => 92 (for example)
                   # 92 / 12 = 7
                   # 7 * 9 = 63
                   # Therefore, width is 63 characters.
end
Returns the height in characters for the number of rows specified.

Vedeu.geometry :main_interface do
  # ... some code
  height rows(3)  # Vedeu.height # => 38 (for example)
                  # 38 / 12 = 3
                  # 3 * 3 = 9
                  # Therefore, height is 9 characters.
end
Specify the starting x position (column) of the interface.

Vedeu.geometry :some_interface do
  x 7 # start on column 7.

  # start on column 8, if :other_interface changes position
  # then :some_interface will too.
  x { use(:other_interface).east }
  # ... some code
end
Specify the ending x position (column) of the interface. This value will override ‘width`.

Vedeu.geometry :some_interface do
  xn 37 # end at column 37.

  # when :other_interface changes position,
  # :some_interface will too.
  xn  { use(:other_interface).right }
  # ... some code
end
Specify the starting y position (row/line) of the interface.

Vedeu.geometry :some_interface do
  y  4 # start at row 4

  # start on row/line 3, when :other_interface changes
  # position, :some_interface will too.
  y  { use(:other_interface).north }
  # ... some code
end
Specify the ending y position (row/line) of the interface. This value will override ‘height`.

Vedeu.geometry :some_interface do
  yn 24 # end at row 24.

  # if :other_interface changes position, :some_interface
  # will too.
  yn { use(:other_interface).bottom }
  # ... some code
end

Groups

Interfaces can be configured to be part of a named group. Once an interface is a member of group, the group can be affected by other controls. For example, assuming the client application is a simple Git client, it may have a group called ‘commit’. The ‘commit’ group will contain the interfaces ‘diff’ (to show the changes), ‘staged’ (to show which files are staged) and ‘unstaged’. A refresh of the ‘commit’ group would cause all interfaces belonging to the group to refresh. Similarly, showing or hiding the group would of course, show or hide the interfaces of that group.

Add interfaces to groups

Specify a new group of interfaces with a simple DSL. Creating a group with the same name as an existing group overwrites the existing group.

The example below resembles ‘vim’ (the popular terminal-based text editor):

Vedeu.group :title_screen do
  add :welcome_interface
  # ... some code
end

Vedeu.group :main_screen do
  add :editor_interface
  add :status_interface
  add :command_interface
  # ... some code
end

or more succinctly:

Vedeu.group :main_screen do
  members :editor_interface,
          :status_interface,
          :command_interface
  # ... some code
end

or when defining an interface:

Vedeu.interface :some_interface do
  group :some_group
  # ... some code
end

Keymaps

You can define keymaps by name which matches a defined interface. When that interface is in focus, keys pressed as part of this definition will affect that interface. This allows you to form context driven behaviour for your application. Define actions for keypresses for when specific interfaces are in focus. Unless an interface is specified, the key will be assumed to be global, meaning its action will happen regardless of the interface in focus.

Vedeu.keymap :my_interface do
  key('s')        { Vedeu.trigger(:save) }
  key('h', :left) { Vedeu.trigger(:left) }
  key('j', :down) { Vedeu.trigger(:down) }
  key('p') do
    # ... some code
  end
  # ... some code
end

# or...

Vedeu.keys :my_interface do
  # ... some code
end

# or...

Vedeu.interface :my_interface do
  keymap do
    # ... some code
  end # or...

  keys do
    # ... some code
  end
end
Define the name of the keymap.

To only allow certain keys to work with specific interfaces, use the same name as the interface.

When the name :global is used, all keys in the keymap block will be available to all interfaces. Once a key has been defined in the :global keymap, it cannot be used for a specific interface.

Vedeu.keymap do
  name :some_interface
end

Provides the mechanism to create menus within client applications and use events to drive them. Add an individual item to the menu. Define the items for the menu. Most powerful when used with one of your model classes.

In the :my_playlist example below, your ‘Track` model may return a collection of tracks to populate the menu.

Vedeu.menu :my_menu do
  items [:item_1, :item_2, :item_3]
end

Vedeu.menu :my_playlist do
  items Track.all_my_favourites
end
The name of the menu. Used to reference the menu throughout your application’s execution lifetime.

Vedeu.menu do
  name :my_menu
  # ...
end

Views

There are two ways to construct views with Vedeu. You would like to draw the view to the screen immediately (immediate render) or you want to save a view to be drawn when you trigger a refresh event later (deferred view).

Both of these approaches require that you have defined an interface (or ‘visible area’) first. You can find out how to define an interface with Vedeu below or in Vedeu::Interfaces::DSL. The examples in ‘Immediate Render’ and ‘Deferred View’ use these interface definitions: (Note: should you use these examples, ensure your terminal is at least 70 characters in width and 5 lines in height.)

Vedeu.interface :main do
  geometry do
    height 4
    width  50
  end
end

Vedeu.interface :title do
  geometry do
    height 1
    width  50
    x      use('main').left
    y      use('main').north
  end
end

Both of these approaches use a concept of Buffers in Vedeu. There are three buffers for any defined interface. These are imaginatively called: ‘back’, ‘front’ and ‘previous’.

The ‘back’ buffer is the content for an interface which will be shown next time a refresh event is fired globally or for that interface. So, ‘back’ becomes ‘front’.

The ‘front’ buffer is the content for an interface which is currently showing. When a refresh event is fired, again, globally or for that interface specifically, the content of this ‘front’ buffer is first copied to the ‘previous’ buffer, and then the current ‘back’ buffer overwrites this ‘front’ buffer.

The ‘previous’ buffer contains what was shown on the ‘front’ before the current ‘front’.

You can only write to either the ‘front’ (you want the content to be drawn immediately (immediate render)) or the ‘back’ (you would like the content to be drawn on the next refresh (deferred view)).

The basic view DSL methods look like this:

renders/views
  |- view
      |- lines
      |   |- line
      |       |- streams
      |       |   |- stream
      |       |       |- char
      |       |
      |       |- stream
      |           |- char
      |
      |- line
          |- streams
          |   |- stream
          |       |- char
          |
          |- stream
              |- char

renders/views
  |- view
      |- lines/line
          |- streams/stream
              |- char
Register an interface by name which will display output from an event or a command. This provides the means for you to define your the views of your application without their content.

Vedeu.interface :my_interface do
  # ... some code
end

Immediate rendering

Directly write a view buffer to the terminal. Using this method means that the refresh event does not need to be triggered after creating the views, though can be later triggered when needed.

Vedeu.renders do
  view :some_interface do
    line do
      stream do
        left 'Title goes here', width: 35
      end
      stream do
        right Time.now.strftime('%H:%m'), width: 7
      end
    end
  end
  view :other_interface do
    lines do
      line 'This is content for the main interface.'
      line ''
      line 'Pretty easy eh?'
    end
  end
  # ... some code
end

# or...

Vedeu.render do
  view :my_interface do
    # ... some code
  end
end

Deferred rendering

Define a view (content) for an interface.

As you can see by comparing the examples above to these below, the immediate render simply wraps what is already here in the deferred view.

The views declared within this block are stored in their respective interface back buffers until a refresh event occurs. When the refresh event is triggered, the back buffers are swapped into the front buffers and the content here will be rendered to Vedeu::Terminal#output.

Vedeu.views do
  view :some_interface do
    line do
      stream do
        left 'Title goes here', width: 35
      end
      stream do
        right Time.now.strftime('%H:%m'), width: 7
      end
    end
  end
  view :other_interface do
    lines do
      line 'This is content for the main interface.'
      line ''
      line 'Pretty easy eh?'
    end
  end
  # ... some code
end

Specifying view content

Provides methods to be used to define views.

Vedeu.renders do
  view :my_interface do
    lines do
      background '#000000'
      foreground '#ffffff'
      line 'This is white text on a black background.'
      line 'Next is a blank line:'
      line ''

      streams { stream 'We can define ' }

      streams do
        foreground '#ff0000'
        stream 'parts of a line '
      end

      streams { stream 'independently using ' }

      streams do
        foreground '#00ff00'
        stream 'streams.'
      end
    end
  end
end
Specify a single line in a view.

Vedeu.renders do
  view :my_interface do
    lines do
      line 'some text...'
      # ... some code

      line 'some more text...'
      # ... some code
    end
  end
end
Define multiple streams (a stream is a subset of a line). Uses Vedeu::DSL::Stream for all directives within the required block.

Vedeu.renders do
  view :my_interface do
    lines do
      line do
        streams do
          # ... some code
        end

        stream do
          # ... some code
        end
      end
    end
  end
end

@todo More documentation coming soon.

Authors Notes

The Rubydoc documentation has more specific information about the DSL methods demonstrated above: RubyDoc.

I've tried to write the DSL in a way which makes it read nice; believing that this will make it easier to use. I hope this is the case for you.