rects.lua

-- Coypright 2015-2017 Stefan Göbel.
--
-- This file is part of Corky.
--
-- Corky is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
-- License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
-- later version.
--
-- Corky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
-- warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
-- details.
--
-- You should have received a copy of the GNU General Public License along with Corky. If not, see
-- <http://www.gnu.org/licenses/>.

-------------------------------------------------------------------------------------------------------------------

--- Rectangular meters for *Corky*.
--
--  **Loading the module:**
--
--    #: include, corky.rects
--
--  **Adding a rectangular meter:**
--
--  _**Note:** This is only one line in the actual configuration. Do not add the line break!_
--
--    #: rect, <name>, <value>, <x>, <y>, <length>, <width>, <rotation>, <fcol>
--       [, <bcol>[, <scale>[, <scol>[, <min>[, <max>[, <hook>]]]]]]
--
--  **Parameters:**
--
--  *`<name>`*
--
--  * The name of the meter, may be any arbitrary string. It is currently not used and may be left empty. Multiple
--  meters with the same name are allowed.
--
--  *`<value>`*
--
--  * The value to display, e.g. `${cpu cpu1}` for a rectangular meter showing the current CPU usage of the first
--  CPU. The value will be retrieved using the corky.cache module's percent() function.
--  To get correct values, it is necessary to manually add the value to the cache and define its minimum and
--  maximum value (either in a Lua script using set() or using the `cache` directive in the
--  configuration file), even if the value itself is already a percentage (like `${cpu cpu1}`)! Please see the
--  corky.cache module for more details.
--
--  *`<x>`*
--
--  * The X coordinate of the starting point of the meter. Must be a valid integer greater than or equal to `0`.
--
--  *`<y>`*
--
--  * The Y coordinate of the starting point of the meter. Must be a valid integer greater than or equal to `0`.
--
--  * **Note:** The meter will be drawn using *Cairo*'s `cairo_line_to()` function, starting at (`<x>`, `<y>`).
--  This coordinate is not located at a corner of the meter, but at the center of the starting edge.
--
--  *`<length>`*
--
--  * The length of the meter. Must be a valid integer greater than `0`.
--
--  *`<width>`*
--
--  * The width of the meter. Must be a valid integer greater than `0`.
--
--  *`<rotation>`*
--
--  * The rotation of the meter, in degrees. Must be a valid number greater than or equal to `0` and less than
--  `360`. For an angle of `0` the meter is drawn in the direction of the positive X axis. The angle increases in
--  clockwise direction, e.g. if set to `90` the meter is drawn downwards, if set to `180` the meter is drawn from
--  right to left, and for `270` it is drawn upwards.
--
--  *`<fcol>`*
--
--  * The foreground color, i.e. the color used to draw the meter from 0% (or the `<min>` value) to the value. The
--  corky.dcolors module will be used to resolve the color, so any named color (see corky.colors), fixed
--  dynamic color or gradient color (see corky.dcolors) may be used, or a `0x<AARRGGBB>` value (or in fact any
--  valid number between `0` and `0xffffffff`, see the colors module) may be used directly.
--
--  * **Note:** There will be no gradient displayed on the meter, i.e. the foreground color will be one color
--  across the length of the meter. The gradient (or fixed dynamic color), if one is used, will just be used to
--  calculate this one color based on the value. If dynamic colors are used, the threshold values must be specified
--  in percent. See corky.dcolors for more details.
--
--  *`<bcol>`*
--
--  * Optional. The background color. If specified, the part of the meter between the value and the 100% position
--  (or the `<max>` value) will be filled with this color. The same notes as for the foreground color apply, please
--  see above.
--
--  *`<scale>`*
--
--  * Optional. The name of a list of scale positions. See the corky.lists module on how to define a list. If
--  this is specified, the list must contain an arbitrary number of numeric values between `0` and `100`
--  (inclusive), in ascending order. These values specify the positions (in percent) of scale marks to be drawn
--  over the meter. To enable these marks, the parameter `<scol>` has to be specified, too.
--
--  *`<scol>`*
--
--  * Optional. The color for the scale marks. The same notes as for the foreground color apply, see above. Note
--  that the `<scale>` has to be a valid list as described above for marks to be drawn.
--
--  *`<min>`*
--
--  * Optional. The minimum value in percent, defaults to `0` if not specified.
--
--  *`<max>`*
--
--  * Optional. The maximum value in percent, defaults to `100` if not specified.
--
--  *`<hook>`*
--
--  * Optional. Specifies when to draw the meter. It must be either `"pre"` (to draw it during the pre-draw hook)
--  or `"post"` (to draw it during the post-draw hook). The default value is "post".
--
--  **Examples:**
--
--  Display the (total) CPU usage. Colors are defined using the `color` (see corky.colors) and `dcolor` (see
--  corky.dcolors) directives, and scale marks will be shown in 10% steps.
--
--  Note that optional values at the end may be left out completely, while the optional name of the meter may be
--  left empty, but the (empty) field itself has to be included!
--
--    #: cache, ${cpu}, 1, 0, 100
--
--    #: color,  black, 0x000000
--    #: color,  grey,  0x444444
--    #: dcolor, fg,    0x00ff00, 50, 0xffff00, 0xff0000
--
--    #: list, scale, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90
--
--    #: rect,, ${cpu}, 50, 50, 100, 10, 0, fg, grey, scale, black
--
--  Display the (total) CPU usage using two meters:
--
--    #: cache, ${cpu}, 1, 0, 100
--
--    #: rect,, ${cpu}, 50, 150, 50, 8, 270, 0xff0000, 0x444444,,,  0,  50
--    #: rect,, ${cpu}, 60, 150, 50, 8, 270, 0xff0000, 0x444444,,, 50, 100
--
--  This example will display two meters for the CPU usage. Values between 0% and 50% will be shown in the first
--  meter, i.e. if the CPU usage is 25%, the first meter will display a 50% value, while the second meter will show
--  0%. Values between 50% and 100% will be shown in the second meter, i.e. for a CPU usage of 75% the first meter
--  will show a 100% value, and the second meter will show a 50% value.
--
--  If you want to use scale marks on the meters, note that these are defined *per meter*. If in the example above
--  a list containing a single value of `50` is specified as `<scale>` parameter, a scale mark at the 50% position
--  of *both* meters will be drawn (i.e. at the 25% and 75% CPU usage positions)!
--
--  @MO corky.rects
--  @CO © 2015-2017 Stefan Göbel
--  @RE 2017033001
--  @LI [GPLv3](http://www.gnu.org/copyleft/gpl.html)
--  @AU Stefan Göbel [[⌂]](http://subtype.de/) [[✉]](mailto:corky@subtype.de)
--  @AL this

-------------------------------------------------------------------------------------------------------------------

--- Stores the defined meters.
--
--  The keys are the meter names, the values are arrays containing tables with the meter data defined under that
--  name, in the order they are added. The meter data tables contain the following keys: `value`, `x`, `y`,
--  `length`, `width`, `rotation`, `fcol`, `bcol`, `scale`, `scol`, `min`, `max`, `hook`. The scale will already be
--  resolved to the appropriate list, all colors will be strings (or numbers) and will be resolved at runtime to
--  allow for dynamic colors.
--
--  @l rects

-------------------------------------------------------------------------------------------------------------------

local utils  = require "corky.utils"                                          -- For table.pack() and .unpack().
local cache  = require "corky.cache"
local cairo  = require "corky.cairo"
local colors = require "corky.dcolors"
local lists  = require "corky.lists"
local v      = require "corky.validator"
local rects  = {}
local this   = {}

-------------------------------------------------------------------------------------------------------------------

--- Add a rectangular meter.
--
--  The parameters are the same as the parameters for the configuration directive, in the same order. Please see
--  above for more details.
--
--  This function will add the new meter to the list of meters, it will not draw it!
--
--  @p ...
--  * `name`: (*string*) Name of the meter.
--  * `value`: (*string*) Value of the meter.
--  * `x`: (**_int_**) X coordinate of the starting point (`x >= 0`).
--  * `y`: (**_int_**) Y coordinate of the starting point (`y >= 0`).
--  * `length`: (**_int_**) The length of the meter (`length > 0`).
--  * `width`: (**_int_**) The width of the meter (`width > 0`).
--  * `rotation`: (**_number_**) The rotation of the meter, in degrees (`0 <= rotation < 360`).
--  * `fcol`: The foreground color, uses corky.dcolors to get the actual color.
--  * `bcol`: The background color, uses corky.dcolors to get the actual color. (*optional*)
--  * `scale`: (*string*) Name of a list with scale mark positions, see corky.lists. (*optional*)
--  * `scol`: Scale color, uses corky.dcolors to get the actual color. (*optional*)
--  * `min`: (**_number_**) Minimum value for the meter (`0 <= min < 100`). (*optional*)
--  * `max`: (**_number_**) Maximum value for the meter (`0 < max <= 100`). (*optional*)
--  * `hook`: (*string*) When to draw the meter (either `"pre"` or `"post"`). (*optional*)
--  @B Returns `true` if the meter could be added, `false` in case of any error.

function this.add (name, value, x, y, length, width, rotation, fcol, bcol, scale, scol, min, max, hook)

   local valid = true

   valid, name     = v { v (name,     valid) . en . dv ("")                            }
   valid, value    = v { v (value,    valid) . r . s . ne ("")                         }
   valid, x        = v { v (x,        valid) . n . ge (0)                              }
   valid, y        = v { v (y,        valid) . n . ge (0)                              }
   valid, length   = v { v (length,   valid) . n . gt (0)                              }
   valid, width    = v { v (width,    valid) . n . gt (0)                              }
   valid, rotation = v { v (rotation, valid) . n . ge (0) . lt (360)                   }
   valid, fcol     = v { v (fcol,     valid) . en . r                                  }
   valid, bcol     = v { v (bcol,     valid) . en                                      }
   valid, scale    = v { v (scale,    valid) . en                                      }
   valid, scol     = v { v (scol,     valid) . en                                      }
   valid, min      = v { v (min,      valid) . en . dv (  0) . n . ge (  0) . lt (100) }
   valid, max      = v { v (max,      valid) . en . dv (100) . n . gt (min) . le (100) }
   valid, hook     = v { v (hook,     valid) . en . dv ("post") . eq ("pre", "post")   }

   if not valid then
      return false
   end

   if scale then
      scale = lists.get (scale)
      if not scale then
         return false
      end
      local last = nil
      for i = 1, #scale do
         valid, scale [i] =  v { v (scale [i]) . n . ir (0, 100) . ni (last) . gt (last) }
         if not valid then
            return false
         end
         last = scale [i]
      end
   end

   local rect = {
      value    = value,
      x        = x,
      y        = y,
      length   = length,
      width    = width,
      rotation = rotation,
      fcol     = fcol,
      bcol     = bcol,
      scale    = scale,
      scol     = scol,
      min      = min,
      max      = max,
      hook     = hook
   }

   if rects [name] then
      rects [name] [#(rects [name]) + 1] = rect
   else
      rects [name] = { rect }
   end

   return true

end

-------------------------------------------------------------------------------------------------------------------

--- Draw a rectangular meter.
--
--  **Note:** This function assumes that corky.cairo.init() has been called and a valid surface and
--  context are available!
--
--  @i x X coordinate of the starting point.
--  @i y Y coordinate of the starting point.
--  @i length The length of the meter.
--  @i width The width of the meter.
--  @n rotation The rotation of the meter, in degrees.
--  @n p The percent value, relative to the meter.
--  @t fcol The foreground color, as `{ r, g, b, a }`, all values between `0` and `1` (inclusive).
--  @t[opt] bcol The background color, as `{ r, g, b, a }`, all values between `0` and `1` (inclusive).
--  @t[opt] scale Array containing the scale mark positions, in percent.
--  @t[opt] scol The scale marks' color, as `{ r, g, b, a }`, all values between `0` and `1` (inclusive).

function this.rect (x, y, length, width, rotation, p, fcol, bcol, scale, scol)

   cairo.save (cairo.c)

   cairo.translate (cairo.c, x, y)
   if rotation ~= 0 then
      cairo.rotate (cairo.c, rotation * (math.pi / 180))
   end

   local val_x = length * p / 100

   cairo.set_line_width (cairo.c, width)

   if bcol and p ~= 100 then
      cairo.move_to (cairo.c, val_x, 0)
      cairo.set_source_rgba (cairo.c, table.unpack (bcol))
      cairo.line_to (cairo.c, length, 0)
      cairo.stroke (cairo.c)
   end

   if p ~= 0 then
      cairo.move_to (cairo.c, 0, 0)
      cairo.set_source_rgba (cairo.c, table.unpack (fcol))
      cairo.rel_line_to (cairo.c, val_x, 0)
      cairo.stroke (cairo.c)
   end

   if scale and scol then

      cairo.set_source_rgba (cairo.c, table.unpack (scol))

      for i, pos in ipairs (scale) do

         cairo.move_to (cairo.c, math.floor (length * pos / 100 + 0.5), 0)
         cairo.rel_line_to (cairo.c, 1, 0)
         cairo.stroke (cairo.c)

      end

   end

   cairo.restore (cairo.c)

end

-------------------------------------------------------------------------------------------------------------------

--- Draw the meters.
--
--  @s hook Which meters to draw, either `"pre"` or `"post"`.

local function draw (hook)

   for _, entry in pairs (rects) do
      for i = 1, #entry do
         local c = entry [i]
         if c.hook == hook then
            local valid, p, fcol, bcol, scol = true, cache.percent (c.value)
            if p then
               valid, fcol = v { v (c.fcol, valid) . r . ch (colors.get (c.fcol, p)) . r }
               valid, bcol = v { v (c.bcol, valid) . o . ch (colors.get (c.bcol, p)) . r }
               valid, scol = v { v (c.scol, valid) . o . ch (colors.get (c.scol, p)) . r }
               if valid then
                  if c.min ~= 0 or c.max ~= 100 then
                     p = utils.percent (p, c.min, c.max)
                  end
                  this.rect (c.x, c.y, c.length, c.width, c.rotation, p, fcol, bcol, c.scale, scol)
               end
            end
         end
      end
   end

end

-------------------------------------------------------------------------------------------------------------------

do

   --- Configuration handler for rect settings.
   --
   --  This handler will be called automatically for every `rect` directive in the configuration
   --  file.
   --
   --  @t setting Array containing the configuration directive split into its individual parts.
   --  @B If successful `true`, `false` in case of any error.
   --  @c corky

   local function config_handler (setting)

      if #setting < 9 or #setting > 15 then                                      -- Wrong number of parameters.
         return false
      end

      return this.add (select (2, table.unpack (setting)))

   end

   local config = require "corky.config"
   config.handler ("rect", config_handler)

   local hooks = require "corky.hooks"
   hooks.add ("pre_draw",  function () draw ("pre" ) end)
   hooks.add ("post_draw", function () draw ("post") end)

end

-------------------------------------------------------------------------------------------------------------------

return this

--                                                     :indentSize=3:tabSize=3:noTabs=true:mode=lua:maxLineLen=115:
generated by LDoc 1.4.6 Last updated 2019-02-14 20:51:07