Class ui.Canvas

A canvas is an object that holds methods by which you can draw on the screen.

Programmers experienced in other environments may loosely think of it as a “device context” (Windows) or “graphics context” (Java).

There are several ways you can get your hands on a canvas. One uncommon way (but the first we cover because it’s the easiest to demonstrate) is to ask the tty module to give you a canvas. This canvas encompasses the whole area of the screen:

keymap.bind('C-y', function()

  local c = tty.get_canvas()

  c:set_style(tty.style("white, red"))
  c:fill_rect(4, 2, 30, 5)
  c:draw_box(4, 2, 30, 5)
  c:goto_xy(10, 4)
  c:draw_string(T"Hello world!")

end)

The other way to get a canvas is to ask a widget —any widget— to give you one. Such canvas encompasses only the area of the widget. Here’s an example showing this in an on_draw event of a custom widget:

local function test()

  local marquee = ui.Custom{cols=30, rows=5}

  marquee.on_draw = function(self)

    local c = self:get_canvas()

    c:set_style(tty.style("white, red"))
    c:erase()
    c:goto_xy(2, 2)
    c:draw_string(T"Hello world!")

  end

  ui.Dialog()
    :add(marquee)
    :add(ui.DefaultButtons())
    :run()

end

keymap.bind('C-y', test)

Behind the scenes

Internally, a canvas is just an object whose sole attributes are the coordinates of a rectangle on the screen. It holds no other state.

The only abstraction a canvas (in MC) provides to the programmer is its local coordinates: When you do c:goto_xy(0, 0) (to go to the canvas' top-left corner), the canvas translates it into absolute screen coordinates and hand them to a lower-level function that actually does the job.

The canvas does nothing more than that. Specifically, it doesn’t clip drawing operations. If you pass it coordinates and measures that go past its width or height, or are negative, it won’t care: the drawing will occur “outside” the canvas.

Virtual vs physical screen

All drawings (and cursor positioning) are done to a virtual screen. This virtual screen, during MC’s event loop, is then copied out to the physical screen. This topic is discussed in the tty module. Usually, with a few exceptions mentioned in the previous link, you don’t need to be aware of this fact.

Methods

ui.Canvas:draw_box(x, y, cols, rows[, use_double_lines]) Draws a box.
ui.Canvas:draw_clipped_string(x, y, string[, x1[, x2]) Draws a string, clipped.
ui.Canvas:draw_string(s) Draws a string, at the cursor position.
ui.Canvas:erase([filler]) Erases the canvas' contents.
ui.Canvas:fill_rect(x, y, cols, rows[, filler]) Fills a rectangle.
ui.Canvas:get_char_at(x, y) Gets the character, and style, displayed at a certain position on the screen.
ui.Canvas:get_cols() Gets the canvas' width.
ui.Canvas:get_rows() Gets the canvas' height.
ui.Canvas:get_x() Gets the canvas' distance from the left edge of the screen.
ui.Canvas:get_xy() Gets the cursor position.
ui.Canvas:get_y() Gets the canvas' distance from the top of the screen.
ui.Canvas:goto_xy(x, y) Positions the cursor.
ui.Canvas:goto_xy1(x, y) Positions the cursor.
ui.Canvas:set_style(style_idx) Sets the current style.


Methods

ui.Canvas:draw_box(x, y, cols, rows[, use_double_lines])
Draws a box.

The interior of the box isn’t filled. Only the frame is drawn.

If use_double_lines is true, double-line characters are used. (If your skin does not define these characters, you won’t notice any difference.)

ui.Canvas:draw_clipped_string(x, y, string[, x1[, x2])
Draws a string, clipped.

We explained in the introduction that the canvas doesn’t inherently performs clipping.

This makes matters awkward when you simply want to print a string that may overflow the canvas' area.

This utility function comes to your rescue. It does the clipping (or “trimming”) for you: it ensures that no part of the string is drawn outside the canvas area.

You must provide x and y, the coordinates at which to start drawing the string (they may well be outside the canvas; i.e., negative numbers are allowed). You may also clip the string further to within a (virtual) column in your canvas by supplying its left (x1) and right (x2) edges.

ui.Canvas:draw_string(s)
Draws a string, at the cursor position.
ui.Canvas:erase([filler])
Erases the canvas' contents.

This utility function simply calls fill_rect() over the whole canvas' area and positions the cursor at the top-left corner.

Parameters:

  • filler Optional. A character to use instead of space. (optional)
ui.Canvas:fill_rect(x, y, cols, rows[, filler])
Fills a rectangle.

By default the space character is used to fill the region. You may override this using the ‘filler’ argument.

ui.Canvas:get_char_at(x, y)
Gets the character, and style, displayed at a certain position on the screen.

This function is intended for developers: to aid them in writing tests and automatically generating “screenshots” for code snippets in documentation. Nevertheless, this shouldn’t prevent end-users from using it for “special effects” and various amusements.

-- Redraws the screen in green on black.

keymap.bind('C-y', function()
  local c = tty.get_canvas()
  c:set_style(tty.style('green, black'))

  for y = 0, c:get_rows() - 1 do
    for x = 0, c:get_cols() - 1 do
      c:goto_xy(x,y)
      c:draw_string(c:get_char_at(x,y))
    end
  end
end

(For more examples, see the modules samples.libs.htmlize (or bin/htmlize) and samples.accessories.eyecandy.drop-shadow.)

When combining characters are used (e.g., diacritics), more than one character will be returned. If a wide character (e.g., Japanese) is used, an empty string will be returned for the next screen column.

Implementation issues:

Currently, if ncurses is used, only ASCII characters are supported: line-drawing characters are returned as “#”, and any other characters are returned as “!”. No such limitations exist when using S-Lang.

ui.Canvas:get_cols()
Gets the canvas' width.
ui.Canvas:get_rows()
Gets the canvas' height.
ui.Canvas:get_x()
Gets the canvas' distance from the left edge of the screen.

(You'll seldom use this method because all coordinates fed to the canvas' methods are local to the canvas; no explicit translation arithmetic is needed.)

ui.Canvas:get_xy()
Gets the cursor position.
ui.Canvas:get_y()
Gets the canvas' distance from the top of the screen.

See also get_x.

ui.Canvas:goto_xy(x, y)
Positions the cursor.
ui.Canvas:goto_xy1(x, y)
Positions the cursor.

This method differs from goto_xy in that the coordinates here are one-based. That is, point (1,1) is the top-left cell. In all the other canvas methods the coordinates are zero-based (meaning that (0,0) is the top-left cell).

Use this method where one-based coordinates would make your code clearer.

ui.Canvas:set_style(style_idx)
Sets the current style.

You feed this method a value tty.style returned.

Unless your drawing code is executing within ui.Custom:on_draw, then right after you create a canvas object the current style is effectively random (it’s the last style used by anybody), so calling this method is often one of the first things you would do.

In these documents the following formula is commonly used in code snippets:

c:set_style(tty.style(...))

However, this has two drawbacks. First, the call to tty.style is somewhat costly and should better be cached. Second, such code doesn’t make it easy for end-users to customize the styles.

You can solve this by storing the styles in a table.

In other words, instead of:

marquee.on_draw = function(self)

  local c = self:get_canvas()

  c:goto_xy(0, 0)
  c:set_style(tty.style("white, red"))
  c:draw_string(T"Why did the chicken cross the road?")

  c:goto_xy(0, 1)
  c:set_style(tty.style("white, blue"))
  c:draw_string(T"To get to the other side.")

end)

Do:

local styles = nil

local function init_styles()
  styles = {
    question = tty.style("white, red"),
    answer = tty.style("white, blue"),
  }
end

marquee.on_draw = function(self)

  if not styles then
    init_styles()
  end

  local c = self:get_canvas()

  c:goto_xy(0, 0)
  c:set_style(styles.question)
  c:draw_string(T"Why did the chicken cross the road?")

  c:goto_xy(0, 1)
  c:set_style(styles.answer)
  c:draw_string(T"To get to the other side.")

end)

event.bind('ui::skin-change', function()
  styles = nil
end)

(This code doesn’t yet deliver the promised end-user customizability. That’s because this snippet isn’t a module and therefore users don’t have a way to “reach” into it. See the sample modules for how to do this.)

generated by LDoc 1.4.3 Last updated 2016-08-23 17:29:40