REF remove submodule for core and move all lua files to common subdir
This commit is contained in:
parent
5549d8c95d
commit
f6e3ff9574
|
@ -1,3 +0,0 @@
|
||||||
[submodule "core"]
|
|
||||||
path = core
|
|
||||||
url = gitolite:conky/core.git
|
|
|
@ -49,6 +49,9 @@ properties:
|
||||||
required: [show_temp, show_clock, show_gpu_util, show_mem_util, show_vid_util]
|
required: [show_temp, show_clock, show_gpu_util, show_mem_util, show_vid_util]
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
properties:
|
properties:
|
||||||
|
dev_power:
|
||||||
|
description: the sysfs path to the graphics card power indicator
|
||||||
|
type: string
|
||||||
show_temp:
|
show_temp:
|
||||||
description: show the GPU temp
|
description: show the GPU temp
|
||||||
type: boolean
|
type: boolean
|
||||||
|
|
28
conky.conf
28
conky.conf
|
@ -3,16 +3,14 @@
|
||||||
|
|
||||||
local conky_dir = debug.getinfo(1).source:match("@?(.*/)")
|
local conky_dir = debug.getinfo(1).source:match("@?(.*/)")
|
||||||
local subdirs = {
|
local subdirs = {
|
||||||
'?.lua',
|
'src/?.lua',
|
||||||
'drawing/?.lua',
|
'src/modules/?.lua',
|
||||||
'schema/?.lua',
|
'src/widget/?.lua',
|
||||||
'core/?.lua',
|
'src/widget/arc/?.lua',
|
||||||
'core/widget/?.lua',
|
'src/widget/text/?.lua',
|
||||||
'core/widget/arc/?.lua',
|
'src/widget/timeseries/?.lua',
|
||||||
'core/widget/text/?.lua',
|
'src/widget/rect/?.lua',
|
||||||
'core/widget/timeseries/?.lua',
|
'src/widget/line/?.lua',
|
||||||
'core/widget/rect/?.lua',
|
|
||||||
'core/widget/line/?.lua',
|
|
||||||
'lib/share/lua/5.4/?.lua',
|
'lib/share/lua/5.4/?.lua',
|
||||||
'lib/share/lua/5.4/?/init.lua',
|
'lib/share/lua/5.4/?/init.lua',
|
||||||
}
|
}
|
||||||
|
@ -41,7 +39,7 @@ if i_o.exe_exists('yajsv') then
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
validate_config = function(_)
|
validate_config = function(_)
|
||||||
print('WARNING: could not validate config')
|
i_o.warnf('could not validate config')
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -52,16 +50,16 @@ local find_valid_config = function(paths)
|
||||||
local r = i_o.read_file(path)
|
local r = i_o.read_file(path)
|
||||||
if r ~= nil then
|
if r ~= nil then
|
||||||
if validate_config(path) then
|
if validate_config(path) then
|
||||||
i_o.printf('INFO: Using config at %s', path)
|
i_o.infof('Using config at %s', path)
|
||||||
return path, yaml.load(r)
|
return path, yaml.load(r)
|
||||||
else
|
else
|
||||||
i_o.printf('WARNING: %s did not pass; trying next', path)
|
i_o.warnf('%s did not pass; trying next', path)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
i_o.printf('INFO: could not find %s; trying next', path)
|
i_o.infof('could not find %s; trying next', path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
assert(false, 'ERROR: could not load valid config')
|
i_o.assertf(false, 'ERROR: could not load valid config')
|
||||||
end
|
end
|
||||||
|
|
||||||
local get_config_dir = function()
|
local get_config_dir = function()
|
||||||
|
|
1
core
1
core
|
@ -1 +0,0 @@
|
||||||
Subproject commit ac2604709f5344125519913849e9013e1dfea717
|
|
|
@ -1,20 +0,0 @@
|
||||||
local M = {}
|
|
||||||
|
|
||||||
M.LEFT_X = 32
|
|
||||||
M.SECTION_WIDTH = 436
|
|
||||||
M.CENTER_PAD = 20
|
|
||||||
M.PANEL_HORZ_SPACING = 10
|
|
||||||
M.PANEL_MARGIN_X = 20
|
|
||||||
M.PANEL_MARGIN_Y = 10
|
|
||||||
M.TOP_Y = 21
|
|
||||||
M.SIDE_HEIGHT = 1020
|
|
||||||
M.CENTER_HEIGHT = 220
|
|
||||||
|
|
||||||
local margin_width = M.PANEL_MARGIN_X * 2 + M.PANEL_HORZ_SPACING
|
|
||||||
|
|
||||||
M.CENTER_LEFT_X = M.LEFT_X + M.SECTION_WIDTH + margin_width
|
|
||||||
M.CENTER_RIGHT_X = M.CENTER_LEFT_X + M.SECTION_WIDTH + M.CENTER_PAD
|
|
||||||
M.CENTER_WIDTH = M.SECTION_WIDTH * 2 + M.CENTER_PAD
|
|
||||||
M.RIGHT_X = M.CENTER_LEFT_X + M.CENTER_WIDTH + margin_width
|
|
||||||
|
|
||||||
return M
|
|
|
@ -1,64 +0,0 @@
|
||||||
local M = {}
|
|
||||||
|
|
||||||
local color = require 'color'
|
|
||||||
|
|
||||||
M.FONT = 'Neuropolitical'
|
|
||||||
|
|
||||||
-- text colors
|
|
||||||
M.HEADER_FG = color.rgb(0xefefef)
|
|
||||||
|
|
||||||
M.PRIMARY_FG = color.rgb(0xbfe1ff)
|
|
||||||
M.CRITICAL_FG = color.rgb(0xff8282)
|
|
||||||
|
|
||||||
M.INACTIVE_TEXT_FG = color.rgb(0xc8c8c8)
|
|
||||||
M.MID_GREY = color.rgb(0xd6d6d6)
|
|
||||||
M.BORDER_FG = color.rgb(0x888888)
|
|
||||||
M.PLOT_GRID_FG = color.rgb(0x666666)
|
|
||||||
M.PLOT_OUTLINE_FG = color.rgb(0x777777)
|
|
||||||
|
|
||||||
|
|
||||||
-- arc bg colors
|
|
||||||
local GREY2 = 0xbfbfbf
|
|
||||||
local GREY5 = 0x565656
|
|
||||||
M.INDICATOR_BG = color.gradient_rgb{
|
|
||||||
[0.0] = GREY5,
|
|
||||||
[0.5] = GREY2,
|
|
||||||
[1.0] = GREY5
|
|
||||||
}
|
|
||||||
|
|
||||||
-- arc/bar fg colors
|
|
||||||
local PRIMARY1 = 0x99CEFF
|
|
||||||
local PRIMARY3 = 0x316BA6
|
|
||||||
M.INDICATOR_FG_PRIMARY = color.gradient_rgb{
|
|
||||||
[0.0] = PRIMARY3,
|
|
||||||
[0.5] = PRIMARY1,
|
|
||||||
[1.0] = PRIMARY3
|
|
||||||
}
|
|
||||||
|
|
||||||
local CRITICAL1 = 0xFF3333
|
|
||||||
local CRITICAL3 = 0xFFB8B8
|
|
||||||
M.INDICATOR_FG_CRITICAL = color.gradient_rgb{
|
|
||||||
[0.0] = CRITICAL1,
|
|
||||||
[0.5] = CRITICAL3,
|
|
||||||
[1.0] = CRITICAL1
|
|
||||||
}
|
|
||||||
|
|
||||||
-- plot colors
|
|
||||||
local PLOT_PRIMARY1 = 0x003f7c
|
|
||||||
local PLOT_PRIMARY2 = 0x1e90ff
|
|
||||||
local PLOT_PRIMARY3 = 0x316ece
|
|
||||||
local PLOT_PRIMARY4 = 0x8cc7ff
|
|
||||||
M.PLOT_FILL_BORDER_PRIMARY = color.gradient_rgb{
|
|
||||||
[0.0] = PLOT_PRIMARY1,
|
|
||||||
[1.0] = PLOT_PRIMARY2
|
|
||||||
}
|
|
||||||
|
|
||||||
M.PLOT_FILL_BG_PRIMARY = color.gradient_rgba{
|
|
||||||
[0.2] = {PLOT_PRIMARY3, 0.5},
|
|
||||||
[1.0] = {PLOT_PRIMARY4, 1.0}
|
|
||||||
}
|
|
||||||
|
|
||||||
-- panel pattern
|
|
||||||
M.PANEL_BG = color.rgba(0x121212, 0.7)
|
|
||||||
|
|
||||||
return M
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
local err = require 'err'
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- colors
|
||||||
|
--
|
||||||
|
-- these are tables like {red :: Int, green :: Int, blue :: Int, alpha :: Int}
|
||||||
|
|
||||||
|
local rgba = function(hex, alpha)
|
||||||
|
local obj = err.safe_table(
|
||||||
|
{
|
||||||
|
r = ((hex / 0x10000) % 0x100) / 255.,
|
||||||
|
g = ((hex / 0x100) % 0x100) / 255.,
|
||||||
|
b = (hex % 0x100) / 255.,
|
||||||
|
a = alpha,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return err.set_type(obj, "color")
|
||||||
|
end
|
||||||
|
|
||||||
|
local rgb = function(hex)
|
||||||
|
return rgba(hex, 1.0)
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Gradients
|
||||||
|
--
|
||||||
|
-- these are tables like {[stop] :: color} where stop is a float between 0 and 1
|
||||||
|
-- and color is a color as defined above
|
||||||
|
|
||||||
|
local _make_gradient = function(colorstops, f)
|
||||||
|
local c = {}
|
||||||
|
for stop, spec in pairs(colorstops) do
|
||||||
|
assert(
|
||||||
|
stop <= 1 and stop >= 0,
|
||||||
|
"ERROR: color stop must be between 0 and 1; got " .. stop
|
||||||
|
)
|
||||||
|
c[stop] = f(spec)
|
||||||
|
end
|
||||||
|
return err.set_type(err.safe_table(c), "gradient")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- {[stop] :: hex} -> Gradient
|
||||||
|
local gradient_rgb = function(colorstops)
|
||||||
|
return _make_gradient(colorstops, rgb)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- {[stop] :: {hex, alpha}} -> Gradient
|
||||||
|
local gradient_rgba = function(colorstops)
|
||||||
|
return _make_gradient(
|
||||||
|
colorstops,
|
||||||
|
function(spec) return rgba(spec[1], spec[2]) end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
local compile_patterns
|
||||||
|
|
||||||
|
compile_patterns = function(patterns)
|
||||||
|
local r = {}
|
||||||
|
for k, v in pairs(patterns) do
|
||||||
|
if type(v) == "number" then
|
||||||
|
r[k] = rgb(v)
|
||||||
|
elseif v.color ~= nil then
|
||||||
|
r[k] = rgba(v.color, v.alpha)
|
||||||
|
elseif v.gradient ~= nil then
|
||||||
|
local p = {}
|
||||||
|
local g = v.gradient
|
||||||
|
for i = 1, #g do
|
||||||
|
local _g = g[i]
|
||||||
|
p[_g.stop] = _g.color
|
||||||
|
end
|
||||||
|
r[k] = gradient_rgb(p)
|
||||||
|
elseif v.gradient_alpha ~= nil then
|
||||||
|
local p = {}
|
||||||
|
local g = v.gradient_alpha
|
||||||
|
for i = 1, #g do
|
||||||
|
local _g = g[i]
|
||||||
|
p[_g.stop] = {_g.color, _g.alpha}
|
||||||
|
end
|
||||||
|
r[k] = gradient_rgba(p)
|
||||||
|
else
|
||||||
|
r[k] = compile_patterns(v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
return compile_patterns
|
|
@ -0,0 +1,52 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local i_o = require 'i_o'
|
||||||
|
|
||||||
|
M.assert_trace = function(test, msg)
|
||||||
|
if not test then
|
||||||
|
i_o.errorf(msg)
|
||||||
|
print(debug.traceback())
|
||||||
|
os.exit(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.safe_table = function(tbl)
|
||||||
|
local ck_key = function(_, key)
|
||||||
|
local v = rawget(tbl, key)
|
||||||
|
M.assert_trace(v ~= nil, "key doesn't exist: "..key)
|
||||||
|
return v
|
||||||
|
end
|
||||||
|
return setmetatable(tbl, {__index = ck_key})
|
||||||
|
end
|
||||||
|
|
||||||
|
local TYPE_KEY = '__type'
|
||||||
|
|
||||||
|
M.set_type = function(tbl, _type)
|
||||||
|
local mt = getmetatable(tbl)
|
||||||
|
mt[TYPE_KEY] = _type
|
||||||
|
return setmetatable(tbl, mt)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.get_type = function(x)
|
||||||
|
local ltype = type(x)
|
||||||
|
if ltype == "table" then
|
||||||
|
local mt = getmetatable(x)
|
||||||
|
if mt == nil then
|
||||||
|
return ltype
|
||||||
|
else
|
||||||
|
return mt[TYPE_KEY]
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return ltype
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.check_type = function(x, _type)
|
||||||
|
local xtype = nil
|
||||||
|
if x ~= nil then
|
||||||
|
xtype = M.get_type(x)
|
||||||
|
end
|
||||||
|
i_o.assertf(xtype == _type, "type must be '%s' got '%s' instead", _type, xtype)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,43 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local __tostring = tostring
|
||||||
|
local __math_floor = math.floor
|
||||||
|
local __math_ceil = math.ceil
|
||||||
|
local __string_format = string.format
|
||||||
|
|
||||||
|
M.round = function(x, places)
|
||||||
|
local m = 10 ^ (places or 0)
|
||||||
|
if x >= 0 then
|
||||||
|
return __math_floor(x * m + 0.5) / m
|
||||||
|
else
|
||||||
|
return __math_ceil(x * m - 0.5) / m
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.round_to_string = function(x, places)
|
||||||
|
places = places or 0
|
||||||
|
if places >= 0 then
|
||||||
|
return __string_format('%.'..places..'f', x)
|
||||||
|
else
|
||||||
|
return __tostring(M.round(x, 0))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.precision_round_to_string = function(x, sig_fig)
|
||||||
|
sig_fig = sig_fig or 4
|
||||||
|
if x < 10 then return M.round_to_string(x, sig_fig - 1)
|
||||||
|
elseif x < 100 then return M.round_to_string(x, sig_fig - 2)
|
||||||
|
elseif x < 1000 then return M.round_to_string(x, sig_fig - 3)
|
||||||
|
else return M.round_to_string(x, sig_fig - 4)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.convert_data_val = function(x)
|
||||||
|
if x < 1024 then return '', x
|
||||||
|
elseif x < 1048576 then return 'Ki', x / 1024
|
||||||
|
elseif x < 1073741824 then return 'Mi', x / 1048576
|
||||||
|
else return 'Gi', x / 1073741824
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,107 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local __string_format = string.format
|
||||||
|
local __tonumber = tonumber
|
||||||
|
local __os_execute = os.execute
|
||||||
|
local __io_popen = io.popen
|
||||||
|
local __io_open = io.open
|
||||||
|
local __string_match = string.match
|
||||||
|
local __conky_parse = conky_parse
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- logging/printing
|
||||||
|
|
||||||
|
M.printf = function(fmt, ...)
|
||||||
|
print(__string_format(fmt, ...))
|
||||||
|
end
|
||||||
|
|
||||||
|
M.logf = function(level, fmt, ...)
|
||||||
|
M.printf(level..': '..fmt, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.errorf = function(fmt, ...)
|
||||||
|
M.logf('ERROR', fmt, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.warnf = function(fmt, ...)
|
||||||
|
M.logf('WARN', fmt, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.infof = function(fmt, ...)
|
||||||
|
M.logf('INFO', fmt, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.assertf = function(test, fmt, ...)
|
||||||
|
-- NOTE use exit here because the assert command will only break one loop
|
||||||
|
-- in the conky update cycle rather than quit the entire program
|
||||||
|
if not test then
|
||||||
|
M.errorf(fmt, ...)
|
||||||
|
print(debug.traceback())
|
||||||
|
os.exit(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- reading files/command output
|
||||||
|
|
||||||
|
--[[
|
||||||
|
available modes per lua docs
|
||||||
|
*n: number (actually returns a number)
|
||||||
|
*a: entire file (default here)
|
||||||
|
*l: reads one line and strips \n (default for read cmd)
|
||||||
|
*L; reads one line and keeps \n
|
||||||
|
N: reads number of lines (where N is a number)
|
||||||
|
--]]
|
||||||
|
local read_entire_file = function(file, regex, mode)
|
||||||
|
if not file then return end
|
||||||
|
local str = file:read(mode or '*a')
|
||||||
|
file:close()
|
||||||
|
if not str then return end
|
||||||
|
if regex then return __string_match(str, regex) or '' else return str end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.read_file = function(path, regex, mode)
|
||||||
|
return read_entire_file(__io_open(path, 'rb'), regex, mode)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.execute_cmd = function(cmd, regex, mode)
|
||||||
|
return read_entire_file(__io_popen(cmd), regex, mode)
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- boolean tests
|
||||||
|
|
||||||
|
M.exit_code_cmd = function(cmd)
|
||||||
|
local _, _, rc = __os_execute(cmd)
|
||||||
|
return rc
|
||||||
|
end
|
||||||
|
|
||||||
|
M.exe_exists = function(exe)
|
||||||
|
return M.exit_code_cmd('command -v '..exe..' > /dev/null') == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
M.assert_exe_exists = function(exe)
|
||||||
|
M.assertf(M.exe_exists(exe), 'executable %s not found', exe)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.file_exists = function(path)
|
||||||
|
return M.exit_code_cmd('stat '..path..' > /dev/null 2>&1') == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
M.assert_file_exists = function(path)
|
||||||
|
M.assertf(M.file_exists(path), '%s does not exist', path)
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- conky object execution
|
||||||
|
|
||||||
|
M.conky = function(expr, regex)
|
||||||
|
local ans = __conky_parse(expr)
|
||||||
|
if regex then return __string_match(ans, regex) or '' else return ans end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.conky_numeric = function(expr, regex)
|
||||||
|
return __tonumber(M.conky(expr, regex)) or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,28 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
M.sequence = function(...)
|
||||||
|
local fs = {...}
|
||||||
|
for i = 1, #fs do
|
||||||
|
fs[i]()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.each = function(f, seq, ...)
|
||||||
|
for i = 1, #seq do
|
||||||
|
f(seq[i], ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.ieach = function(f, seq, ...)
|
||||||
|
for i = 1, #seq do
|
||||||
|
f(i, seq[i], ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.each2 = function(f, seq1, seq2, ...)
|
||||||
|
for i = 1, #seq1 do
|
||||||
|
f(seq1[i], seq2[i], ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,194 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local __string_gsub = string.gsub
|
||||||
|
local __string_char = string.char
|
||||||
|
local __string_find = string.find
|
||||||
|
local __string_sub = string.sub
|
||||||
|
local __table_concat = table.concat
|
||||||
|
local __math_floor = math.floor
|
||||||
|
local __pairs = pairs
|
||||||
|
local __tonumber = tonumber
|
||||||
|
|
||||||
|
local decode -- to ref this before definition
|
||||||
|
|
||||||
|
local decode_scan_whitespace = function(s, start_pos)
|
||||||
|
local whitespace = " \n\r\t"
|
||||||
|
local string_len = #s
|
||||||
|
|
||||||
|
while (__string_find(whitespace, __string_sub(s, start_pos, start_pos), 1, true) and
|
||||||
|
start_pos <= string_len) do
|
||||||
|
start_pos = start_pos + 1
|
||||||
|
end
|
||||||
|
return start_pos
|
||||||
|
end
|
||||||
|
|
||||||
|
local decode_scan_array = function(s, start_pos)
|
||||||
|
local array = {}
|
||||||
|
local string_len = #s
|
||||||
|
|
||||||
|
start_pos = start_pos + 1
|
||||||
|
|
||||||
|
repeat
|
||||||
|
start_pos = decode_scan_whitespace(s, start_pos)
|
||||||
|
|
||||||
|
local cur_char = __string_sub(s,start_pos,start_pos)
|
||||||
|
|
||||||
|
if (cur_char == ']') then
|
||||||
|
return array, start_pos + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if (cur_char == ',') then
|
||||||
|
start_pos = decode_scan_whitespace(s, start_pos + 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
object, start_pos = decode(s, start_pos)
|
||||||
|
array[#array + 1] = object
|
||||||
|
until false
|
||||||
|
end
|
||||||
|
|
||||||
|
local decode_scan_comment = function(s, start_pos)
|
||||||
|
local end_pos = __string_find(s, '*/', start_pos + 2)
|
||||||
|
return end_pos + 2
|
||||||
|
end
|
||||||
|
|
||||||
|
local decode_scan_constant = function(s, start_pos)
|
||||||
|
local consts = {["true"] = true, ["false"] = false, ["null"] = nil}
|
||||||
|
local const_names = {"true", "false", "null"}
|
||||||
|
|
||||||
|
for _, k in __pairs(const_names) do
|
||||||
|
if __string_sub(s, start_pos, start_pos + #k - 1 ) == k then
|
||||||
|
return consts[k], start_pos + #k
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local decode_scan_number = function(s, start_pos)
|
||||||
|
local end_pos = start_pos + 1
|
||||||
|
local string_len = #s
|
||||||
|
local acceptable_chars = "+-0123456789.e"
|
||||||
|
|
||||||
|
while (__string_find(acceptable_chars, __string_sub(s, end_pos, end_pos), 1, true)
|
||||||
|
and end_pos <= string_len) do
|
||||||
|
end_pos = end_pos + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local number_string = __string_gsub(__string_sub(s, start_pos, end_pos - 1), '+', '')
|
||||||
|
return __tonumber(number_string), end_pos
|
||||||
|
end
|
||||||
|
|
||||||
|
local decode_scan_object = function(s, start_pos)
|
||||||
|
local object = {}
|
||||||
|
local string_len = #s
|
||||||
|
local key, value
|
||||||
|
|
||||||
|
start_pos = start_pos + 1
|
||||||
|
|
||||||
|
repeat
|
||||||
|
start_pos = decode_scan_whitespace(s, start_pos)
|
||||||
|
|
||||||
|
local cur_char = __string_sub(s, start_pos, start_pos)
|
||||||
|
|
||||||
|
if (cur_char == '}') then
|
||||||
|
return object, start_pos + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if (cur_char == ',') then
|
||||||
|
start_pos = decode_scan_whitespace(s, start_pos + 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Scan the key
|
||||||
|
key, start_pos = decode(s, start_pos)
|
||||||
|
|
||||||
|
start_pos = decode_scan_whitespace(s, start_pos)
|
||||||
|
start_pos = decode_scan_whitespace(s, start_pos + 1)
|
||||||
|
|
||||||
|
value, start_pos = decode(s, start_pos)
|
||||||
|
|
||||||
|
object[key] = value
|
||||||
|
until false
|
||||||
|
end
|
||||||
|
|
||||||
|
local escape_sequences = {
|
||||||
|
["\\t"] = "\t",
|
||||||
|
["\\f"] = "\f",
|
||||||
|
["\\r"] = "\r",
|
||||||
|
["\\n"] = "\n",
|
||||||
|
["\\b"] = "\b"
|
||||||
|
}
|
||||||
|
|
||||||
|
setmetatable(escape_sequences, {__index = function(t, k) return __string_sub(k, 2) end})--skip "\"
|
||||||
|
|
||||||
|
local decode_scan_string = function (s, start_pos)
|
||||||
|
local start_char = __string_sub(s, start_pos, start_pos)
|
||||||
|
|
||||||
|
local t = {}
|
||||||
|
local i, j = start_pos, start_pos
|
||||||
|
|
||||||
|
while __string_find(s, start_char, j + 1) ~= j + 1 do
|
||||||
|
local oldj = j
|
||||||
|
local x, y = __string_find(s, start_char, oldj + 1)
|
||||||
|
|
||||||
|
i, j = __string_find(s, "\\.", j + 1)
|
||||||
|
|
||||||
|
if not i or x < i then i, j = x, y - 1 end
|
||||||
|
|
||||||
|
t[#t + 1] = __string_sub(s, oldj + 1, i - 1)
|
||||||
|
|
||||||
|
if __string_sub(s, i, j) == "\\u" then
|
||||||
|
local a = __string_sub(s, j + 1, j + 4)
|
||||||
|
local n = __tonumber(a, 16)
|
||||||
|
local x
|
||||||
|
|
||||||
|
j = j + 4
|
||||||
|
|
||||||
|
if n < 0x80 then
|
||||||
|
x = __string_char(n % 0x80)
|
||||||
|
elseif n < 0x800 then
|
||||||
|
x = __string_char(0xC0 + (__math_floor(n / 64) % 0x20), 0x80 + (n % 0x40))
|
||||||
|
else
|
||||||
|
x = __string_char(0xE0 + (__math_floor(n / 4096) % 0x10), 0x80 +
|
||||||
|
(__math_floor(n / 64) % 0x40), 0x80 + (n % 0x40))
|
||||||
|
end
|
||||||
|
|
||||||
|
t[#t + 1] = x
|
||||||
|
else
|
||||||
|
t[#t + 1] = escape_sequences[__string_sub(s, i, j)]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
t[#t + 1] = __string_sub(j, j + 1)
|
||||||
|
|
||||||
|
return __table_concat(t, ""), j + 2
|
||||||
|
end
|
||||||
|
|
||||||
|
decode = function(s, start_pos)
|
||||||
|
start_pos = start_pos or 1
|
||||||
|
start_pos = decode_scan_whitespace(s, start_pos)
|
||||||
|
|
||||||
|
local cur_char = __string_sub(s, start_pos, start_pos)
|
||||||
|
|
||||||
|
if cur_char == '{' then
|
||||||
|
return decode_scan_object(s, start_pos)
|
||||||
|
end
|
||||||
|
|
||||||
|
if cur_char == '[' then
|
||||||
|
return decode_scan_array(s, start_pos)
|
||||||
|
end
|
||||||
|
|
||||||
|
if __string_find("+-0123456789.e", cur_char, 1, true) then
|
||||||
|
return decode_scan_number(s, start_pos)
|
||||||
|
end
|
||||||
|
|
||||||
|
if cur_char == [["]] or cur_char == [[']] then
|
||||||
|
return decode_scan_string(s, start_pos)
|
||||||
|
end
|
||||||
|
|
||||||
|
if __string_sub(s, start_pos, start_pos + 1) == '/*' then
|
||||||
|
return decode(s, decode_scan_comment(s, start_pos))
|
||||||
|
end
|
||||||
|
|
||||||
|
return decode_scan_constant(s, start_pos)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.decode = decode
|
||||||
|
|
||||||
|
return M
|
|
@ -19,43 +19,10 @@ local style = require 'style'
|
||||||
local source = require 'source'
|
local source = require 'source'
|
||||||
local pure = require 'pure'
|
local pure = require 'pure'
|
||||||
|
|
||||||
local compile_patterns
|
|
||||||
|
|
||||||
-- TODO move to color module
|
|
||||||
compile_patterns = function(patterns)
|
|
||||||
local r = {}
|
|
||||||
for k, v in pairs(patterns) do
|
|
||||||
if type(v) == "number" then
|
|
||||||
r[k] = color.rgb(v)
|
|
||||||
elseif v.color ~= nil then
|
|
||||||
r[k] = color.rgba(v.color, v.alpha)
|
|
||||||
elseif v.gradient ~= nil then
|
|
||||||
local p = {}
|
|
||||||
local g = v.gradient
|
|
||||||
for i = 1, #g do
|
|
||||||
local _g = g[i]
|
|
||||||
p[_g.stop] = _g.color
|
|
||||||
end
|
|
||||||
r[k] = color.gradient_rgb(p)
|
|
||||||
elseif v.gradient_alpha ~= nil then
|
|
||||||
local p = {}
|
|
||||||
local g = v.gradient_alpha
|
|
||||||
for i = 1, #g do
|
|
||||||
local _g = g[i]
|
|
||||||
p[_g.stop] = {_g.color, _g.alpha}
|
|
||||||
end
|
|
||||||
r[k] = color.gradient_rgba(p)
|
|
||||||
else
|
|
||||||
r[k] = compile_patterns(v)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return r
|
|
||||||
end
|
|
||||||
|
|
||||||
return function(config)
|
return function(config)
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local patterns = compile_patterns(config.theme.patterns)
|
local patterns = color(config.theme.patterns)
|
||||||
local font = config.theme.font
|
local font = config.theme.font
|
||||||
local font_sizes = font.sizes
|
local font_sizes = font.sizes
|
||||||
local font_family = font.family
|
local font_family = font.family
|
|
@ -10,7 +10,7 @@ return function(config, main_state, common, width, point)
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
-- smartd
|
-- smartd
|
||||||
|
|
||||||
i_o.exe_assert('pidof')
|
i_o.assert_exe_exists('pidof')
|
||||||
|
|
||||||
local mk_smart = function(y)
|
local mk_smart = function(y)
|
||||||
local obj = common.make_text_row(point.x, y, width, 'SMART Daemon')
|
local obj = common.make_text_row(point.x, y, width, 'SMART Daemon')
|
||||||
|
@ -35,6 +35,7 @@ return function(config, main_state, common, width, point)
|
||||||
local mk_bars = function(y)
|
local mk_bars = function(y)
|
||||||
local paths = pure.map_keys('path', config.fs_paths)
|
local paths = pure.map_keys('path', config.fs_paths)
|
||||||
local names = pure.map_keys('name', config.fs_paths)
|
local names = pure.map_keys('name', config.fs_paths)
|
||||||
|
impure.each(i_o.assert_file_exists, paths)
|
||||||
local CONKY_CMDS = pure.map(
|
local CONKY_CMDS = pure.map(
|
||||||
pure.partial(string.format, '${fs_used_perc %s}', true),
|
pure.partial(string.format, '${fs_used_perc %s}', true),
|
||||||
paths
|
paths
|
|
@ -14,7 +14,8 @@ return function(update_freq, config, common, width, point)
|
||||||
-----------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
-- nvidia state
|
-- nvidia state
|
||||||
|
|
||||||
i_o.exe_assert(NVIDIA_EXE)
|
i_o.assert_exe_exists(NVIDIA_EXE)
|
||||||
|
i_o.assert_file_exists(config.dev_power)
|
||||||
|
|
||||||
-- vars to process the nv settings glob
|
-- vars to process the nv settings glob
|
||||||
--
|
--
|
||||||
|
@ -38,8 +39,6 @@ return function(update_freq, config, common, width, point)
|
||||||
'(%d+),(%d+)\n'..
|
'(%d+),(%d+)\n'..
|
||||||
'graphics=(%d+), memory=%d+, video=(%d+), PCIe=%d+\n'
|
'graphics=(%d+), memory=%d+, video=(%d+), PCIe=%d+\n'
|
||||||
|
|
||||||
local GPU_BUS_CTRL = '/sys/bus/pci/devices/0000:01:00.0/power/control'
|
|
||||||
|
|
||||||
local mod_state = {
|
local mod_state = {
|
||||||
error = false,
|
error = false,
|
||||||
used_memory = 0,
|
used_memory = 0,
|
||||||
|
@ -52,7 +51,7 @@ return function(update_freq, config, common, width, point)
|
||||||
}
|
}
|
||||||
|
|
||||||
local update_state = function()
|
local update_state = function()
|
||||||
if i_o.read_file(GPU_BUS_CTRL, nil, '*l') == 'on' then
|
if i_o.read_file(config.dev_power, nil, '*l') == 'on' then
|
||||||
local nvidia_settings_glob = i_o.execute_cmd(NV_QUERY)
|
local nvidia_settings_glob = i_o.execute_cmd(NV_QUERY)
|
||||||
if nvidia_settings_glob == '' then
|
if nvidia_settings_glob == '' then
|
||||||
mod_state.error = 'Error'
|
mod_state.error = 'Error'
|
|
@ -39,13 +39,11 @@ return function(update_freq, config, main_state, common, width, point)
|
||||||
if math.fmod(ncores, config.core_rows) == 0 then
|
if math.fmod(ncores, config.core_rows) == 0 then
|
||||||
show_cores = true
|
show_cores = true
|
||||||
else
|
else
|
||||||
print(
|
i_o.warnf(
|
||||||
string.format(
|
'could not evenly distribute %i cores over %i rows; disabling',
|
||||||
'WARNING: could not evenly distribute %i cores over %i rows',
|
|
||||||
ncores,
|
ncores,
|
||||||
config.core_rows
|
config.core_rows
|
||||||
)
|
)
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
local format = require 'format'
|
local format = require 'format'
|
||||||
local pure = require 'pure'
|
local pure = require 'pure'
|
||||||
local sys = require 'sys'
|
local sys = require 'sys'
|
||||||
|
local i_o = require 'i_o'
|
||||||
|
local impure = require 'impure'
|
||||||
|
|
||||||
return function(update_freq, config, common, width, point)
|
return function(update_freq, config, common, width, point)
|
||||||
local PLOT_SEC_BREAK = 20
|
local PLOT_SEC_BREAK = 20
|
||||||
|
@ -9,6 +11,8 @@ return function(update_freq, config, common, width, point)
|
||||||
local mod_state = {read = 0, write = 0}
|
local mod_state = {read = 0, write = 0}
|
||||||
local device_paths = sys.get_disk_paths(config.devices)
|
local device_paths = sys.get_disk_paths(config.devices)
|
||||||
|
|
||||||
|
impure.each(i_o.assert_file_exists, device_paths)
|
||||||
|
|
||||||
local update_state = function()
|
local update_state = function()
|
||||||
mod_state.read, mod_state.write = sys.get_total_disk_io(device_paths)
|
mod_state.read, mod_state.write = sys.get_total_disk_io(device_paths)
|
||||||
end
|
end
|
|
@ -0,0 +1,259 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local err = require 'err'
|
||||||
|
|
||||||
|
local __math_floor = math.floor
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- zippy functions
|
||||||
|
|
||||||
|
-- TODO generalize to arbitrary number of sequences
|
||||||
|
M.zip_with = function(f, seq1, seq2)
|
||||||
|
local r = {}
|
||||||
|
for i = 1, #seq1 do
|
||||||
|
r[i] = f(seq1[i], seq2[i])
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
M.zip = function(...)
|
||||||
|
local seqs = {...}
|
||||||
|
local imax = math.min(table.unpack(M.map(function(t) return #t end, seqs)))
|
||||||
|
local jmax = #seqs
|
||||||
|
local r = {}
|
||||||
|
for i = 1, imax do
|
||||||
|
r[i] = {}
|
||||||
|
for j = 1, jmax do
|
||||||
|
r[i][j] = seqs[j][i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
M.unzip = function(seqs)
|
||||||
|
return M.zip(table.unpack(seqs))
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- reductions
|
||||||
|
|
||||||
|
M.reduce = function(f, init, seq)
|
||||||
|
if seq == nil then
|
||||||
|
return init
|
||||||
|
else
|
||||||
|
local r = init
|
||||||
|
for i = 1, #seq do
|
||||||
|
r = f(r, seq[i])
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- mappy functions
|
||||||
|
|
||||||
|
M.map = function(f, seq, ...)
|
||||||
|
local r = {}
|
||||||
|
for i = 1, #seq do
|
||||||
|
r[i] = f(seq[i], ...)
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
M.map_n = function(f, n, ...)
|
||||||
|
local r = {}
|
||||||
|
for i = 1, n do
|
||||||
|
r[i] = f(i, ...)
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
M.imap = function(f, seq)
|
||||||
|
local r = {}
|
||||||
|
for i = 1, #seq do
|
||||||
|
r[i] = f(i, seq[i])
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
M.map_keys = function(key, tbls)
|
||||||
|
local r = {}
|
||||||
|
for i = 1, #tbls do
|
||||||
|
r[i] = tbls[i][key]
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
M.map_at = function(key, f, tbl)
|
||||||
|
local r = {}
|
||||||
|
for k, v in pairs(tbl) do
|
||||||
|
if k == key then
|
||||||
|
r[k] = f(v)
|
||||||
|
else
|
||||||
|
r[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- generations
|
||||||
|
|
||||||
|
M.seq = function(n, start)
|
||||||
|
start = start or 1
|
||||||
|
local r = {}
|
||||||
|
for i = 1, n do
|
||||||
|
r[i] = i + start - 1
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
M.rep = function(n, x)
|
||||||
|
local r = {}
|
||||||
|
for i = 1, n do
|
||||||
|
r[i] = x
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- random list things
|
||||||
|
|
||||||
|
M.set = function(tbl, key, value)
|
||||||
|
local r = {}
|
||||||
|
for k, v in pairs(tbl) do
|
||||||
|
if k == key then
|
||||||
|
r[k] = value
|
||||||
|
else
|
||||||
|
r[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
M.reverse = function(xs)
|
||||||
|
local j = 1
|
||||||
|
local r = {}
|
||||||
|
for i = #xs, 1, -1 do
|
||||||
|
r[j] = xs[i]
|
||||||
|
j = j + 1
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
M.filter = function(f, seq)
|
||||||
|
local r = {}
|
||||||
|
local j = 1
|
||||||
|
for i = 1, #seq do
|
||||||
|
if f(seq[i]) == true then
|
||||||
|
r[j] = seq[i]
|
||||||
|
j = j + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
M.flatten = function(xs)
|
||||||
|
local r = {}
|
||||||
|
for i = 1, #xs do
|
||||||
|
for j = 1, #xs[i] do
|
||||||
|
table.insert(r, xs[i][j])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
M.concat = function(...)
|
||||||
|
return M.flatten({...})
|
||||||
|
end
|
||||||
|
|
||||||
|
M.table_array = function(tbl)
|
||||||
|
local r = {}
|
||||||
|
for i = 1, #tbl do
|
||||||
|
r[i] = tbl[i]
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- functional functions
|
||||||
|
|
||||||
|
local get_arity = function(f, args)
|
||||||
|
local i = #args
|
||||||
|
while args[i] == true do
|
||||||
|
i = i - 1
|
||||||
|
end
|
||||||
|
if i < #args then
|
||||||
|
return table.move(args, 1, #args - i, 1, {}), #args
|
||||||
|
else
|
||||||
|
local arity = debug.getinfo(f, "u")["nparams"]
|
||||||
|
err.assert_trace(arity > #args, 'too many arguments for partial')
|
||||||
|
return args, arity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- poor man's Lisp macro :)
|
||||||
|
M.partial = function(f, ...)
|
||||||
|
local args, arity = get_arity(f, {...})
|
||||||
|
local format_args = function(fmt, n, start)
|
||||||
|
return table.concat(
|
||||||
|
M.map(function(i) return string.format(fmt, i) end, M.seq(n, start)),
|
||||||
|
','
|
||||||
|
)
|
||||||
|
end
|
||||||
|
local partial_args = format_args('args[%i]', #args, 1)
|
||||||
|
local rem_args = format_args('x%i', arity - #args, #args + 1)
|
||||||
|
local src = string.format(
|
||||||
|
'return function(%s) return f(%s,%s) end',
|
||||||
|
rem_args,
|
||||||
|
partial_args,
|
||||||
|
rem_args
|
||||||
|
)
|
||||||
|
return load(src, 'partial_apply', 't', {f = f, args = args})()
|
||||||
|
end
|
||||||
|
|
||||||
|
M.compose = function(f, ...)
|
||||||
|
if #{...} == 0 then
|
||||||
|
return f
|
||||||
|
else
|
||||||
|
local g = M.compose(...)
|
||||||
|
return function(x) return f(g(x)) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TODO is there a way to do this without nesting a zillion function calls?
|
||||||
|
M.sequence = function(...)
|
||||||
|
local fs = {...}
|
||||||
|
return function(x)
|
||||||
|
for i = 1, #fs do
|
||||||
|
fs[i](x)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.memoize = function(f)
|
||||||
|
local mem = {} -- memoizing table
|
||||||
|
setmetatable(mem, {__mode = "kv"}) -- make it weak
|
||||||
|
return function (x, ...)
|
||||||
|
local r = mem[x]
|
||||||
|
if not r then
|
||||||
|
r = f(x, ...)
|
||||||
|
mem[x] = r
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.maybe = function(def, f, x)
|
||||||
|
if x == nil then
|
||||||
|
return def
|
||||||
|
else
|
||||||
|
return f(x)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- round to whole numbers since I don't need more granularity and extra values
|
||||||
|
-- will lead to cache misses
|
||||||
|
M.round_percent = __math_floor
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,332 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local i_o = require 'i_o'
|
||||||
|
local pure = require 'pure'
|
||||||
|
|
||||||
|
local __string_match = string.match
|
||||||
|
local __string_gmatch = string.gmatch
|
||||||
|
local __string_format = string.format
|
||||||
|
local __tonumber = tonumber
|
||||||
|
|
||||||
|
local dirname = function(s)
|
||||||
|
return __string_match(s, '(.*)/name')
|
||||||
|
end
|
||||||
|
|
||||||
|
local read_micro = function(path)
|
||||||
|
return i_o.read_file(path, nil, '*n') * 0.000001
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- memory
|
||||||
|
|
||||||
|
local MEMINFO_PATH = '/proc/meminfo'
|
||||||
|
|
||||||
|
local fmt_mem_field = function(field)
|
||||||
|
return field..':%s+(%d+)'
|
||||||
|
end
|
||||||
|
|
||||||
|
local meminfo_regex = function(read_swap)
|
||||||
|
-- ASSUME the order of the meminfo file will never change, but some options
|
||||||
|
-- (like swap) might not exist
|
||||||
|
local free_fields = {
|
||||||
|
'MemFree',
|
||||||
|
'Buffers',
|
||||||
|
'Cached'
|
||||||
|
}
|
||||||
|
local swap_field = 'SwapFree'
|
||||||
|
local slab_fields = {
|
||||||
|
'Shmem',
|
||||||
|
'SReclaimable'
|
||||||
|
}
|
||||||
|
local all_fields = read_swap == true
|
||||||
|
and {free_fields, {swap_field}, slab_fields}
|
||||||
|
or {free_fields, slab_fields}
|
||||||
|
local patterns = pure.map(fmt_mem_field, pure.flatten(all_fields))
|
||||||
|
return table.concat(patterns, '.+\n')
|
||||||
|
end
|
||||||
|
|
||||||
|
M.meminfo_updater_swap = function(mem_state, swap_state)
|
||||||
|
local regex = meminfo_regex(true)
|
||||||
|
return function()
|
||||||
|
mem_state.memfree,
|
||||||
|
mem_state.buffers,
|
||||||
|
mem_state.cached,
|
||||||
|
swap_state.free,
|
||||||
|
mem_state.shmem,
|
||||||
|
mem_state.sreclaimable
|
||||||
|
= __string_match(i_o.read_file(MEMINFO_PATH), regex)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.meminfo_updater_noswap = function(mem_state)
|
||||||
|
local regex = meminfo_regex(false)
|
||||||
|
return function()
|
||||||
|
mem_state.memfree,
|
||||||
|
mem_state.buffers,
|
||||||
|
mem_state.cached,
|
||||||
|
mem_state.shmem,
|
||||||
|
mem_state.sreclaimable
|
||||||
|
= __string_match(i_o.read_file(MEMINFO_PATH), regex)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.meminfo_field_reader = function(field)
|
||||||
|
local pattern = fmt_mem_field(field)
|
||||||
|
return function()
|
||||||
|
return tonumber(i_o.read_file(MEMINFO_PATH, pattern))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- intel powercap
|
||||||
|
|
||||||
|
local SYSFS_RAPL = '/sys/class/powercap'
|
||||||
|
|
||||||
|
M.intel_powercap_reader = function(dev)
|
||||||
|
local uj = __string_format('%s/%s/energy_uj', SYSFS_RAPL, dev)
|
||||||
|
i_o.assert_file_exists(uj)
|
||||||
|
return function()
|
||||||
|
return read_micro(uj)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- battery
|
||||||
|
|
||||||
|
local SYSFS_POWER = '/sys/class/power_supply'
|
||||||
|
|
||||||
|
local format_power_path = function(battery, property)
|
||||||
|
local p = __string_format('%s/%s/%s', SYSFS_POWER, battery, property)
|
||||||
|
i_o.assert_file_exists(p)
|
||||||
|
return p
|
||||||
|
end
|
||||||
|
|
||||||
|
M.battery_power_reader = function(battery)
|
||||||
|
local current = format_power_path(battery, 'current_now')
|
||||||
|
local voltage = format_power_path(battery, 'voltage_now')
|
||||||
|
return function()
|
||||||
|
return read_micro(current) * read_micro(voltage)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.battery_status_reader = function(battery)
|
||||||
|
local status = format_power_path(battery, 'status')
|
||||||
|
return function()
|
||||||
|
return i_o.read_file(status, nil, '*l') ~= 'Discharging'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- disk io
|
||||||
|
|
||||||
|
M.get_disk_paths = function(devs)
|
||||||
|
return pure.map(pure.partial(string.format, '/sys/block/%s/stat', true), devs)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- fields 3 and 7 (sectors read and written)
|
||||||
|
local RW_REGEX = '%s+%d+%s+%d+%s+(%d+)%s+%d+%s+%d+%s+%d+%s+(%d+)'
|
||||||
|
|
||||||
|
-- the sector size of any block device in linux is 512 bytes
|
||||||
|
-- see https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/types.h?id=v4.4-rc6#n121
|
||||||
|
local BLOCK_SIZE_BYTES = 512
|
||||||
|
|
||||||
|
M.get_disk_io = function(path)
|
||||||
|
local r, w = __string_match(i_o.read_file(path), RW_REGEX)
|
||||||
|
return __tonumber(r) * BLOCK_SIZE_BYTES, __tonumber(w) * BLOCK_SIZE_BYTES
|
||||||
|
end
|
||||||
|
|
||||||
|
M.get_total_disk_io = function(paths)
|
||||||
|
local r = 0
|
||||||
|
local w = 0
|
||||||
|
for i = 1, #paths do
|
||||||
|
local _r, _w = M.get_disk_io(paths[i])
|
||||||
|
r = r + _r
|
||||||
|
w = w + _w
|
||||||
|
end
|
||||||
|
return r, w
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- network
|
||||||
|
|
||||||
|
-- ASSUME realpath exists (part of coreutils)
|
||||||
|
|
||||||
|
local get_interfaces = function()
|
||||||
|
local s = i_o.execute_cmd('realpath /sys/class/net/* | grep -v virtual')
|
||||||
|
local interfaces = {}
|
||||||
|
for iface in __string_gmatch(s, '/([^/\n]+)\n') do
|
||||||
|
interfaces[#interfaces + 1] = iface
|
||||||
|
end
|
||||||
|
return interfaces
|
||||||
|
end
|
||||||
|
|
||||||
|
M.get_net_interface_paths = function()
|
||||||
|
local is = get_interfaces()
|
||||||
|
return pure.map(
|
||||||
|
function(s)
|
||||||
|
local dir = string.format('/sys/class/net/%s/statistics/', s)
|
||||||
|
return {rx = dir..'rx_bytes', tx = dir..'tx_bytes'}
|
||||||
|
end,
|
||||||
|
is
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- cpu
|
||||||
|
|
||||||
|
-- ASSUME nproc and lscpu will always be available
|
||||||
|
|
||||||
|
M.get_core_number = function()
|
||||||
|
return tonumber(i_o.read_file('/proc/cpuinfo', 'cpu cores%s+:%s(%d+)'))
|
||||||
|
end
|
||||||
|
|
||||||
|
M.get_cpu_number = function()
|
||||||
|
return tonumber(i_o.execute_cmd('nproc', nil, '*n'))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TODO what if this fails?
|
||||||
|
local get_coretemp_dir = function()
|
||||||
|
i_o.assert_exe_exists('grep')
|
||||||
|
local s = i_o.execute_cmd('grep -l \'^coretemp$\' /sys/class/hwmon/*/name')
|
||||||
|
return dirname(s)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- map cores to integer values starting at 1; this is necessary since some cpus
|
||||||
|
-- don't report their core id's as a sequence of integers starting at 0
|
||||||
|
local get_core_id_mapper = function()
|
||||||
|
local s = i_o.execute_cmd('lscpu -p=CORE | tail -n+5 | sort | uniq')
|
||||||
|
local m = {}
|
||||||
|
local i = 1
|
||||||
|
for core_id in string.gmatch(s, '(%d+)') do
|
||||||
|
m[tonumber(core_id)] = i
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
return m
|
||||||
|
end
|
||||||
|
|
||||||
|
local get_core_mappings = function()
|
||||||
|
local ncpus = M.get_cpu_number()
|
||||||
|
local ncores = M.get_core_number()
|
||||||
|
local nthreads = ncpus / ncores
|
||||||
|
local core_id_mapper = get_core_id_mapper()
|
||||||
|
local conky_thread_ids = pure.rep(ncores, nthreads)
|
||||||
|
local core_mappings = {}
|
||||||
|
local s = i_o.execute_cmd('lscpu -p=cpu,CORE | tail -n+5')
|
||||||
|
for cpu_id, core_id in string.gmatch(s, '(%d+),(%d+)') do
|
||||||
|
local conky_core_id = core_id_mapper[tonumber(core_id)]
|
||||||
|
local conky_cpu_id = tonumber(cpu_id) + 1
|
||||||
|
core_mappings[conky_cpu_id] = {
|
||||||
|
conky_core_id = conky_core_id,
|
||||||
|
conky_thread_id = conky_thread_ids[conky_core_id],
|
||||||
|
}
|
||||||
|
conky_thread_ids[conky_core_id] = conky_thread_ids[conky_core_id] - 1
|
||||||
|
end
|
||||||
|
return core_mappings
|
||||||
|
end
|
||||||
|
|
||||||
|
M.get_coretemp_paths = function()
|
||||||
|
local d = get_coretemp_dir()
|
||||||
|
i_o.assert_exe_exists('grep')
|
||||||
|
local s = i_o.execute_cmd(string.format('grep Core %s/temp*_label', d))
|
||||||
|
local ps = {}
|
||||||
|
local core_id_mapper = get_core_id_mapper()
|
||||||
|
for temp_name, core_id in string.gmatch(s, '/([^/\n]+)_label:Core (%d+)\n') do
|
||||||
|
ps[core_id_mapper[tonumber(core_id)]] = string.format('%s/%s_input', d, temp_name)
|
||||||
|
end
|
||||||
|
return ps
|
||||||
|
end
|
||||||
|
|
||||||
|
M.read_freq = function()
|
||||||
|
-- NOTE: Using the builtin conky functions for getting cpu freq seems to make
|
||||||
|
-- the entire loop jittery due to high variance latency. Querying
|
||||||
|
-- scaling_cur_freq in sysfs seems to do the same thing. It appears lscpu
|
||||||
|
-- (which queries /proc/cpuinfo) is much faster and doesn't have this jittery
|
||||||
|
-- problem.
|
||||||
|
local c = i_o.execute_cmd('lscpu -p=MHZ')
|
||||||
|
local f = 0
|
||||||
|
local n = 0
|
||||||
|
for s in __string_gmatch(c, '(%d+%.%d+)') do
|
||||||
|
f = f + __tonumber(s)
|
||||||
|
n = n + 1
|
||||||
|
end
|
||||||
|
return __string_format('%.0f Mhz', f / n)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.get_hwp_paths = function()
|
||||||
|
return pure.map_n(
|
||||||
|
function(i)
|
||||||
|
return '/sys/devices/system/cpu/cpu'
|
||||||
|
.. (i - 1)
|
||||||
|
.. '/cpufreq/energy_performance_preference'
|
||||||
|
end,
|
||||||
|
M.get_cpu_number()
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.read_hwp = function(hwp_paths)
|
||||||
|
-- read HWP of first cpu, then test all others to see if they match
|
||||||
|
local hwp_pref = i_o.read_file(hwp_paths[1], nil, "*l")
|
||||||
|
local mixed = false
|
||||||
|
local i = 2
|
||||||
|
|
||||||
|
while not mixed and i <= #hwp_paths do
|
||||||
|
if hwp_pref ~= i_o.read_file(hwp_paths[i], nil, '*l') then
|
||||||
|
mixed = true
|
||||||
|
end
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if mixed then
|
||||||
|
return 'Mixed'
|
||||||
|
elseif hwp_pref == 'power' then
|
||||||
|
return 'Power'
|
||||||
|
elseif hwp_pref == 'balance_power' then
|
||||||
|
return 'Bal. Power'
|
||||||
|
elseif hwp_pref == 'balance_performance' then
|
||||||
|
return 'Bal. Performance'
|
||||||
|
elseif hwp_pref == 'performance' then
|
||||||
|
return 'Performance'
|
||||||
|
elseif hwp_pref == 'default' then
|
||||||
|
return 'Default'
|
||||||
|
else
|
||||||
|
return 'Unknown'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.init_cpu_loads = function()
|
||||||
|
local m = get_core_mappings()
|
||||||
|
local cpu_loads = {}
|
||||||
|
for cpu_id, core in pairs(m) do
|
||||||
|
cpu_loads[cpu_id] = {
|
||||||
|
active_prev = 0,
|
||||||
|
total_prev = 0,
|
||||||
|
percent_active = 0,
|
||||||
|
conky_core_id = core.conky_core_id,
|
||||||
|
conky_thread_id = core.conky_thread_id,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
return cpu_loads
|
||||||
|
end
|
||||||
|
|
||||||
|
M.read_cpu_loads = function(cpu_loads)
|
||||||
|
local ncpus = #cpu_loads
|
||||||
|
local i = 1
|
||||||
|
local iter = io.lines('/proc/stat')
|
||||||
|
iter() -- ignore first line
|
||||||
|
for ln in iter do
|
||||||
|
if i > ncpus then break end
|
||||||
|
local user, system, idle = __string_match(ln, '(%d+) %d+ (%d+) (%d+)', 5)
|
||||||
|
local active = user + system
|
||||||
|
local total = active + idle
|
||||||
|
local c = cpu_loads[i]
|
||||||
|
if total > c.total_prev then -- guard against 1/0 errors
|
||||||
|
c.percent_active = (active - c.active_prev) / (total - c.total_prev)
|
||||||
|
c.active_prev = active
|
||||||
|
c.total_prev = total
|
||||||
|
end
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
return cpu_loads
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,29 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local geom = require 'geom'
|
||||||
|
local circle = require 'circle'
|
||||||
|
local path = require 'path'
|
||||||
|
local shape = require 'shape'
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- pure
|
||||||
|
|
||||||
|
M.make_shape = function(arc, thickness, pattern)
|
||||||
|
return shape.shape(
|
||||||
|
path.create_arc_from_geom(geom.CR_DUMMY, arc),
|
||||||
|
circle.make_source(pattern, arc.center, arc.radius, thickness)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make = function(arc, config)
|
||||||
|
return circle._make(arc, config, M.make_shape)
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- impure
|
||||||
|
|
||||||
|
M.config = circle.config
|
||||||
|
|
||||||
|
M.draw = circle.draw
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,55 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local geom = require 'geom'
|
||||||
|
local err = require 'err'
|
||||||
|
local source = require 'source'
|
||||||
|
local style = require 'style'
|
||||||
|
local path = require 'path'
|
||||||
|
local shape = require 'shape'
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- pure
|
||||||
|
|
||||||
|
M.make_pattern_radii = function(r, t)
|
||||||
|
return r - t * 0.5, r + t * 0.5
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make_source = function(pattern, center, radius, thickness)
|
||||||
|
local r1, r2 = M.make_pattern_radii(radius, thickness)
|
||||||
|
return source.radial_pattern(pattern, center, r1, r2)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make_shape = function(circle, thickness, pattern)
|
||||||
|
return shape.shape(
|
||||||
|
path.create_circle_from_geom(geom.CR_DUMMY, circle),
|
||||||
|
M.make_source(pattern, circle.center, circle.radius, thickness)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M._make = function(_geom, config, make_shape_fun)
|
||||||
|
return shape.styled_shape(
|
||||||
|
config.style,
|
||||||
|
make_shape_fun,
|
||||||
|
_geom,
|
||||||
|
config.style.thickness,
|
||||||
|
config.pattern
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make = function(circle, config)
|
||||||
|
return M._make(circle, config, M.make_shape)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.config = function(_style, pattern)
|
||||||
|
return err.safe_table({style = _style, pattern = pattern})
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- impure
|
||||||
|
|
||||||
|
M.draw = function(obj, cr)
|
||||||
|
style.set_line_style(obj.style, cr)
|
||||||
|
shape.draw_shape(obj.shape, cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,60 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local arc = require 'arc'
|
||||||
|
local dial = require 'dial'
|
||||||
|
local pure = require 'pure'
|
||||||
|
local impure = require 'impure'
|
||||||
|
local style = require 'style'
|
||||||
|
local shape = require 'shape'
|
||||||
|
local dynamic = require 'dynamic'
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- pure
|
||||||
|
|
||||||
|
M.make = function(_arc, bg_config, fg_threshold_config, inner_radius, num_dials)
|
||||||
|
local t = bg_config.style.thickness
|
||||||
|
|
||||||
|
local spacing = (t * num_dials - _arc.radius + inner_radius)
|
||||||
|
/ (1 - num_dials)
|
||||||
|
assert(spacing >= 0, "ERROR: compound dial spacing is negative")
|
||||||
|
local arcs = pure.map_n(
|
||||||
|
function(i)
|
||||||
|
return pure.set(
|
||||||
|
_arc,
|
||||||
|
"radius",
|
||||||
|
inner_radius + t * 0.5 + (i - 1) * (spacing + t)
|
||||||
|
)
|
||||||
|
end,
|
||||||
|
num_dials
|
||||||
|
)
|
||||||
|
local setters = pure.map(dial.make_setter, arcs, t, fg_threshold_config)
|
||||||
|
|
||||||
|
return dynamic.compound(
|
||||||
|
{
|
||||||
|
shapes = pure.map(arc.make_shape, arcs, t, bg_config.pattern),
|
||||||
|
style = bg_config.style,
|
||||||
|
},
|
||||||
|
setters,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- impure
|
||||||
|
|
||||||
|
M.set = function(obj, i, percent)
|
||||||
|
obj.var[i] = obj.setters[i](percent)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.draw_static = function(obj, cr)
|
||||||
|
local static = obj.static
|
||||||
|
style.set_line_style(static.style, cr)
|
||||||
|
impure.each(shape.draw_shape, static.shapes, cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.draw_dynamic = function(obj, cr)
|
||||||
|
style.set_line_style(obj.static.style, cr)
|
||||||
|
impure.each(shape.draw_shape, obj.var, cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,75 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local geom = require 'geom'
|
||||||
|
local pure = require 'pure'
|
||||||
|
local arc = require 'arc'
|
||||||
|
local circle = require 'circle'
|
||||||
|
local source = require 'source'
|
||||||
|
local style = require 'style'
|
||||||
|
local path = require 'path'
|
||||||
|
local shape = require 'shape'
|
||||||
|
local dynamic = require 'dynamic'
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- pure
|
||||||
|
|
||||||
|
M.make_setter = function(_arc, thickness, threshold_config)
|
||||||
|
local c = _arc.center
|
||||||
|
local r = _arc.radius
|
||||||
|
local t1 = _arc.theta0
|
||||||
|
local t2 = _arc.theta1
|
||||||
|
local r1, r2 = circle.make_pattern_radii(r, thickness)
|
||||||
|
local source_chooser = source.radial_pattern_chooser(
|
||||||
|
threshold_config.low_pattern,
|
||||||
|
threshold_config.high_pattern,
|
||||||
|
threshold_config.threshold,
|
||||||
|
c,
|
||||||
|
r1,
|
||||||
|
r2
|
||||||
|
)
|
||||||
|
local f = pure.memoize(
|
||||||
|
function(percent)
|
||||||
|
return shape.shape(
|
||||||
|
path.create_arc(
|
||||||
|
geom.CR_DUMMY,
|
||||||
|
c.x,
|
||||||
|
c.y,
|
||||||
|
r,
|
||||||
|
t1,
|
||||||
|
t1 + (percent / 100) * (t2 - t1)
|
||||||
|
),
|
||||||
|
source_chooser(percent)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
return function(percent)
|
||||||
|
return f(pure.round_percent(percent))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make = function(_arc, bg_config, fg_threshold_config)
|
||||||
|
local setter = M.make_setter(
|
||||||
|
_arc,
|
||||||
|
bg_config.style.thickness,
|
||||||
|
fg_threshold_config
|
||||||
|
)
|
||||||
|
return dynamic.single(arc.make(_arc, bg_config), setter, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- impure
|
||||||
|
|
||||||
|
M.set = function(obj, percent)
|
||||||
|
obj.var = obj.setter(percent)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.draw_static = function(obj, cr)
|
||||||
|
arc.draw(obj.static, cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.draw_dynamic = function(obj, cr)
|
||||||
|
style.set_line_style(obj.static.style, cr)
|
||||||
|
shape.draw_shape(obj.var, cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,45 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local err = require 'err'
|
||||||
|
local pure = require 'pure'
|
||||||
|
|
||||||
|
-- TODO generalize these
|
||||||
|
-- types of dynamic mappers
|
||||||
|
-- 1 -> 1
|
||||||
|
-- 1 -> 1 with recursion
|
||||||
|
-- 1 -> many
|
||||||
|
-- 1 -> many (nested in many)
|
||||||
|
-- many -> many
|
||||||
|
|
||||||
|
M.single = function(static, setter, init)
|
||||||
|
return err.safe_table(
|
||||||
|
{
|
||||||
|
static = static,
|
||||||
|
setter = setter,
|
||||||
|
var = setter(init)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TODO think of a better more mathy name
|
||||||
|
M.multi = function(static, setter, inits)
|
||||||
|
return err.safe_table(
|
||||||
|
{
|
||||||
|
static = static,
|
||||||
|
setter = setter,
|
||||||
|
var = pure.map(setter, inits)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.compound = function(static, setters, init)
|
||||||
|
return err.safe_table(
|
||||||
|
{
|
||||||
|
static = static,
|
||||||
|
setters = setters,
|
||||||
|
var = pure.map(function(setter) return setter(init) end, setters),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,60 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local err = require 'err'
|
||||||
|
|
||||||
|
-- TODO this is weird to have here
|
||||||
|
-- dummy drawing surface
|
||||||
|
local cs = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1366, 768)
|
||||||
|
M.CR_DUMMY = cairo_create(cs)
|
||||||
|
cairo_surface_destroy(cs)
|
||||||
|
cs = nil
|
||||||
|
|
||||||
|
M.make_point = function(x, y)
|
||||||
|
return err.safe_table({x = x, y = y})
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make_box_at_point = function(p, w, h)
|
||||||
|
return err.safe_table(
|
||||||
|
{
|
||||||
|
corner = p,
|
||||||
|
width = w,
|
||||||
|
height = h,
|
||||||
|
-- TODO these might be unnecessary
|
||||||
|
right_x = w + p.x,
|
||||||
|
bottom_y = h + p.y
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make_box = function(x, y, w, h)
|
||||||
|
return M.make_box_at_point(M.make_point(x, y), w, h)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make_arc_at_point = function(p, r, t1, t2)
|
||||||
|
return err.safe_table(
|
||||||
|
{
|
||||||
|
center = p,
|
||||||
|
radius = r,
|
||||||
|
theta0 = t1,
|
||||||
|
theta1 = t2,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make_arc = function(x, y, r, t0, t1)
|
||||||
|
return M.make_arc_at_point(M.make_point(x, y), r, t0, t1)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make_circle_at_point = function(p, r)
|
||||||
|
return err.safe_table({center = p, radius = r})
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make_circle = function(x, y, r)
|
||||||
|
return M.make_circle_at_point(M.make_point(x, y), r)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make_line = function(p1, p2)
|
||||||
|
return err.safe_table({p1 = p1, p2 = p2})
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,31 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local __imlib_load_image = imlib_load_image
|
||||||
|
local __imlib_context_set_image = imlib_context_set_image
|
||||||
|
local __imlib_render_image_on_drawable = imlib_render_image_on_drawable
|
||||||
|
local __imlib_free_image = imlib_free_image
|
||||||
|
local __imlib_image_get_width = imlib_image_get_width
|
||||||
|
local __imlib_image_get_height = imlib_image_get_height
|
||||||
|
|
||||||
|
local set = function(obj, path)
|
||||||
|
local img = __imlib_load_image(path)
|
||||||
|
__imlib_context_set_image(img)
|
||||||
|
|
||||||
|
obj.width = __imlib_image_get_width()
|
||||||
|
obj.height = __imlib_image_get_height()
|
||||||
|
obj.path = path
|
||||||
|
|
||||||
|
__imlib_free_image()
|
||||||
|
end
|
||||||
|
|
||||||
|
local draw = function(obj)
|
||||||
|
local img = __imlib_load_image(obj.path)
|
||||||
|
__imlib_context_set_image(img)
|
||||||
|
__imlib_render_image_on_drawable(obj.x, obj.y)
|
||||||
|
__imlib_free_image()
|
||||||
|
end
|
||||||
|
|
||||||
|
M.set = set
|
||||||
|
M.draw = draw
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,31 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local __imlib_load_image = imlib_load_image
|
||||||
|
local __imlib_context_set_image = imlib_context_set_image
|
||||||
|
local __imlib_render_image_on_drawable_at_size = imlib_render_image_on_drawable_at_size
|
||||||
|
local __imlib_free_image = imlib_free_image
|
||||||
|
local __imlib_image_get_width = imlib_image_get_width
|
||||||
|
local __imlib_image_get_height = imlib_image_get_height
|
||||||
|
|
||||||
|
local set = function(obj, path)
|
||||||
|
local img = __imlib_load_image(path)
|
||||||
|
__imlib_context_set_image(img)
|
||||||
|
|
||||||
|
obj.img_width = __imlib_image_get_width()
|
||||||
|
obj.img_height = __imlib_image_get_height()
|
||||||
|
obj.path = path
|
||||||
|
|
||||||
|
__imlib_free_image()
|
||||||
|
end
|
||||||
|
|
||||||
|
local draw = function(obj)
|
||||||
|
local img = __imlib_load_image(obj.path)
|
||||||
|
__imlib_context_set_image(img)
|
||||||
|
__imlib_render_image_on_drawable_at_size(obj.x, obj.y, obj.width, obj.height)
|
||||||
|
__imlib_free_image()
|
||||||
|
end
|
||||||
|
|
||||||
|
M.set = set
|
||||||
|
M.draw = draw
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,61 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local geom = require 'geom'
|
||||||
|
local line = require 'line'
|
||||||
|
local source = require 'source'
|
||||||
|
local pure = require 'pure'
|
||||||
|
local path = require 'path'
|
||||||
|
local shape = require 'shape'
|
||||||
|
local dynamic = require 'dynamic'
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- pure
|
||||||
|
|
||||||
|
M.make_setter = function(_line, config, threshold_config)
|
||||||
|
local p1 = _line.p1
|
||||||
|
local p2 = _line.p2
|
||||||
|
local _p1, _p2 = line.get_wide_pattern_points(_line, config.style.thickness, config.is_wide_pattern)
|
||||||
|
|
||||||
|
local source_chooser = source.linear_pattern_chooser(
|
||||||
|
threshold_config.low_pattern,
|
||||||
|
threshold_config.high_pattern,
|
||||||
|
threshold_config.threshold,
|
||||||
|
_p1,
|
||||||
|
_p2
|
||||||
|
)
|
||||||
|
local f = pure.memoize(
|
||||||
|
function(percent)
|
||||||
|
local frac = percent / 100
|
||||||
|
local mp = geom.make_point(
|
||||||
|
(p2.x - p1.x) * frac + p1.x,
|
||||||
|
(p2.y - p1.y) * frac + p1.y
|
||||||
|
)
|
||||||
|
return shape.shape(
|
||||||
|
path.create_line(geom.CR_DUMMY, p1, mp),
|
||||||
|
source_chooser(percent)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
return function(percent)
|
||||||
|
return f(pure.round_percent(percent))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make = function(p1, p2, bg_config, fg_threshold_config)
|
||||||
|
local setter = M.make_setter(geom.make_line(p1, p2), bg_config, fg_threshold_config)
|
||||||
|
return dynamic.single(line.make(p1, p2, bg_config), setter, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- impure
|
||||||
|
|
||||||
|
M.set = function(obj, percent)
|
||||||
|
obj.var = obj.setter(percent)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.draw = function(obj, cr)
|
||||||
|
line.draw(obj.static, cr)
|
||||||
|
shape.draw_shape(obj.var, cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,67 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local geom = require 'geom'
|
||||||
|
local bar = require 'bar'
|
||||||
|
local dynamic = require 'dynamic'
|
||||||
|
local line = require 'line'
|
||||||
|
local style = require 'style'
|
||||||
|
local pure = require 'pure'
|
||||||
|
local impure = require 'impure'
|
||||||
|
local shape = require 'shape'
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- pure
|
||||||
|
|
||||||
|
-- TODO make this handle vertical bars
|
||||||
|
M.make = function(point, length, bg_config, fg_threshold_config, spacing,
|
||||||
|
num_bars, is_vertical)
|
||||||
|
local lines = pure.map_n(
|
||||||
|
function(i)
|
||||||
|
local y = (i - 1) * spacing + point.y
|
||||||
|
local p1 = geom.make_point(point.x, y)
|
||||||
|
local p2 = geom.make_point(point.x + length, y)
|
||||||
|
return geom.make_line(p1, p2)
|
||||||
|
end,
|
||||||
|
num_bars
|
||||||
|
)
|
||||||
|
|
||||||
|
local setters = pure.map(
|
||||||
|
bar.make_setter,
|
||||||
|
lines,
|
||||||
|
bg_config,
|
||||||
|
fg_threshold_config
|
||||||
|
)
|
||||||
|
|
||||||
|
local bs = bg_config.style
|
||||||
|
local static = {
|
||||||
|
shapes = pure.map(
|
||||||
|
line.make_shape,
|
||||||
|
lines,
|
||||||
|
bg_config.pattern,
|
||||||
|
bs.thickness,
|
||||||
|
bg_config.is_wide_pattern
|
||||||
|
),
|
||||||
|
style = bs,
|
||||||
|
}
|
||||||
|
return dynamic.compound(static, setters, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- pure
|
||||||
|
|
||||||
|
M.set = function(obj, i, percent)
|
||||||
|
obj.var[i] = obj.setters[i](percent)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.draw_static = function(obj, cr)
|
||||||
|
local static = obj.static
|
||||||
|
style.set_line_style(static.style, cr)
|
||||||
|
impure.each(shape.draw_shape, static.shapes, cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.draw_dynamic = function(obj, cr)
|
||||||
|
style.set_line_style(obj.static.style, cr)
|
||||||
|
impure.each(shape.draw_shape, obj.var, cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,74 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local geom = require 'geom'
|
||||||
|
local err = require 'err'
|
||||||
|
local source = require 'source'
|
||||||
|
local style = require 'style'
|
||||||
|
local path = require 'path'
|
||||||
|
local shape = require 'shape'
|
||||||
|
|
||||||
|
local __math_atan2 = math.atan2
|
||||||
|
local __math_sin = math.sin
|
||||||
|
local __math_cos = math.cos
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- pure
|
||||||
|
|
||||||
|
M.config = function(_style, pattern, is_wide_pattern)
|
||||||
|
return err.safe_table(
|
||||||
|
{
|
||||||
|
style = _style,
|
||||||
|
pattern = pattern,
|
||||||
|
is_wide_pattern = is_wide_pattern or false,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.get_wide_pattern_points = function(line, thickness, is_wide)
|
||||||
|
local p1 = line.p1
|
||||||
|
local p2 = line.p2
|
||||||
|
if is_wide then
|
||||||
|
local theta = __math_atan2(p2.y - p1.y, p2.x - p1.x)
|
||||||
|
local delta_x = 0.5 * thickness * __math_sin(theta) --and yes, these are actually flipped
|
||||||
|
local delta_y = 0.5 * thickness * __math_cos(theta)
|
||||||
|
local _p1 = geom.make_point(p1.x + delta_x, p1.y + delta_y)
|
||||||
|
local _p2 = geom.make_point(p1.x - delta_x, p1.y - delta_y)
|
||||||
|
return _p1, _p2
|
||||||
|
else
|
||||||
|
return p1, p2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make_source = function(line, thickness, pattern, is_wide)
|
||||||
|
local p1, p2 = M.get_wide_pattern_points(line, thickness, is_wide)
|
||||||
|
return source.linear_pattern(pattern, p1, p2)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make_shape = function(line, pattern, thickness, is_wide)
|
||||||
|
return shape.shape(
|
||||||
|
path.create_line_from_geom(geom.CR_DUMMY, line),
|
||||||
|
M.make_source(line, thickness, pattern, is_wide)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make = function(line, config)
|
||||||
|
local _style = config.style
|
||||||
|
return shape.styled_shape(
|
||||||
|
_style,
|
||||||
|
M.make_shape,
|
||||||
|
line,
|
||||||
|
config.pattern,
|
||||||
|
_style.thickness,
|
||||||
|
config.is_wide_pattern
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- impure
|
||||||
|
|
||||||
|
M.draw = function(obj, cr)
|
||||||
|
style.set_line_style(obj.style, cr)
|
||||||
|
shape.draw_shape(obj.shape, cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,73 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local impure = require 'impure'
|
||||||
|
|
||||||
|
local __cairo_new_path = cairo_new_path
|
||||||
|
local __cairo_line_to = cairo_line_to
|
||||||
|
local __cairo_copy_path = cairo_copy_path
|
||||||
|
local __cairo_close_path = cairo_close_path
|
||||||
|
local __cairo_arc = cairo_arc
|
||||||
|
local __cairo_rectangle = cairo_rectangle
|
||||||
|
local __math_rad = math.rad
|
||||||
|
|
||||||
|
local line_to = function(p, cr)
|
||||||
|
__cairo_line_to(cr, p.x, p.y)
|
||||||
|
end
|
||||||
|
|
||||||
|
local create_poly = function(cr, points)
|
||||||
|
__cairo_new_path(cr)
|
||||||
|
impure.each(line_to, points, cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
local copy_path = function(cr)
|
||||||
|
local path = __cairo_copy_path(cr)
|
||||||
|
__cairo_new_path(cr) -- clear path to keep it from reappearing
|
||||||
|
return path
|
||||||
|
end
|
||||||
|
|
||||||
|
M.create_open_poly = function(cr, points)
|
||||||
|
create_poly(cr, points)
|
||||||
|
return copy_path(cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.create_closed_poly = function(cr, points)
|
||||||
|
create_poly(cr, points)
|
||||||
|
__cairo_close_path(cr)
|
||||||
|
return copy_path(cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.create_line = function(cr, p1, p2)
|
||||||
|
return M.create_open_poly(cr, {p1, p2})
|
||||||
|
end
|
||||||
|
|
||||||
|
M.create_line_from_geom = function(cr, line)
|
||||||
|
return M.create_line(cr, line.p1, line.p2)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.create_arc = function(cr, x, y, radius, theta1, theta2)
|
||||||
|
__cairo_new_path(cr)
|
||||||
|
__cairo_arc(cr, x, y, radius, __math_rad(theta1), __math_rad(theta2))
|
||||||
|
return copy_path(cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.create_arc_from_geom = function(cr, arc)
|
||||||
|
local c = arc.center
|
||||||
|
return M.create_arc(cr, c.x, c.y, arc.radius, arc.theta0, arc.theta1)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.create_circle = function(cr, x, y, radius)
|
||||||
|
return M.create_arc(cr, x, y, radius, 0, 360)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.create_circle_from_geom = function(cr, circle)
|
||||||
|
local c = circle.center
|
||||||
|
return M.create_circle(cr, c.x, c.y, circle.radius)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.create_rect = function(cr, x, y, w, h)
|
||||||
|
__cairo_new_path(cr)
|
||||||
|
__cairo_rectangle(cr, x, y, w, h)
|
||||||
|
return copy_path(cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,34 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local geom = require 'geom'
|
||||||
|
local source = require 'source'
|
||||||
|
local path = require 'path'
|
||||||
|
local style = require 'style'
|
||||||
|
local shape = require 'shape'
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- pure
|
||||||
|
|
||||||
|
local make_shape = function(box, line_pattern, fill_pattern)
|
||||||
|
local p1 = box.corner
|
||||||
|
local p2 = geom.make_point(p1.x, p1.y + box.height)
|
||||||
|
return shape.filled_shape(
|
||||||
|
path.create_rect(geom.CR_DUMMY, p1.x, p1.y, box.width, box.height),
|
||||||
|
source.linear_pattern(line_pattern, p1, p2),
|
||||||
|
source.linear_pattern(fill_pattern, p1, p2)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make = function(box, config, fill_pattern)
|
||||||
|
return shape.styled_shape(config.style, make_shape, box, config.pattern, fill_pattern)
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- impure
|
||||||
|
|
||||||
|
M.draw = function(obj, cr)
|
||||||
|
style.set_closed_poly_style(obj.style, cr)
|
||||||
|
shape.draw_filled_shape(obj.shape, cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,40 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local geom = require 'geom'
|
||||||
|
local err = require 'err'
|
||||||
|
local source = require 'source'
|
||||||
|
local style = require 'style'
|
||||||
|
local path = require 'path'
|
||||||
|
local shape = require 'shape'
|
||||||
|
|
||||||
|
M.config = function(closed_poly_style, pattern)
|
||||||
|
return err.safe_table(
|
||||||
|
{
|
||||||
|
style = closed_poly_style,
|
||||||
|
pattern = pattern,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
local make_shape = function(box, pattern)
|
||||||
|
local p = box.corner
|
||||||
|
return shape.shape(
|
||||||
|
path.create_rect(geom.CR_DUMMY, p.x, p.y, box.width, box.height),
|
||||||
|
source.linear_pattern(
|
||||||
|
pattern,
|
||||||
|
p,
|
||||||
|
geom.make_point(p.x, p.y + box.height)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make = function(box, config)
|
||||||
|
return shape.styled_shape(config.style, make_shape, box, config.pattern)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.draw = function(obj, cr)
|
||||||
|
style.set_closed_poly_style(obj.style, cr)
|
||||||
|
shape.draw_shape(obj.shape, cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,70 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local impure = require 'impure'
|
||||||
|
local err = require 'err'
|
||||||
|
|
||||||
|
local __cairo_append_path = cairo_append_path
|
||||||
|
local __cairo_fill_preserve = cairo_fill_preserve
|
||||||
|
local __cairo_set_source = cairo_set_source
|
||||||
|
local __cairo_stroke = cairo_stroke
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- pure
|
||||||
|
|
||||||
|
M.shape = function(path, source)
|
||||||
|
return {path = path, source = source}
|
||||||
|
end
|
||||||
|
|
||||||
|
M.filled_shape = function(path, line_source, fill_source)
|
||||||
|
return {path = path, line_source = line_source, fill_source = fill_source}
|
||||||
|
end
|
||||||
|
|
||||||
|
M.shapes = function(paths, source)
|
||||||
|
return {paths = paths, source = source}
|
||||||
|
end
|
||||||
|
|
||||||
|
M.styled_shape = function(style, make_shape_fun, geom, ...)
|
||||||
|
return err.safe_table(
|
||||||
|
{
|
||||||
|
style = style,
|
||||||
|
shape = make_shape_fun(geom, ...),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- impure
|
||||||
|
|
||||||
|
M.draw_path_with_source = function(path, cr, source)
|
||||||
|
__cairo_append_path(cr, path)
|
||||||
|
__cairo_set_source(cr, source)
|
||||||
|
__cairo_stroke(cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.draw_shape = function(shape, cr)
|
||||||
|
M.draw_path_with_source(shape.path, cr, shape.source)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.draw_filled_shape = function(shape, cr)
|
||||||
|
__cairo_append_path(cr, shape.path)
|
||||||
|
__cairo_set_source(cr, shape.fill_source)
|
||||||
|
__cairo_fill_preserve(cr)
|
||||||
|
__cairo_set_source(cr, shape.line_source)
|
||||||
|
__cairo_stroke(cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
local append_path = function(p, cr)
|
||||||
|
__cairo_append_path(cr, p)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.draw_paths_with_source = function(paths, cr, source)
|
||||||
|
impure.each(append_path, paths, cr)
|
||||||
|
__cairo_set_source(cr, source)
|
||||||
|
__cairo_stroke(cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.draw_shapes = function(shapes, cr)
|
||||||
|
M.draw_paths_with_source(shapes.paths, cr, shapes.source)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,117 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local err = require 'err'
|
||||||
|
|
||||||
|
local __cairo_pattern_create_rgba = cairo_pattern_create_rgba
|
||||||
|
local __cairo_pattern_create_radial = cairo_pattern_create_radial
|
||||||
|
local __cairo_pattern_create_linear = cairo_pattern_create_linear
|
||||||
|
local __cairo_pattern_add_color_stop_rgba = cairo_pattern_add_color_stop_rgba
|
||||||
|
|
||||||
|
M.threshold_config = function(low_pattern, high_pattern, threshold)
|
||||||
|
return err.safe_table(
|
||||||
|
{
|
||||||
|
low_pattern = low_pattern,
|
||||||
|
high_pattern = high_pattern,
|
||||||
|
threshold = threshold,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
local set_color_stops = function(pattern, colorstops)
|
||||||
|
for stop, color in pairs(colorstops) do
|
||||||
|
__cairo_pattern_add_color_stop_rgba(
|
||||||
|
pattern,
|
||||||
|
stop,
|
||||||
|
color.r,
|
||||||
|
color.g,
|
||||||
|
color.b,
|
||||||
|
color.a
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local _solid_color = function(color)
|
||||||
|
return __cairo_pattern_create_rgba(color.r, color.g, color.b, color.a)
|
||||||
|
end
|
||||||
|
|
||||||
|
local _linear_gradient = function(gradient, p1, p2)
|
||||||
|
local pattern = __cairo_pattern_create_linear(p1.x, p1.y, p2.x, p2.y)
|
||||||
|
set_color_stops(pattern, gradient)
|
||||||
|
return pattern
|
||||||
|
end
|
||||||
|
|
||||||
|
local _radial_gradient = function(gradient, p, r1, r2)
|
||||||
|
local pattern = __cairo_pattern_create_radial(p.x, p.y, r1, p.x, p.y, r2)
|
||||||
|
set_color_stops(pattern, gradient)
|
||||||
|
return pattern
|
||||||
|
end
|
||||||
|
|
||||||
|
local _is_gradient = function(spec)
|
||||||
|
return err.get_type(spec) == "gradient"
|
||||||
|
end
|
||||||
|
|
||||||
|
local _create_critical_function = function(limit)
|
||||||
|
if limit then
|
||||||
|
return function(n) return (n > limit) end
|
||||||
|
else
|
||||||
|
return function(_) return nil end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.solid_color = function(spec)
|
||||||
|
err.check_type(spec, "color")
|
||||||
|
return _solid_color(spec)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.linear_pattern = function(spec, p1, p2)
|
||||||
|
if _is_gradient(spec) then
|
||||||
|
return _linear_gradient(spec, p1, p2)
|
||||||
|
else
|
||||||
|
return _solid_color(spec)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.radial_pattern = function(spec, p, r1, r2)
|
||||||
|
if _is_gradient(spec) then
|
||||||
|
return _radial_gradient(spec, p, r1, r2)
|
||||||
|
else
|
||||||
|
return _solid_color(spec)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local source_chooser = function(low_source, high_source, limit)
|
||||||
|
local f = _create_critical_function(limit)
|
||||||
|
return function(value)
|
||||||
|
if f(value) then
|
||||||
|
return high_source
|
||||||
|
else
|
||||||
|
return low_source
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.solid_color_chooser = function(low_color, high_color, limit)
|
||||||
|
return source_chooser(
|
||||||
|
M.solid_color(low_color),
|
||||||
|
M.solid_color(high_color),
|
||||||
|
limit
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.linear_pattern_chooser = function(low_pattern, high_pattern, limit, p1, p2)
|
||||||
|
return source_chooser(
|
||||||
|
M.linear_pattern(low_pattern, p1, p2),
|
||||||
|
M.linear_pattern(high_pattern, p1, p2),
|
||||||
|
limit
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.radial_pattern_chooser = function(low_pattern, high_pattern, limit, p, r1, r2)
|
||||||
|
return source_chooser(
|
||||||
|
M.radial_pattern(low_pattern, p, r1, r2),
|
||||||
|
M.radial_pattern(high_pattern, p, r1, r2),
|
||||||
|
limit
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,55 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local err = require 'err'
|
||||||
|
|
||||||
|
local __cairo_set_line_width = cairo_set_line_width
|
||||||
|
local __cairo_set_line_cap = cairo_set_line_cap
|
||||||
|
local __cairo_set_line_join = cairo_set_line_join
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- pure
|
||||||
|
|
||||||
|
-- circle: a closed path with no corners
|
||||||
|
M.circle = function(thickness)
|
||||||
|
return err.safe_table({thickness = thickness})
|
||||||
|
end
|
||||||
|
|
||||||
|
-- line: an open path with no corners
|
||||||
|
M.line = function(thickness, cap)
|
||||||
|
return err.safe_table({thickness = thickness, cap = cap})
|
||||||
|
end
|
||||||
|
|
||||||
|
-- open poly: an open path with corners
|
||||||
|
M.open_poly = function(thickness, cap, join)
|
||||||
|
return err.safe_table({thickness = thickness, cap = cap, join = join})
|
||||||
|
end
|
||||||
|
|
||||||
|
-- closed poly: an closed path with corners
|
||||||
|
M.closed_poly = function(thickness, join)
|
||||||
|
return err.safe_table({thickness = thickness, join = join})
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- impure
|
||||||
|
|
||||||
|
M.set_circle_style = function(style, cr)
|
||||||
|
__cairo_set_line_width(cr, style.thickness)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.set_line_style = function(style, cr)
|
||||||
|
__cairo_set_line_width(cr, style.thickness)
|
||||||
|
__cairo_set_line_cap(cr, style.cap)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.set_open_poly_style = function(style, cr)
|
||||||
|
__cairo_set_line_width(cr, style.thickness)
|
||||||
|
__cairo_set_line_cap(cr, style.cap)
|
||||||
|
__cairo_set_line_join(cr, style.join)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.set_closed_poly_style = function(style, cr)
|
||||||
|
__cairo_set_line_width(cr, style.thickness)
|
||||||
|
__cairo_set_line_join(cr, style.join)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,58 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local err = require 'err'
|
||||||
|
local dynamic = require 'dynamic'
|
||||||
|
local ti = require 'text_internal'
|
||||||
|
local source = require 'source'
|
||||||
|
local pure = require 'pure'
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- pure
|
||||||
|
|
||||||
|
M.config = function(font_spec, color, x_align, y_align)
|
||||||
|
return err.safe_table(
|
||||||
|
{
|
||||||
|
font_spec = font_spec,
|
||||||
|
color = color,
|
||||||
|
x_align = x_align,
|
||||||
|
y_align = y_align,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
local _make = function(point, chars, config, format)
|
||||||
|
local font = ti.make_font(config.font_spec)
|
||||||
|
local get_delta_x = ti.x_align_function(config.x_align, font)
|
||||||
|
local format_chars = ti.make_format_function(format)
|
||||||
|
local make_vtext = pure.partial(ti.make_vtext, point.x, get_delta_x)
|
||||||
|
local setter = pure.memoize(pure.compose(make_vtext, format_chars))
|
||||||
|
local static = {
|
||||||
|
y = point.y + ti.get_delta_y(config.y_align, font),
|
||||||
|
font = font,
|
||||||
|
source = source.solid_color(config.color),
|
||||||
|
}
|
||||||
|
return dynamic.single(static, setter, chars)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make_formatted = function(point, text, config, format)
|
||||||
|
return _make(point, (text or ti.NULL_TEXT_STRING), config, format)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make_plain = function(point, text, config)
|
||||||
|
return M.make_formatted(point, text, config, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- impure
|
||||||
|
|
||||||
|
M.set = function(obj, text)
|
||||||
|
obj.var = obj.setter(text)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.draw = function(obj, cr)
|
||||||
|
local st = obj.static
|
||||||
|
ti.set_font_spec(cr, st.font, st.source)
|
||||||
|
ti.draw_vtext_at_y(obj.var, st.y, cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,49 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local source = require 'source'
|
||||||
|
local ti = require 'text_internal'
|
||||||
|
local pure = require 'pure'
|
||||||
|
local impure = require 'impure'
|
||||||
|
local dynamic = require 'dynamic'
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- pure
|
||||||
|
|
||||||
|
M.make = function(point, texts, config, format, spacing)
|
||||||
|
local font = ti.make_font(config.font_spec)
|
||||||
|
local get_delta_x = ti.x_align_function(config.x_align, font)
|
||||||
|
local format_chars = ti.make_format_function(format)
|
||||||
|
local delta_y = ti.get_delta_y(config.y_align, font)
|
||||||
|
local ys = pure.map_n(
|
||||||
|
function(i) return point.y + spacing * (i - 1) + delta_y end,
|
||||||
|
#texts
|
||||||
|
)
|
||||||
|
local make_vtext = pure.partial(ti.make_vtext, point.x, get_delta_x)
|
||||||
|
local setter = pure.memoize(pure.compose(make_vtext, format_chars))
|
||||||
|
local static = {
|
||||||
|
y_positions = ys,
|
||||||
|
font = font,
|
||||||
|
source = source.solid_color(config.color),
|
||||||
|
}
|
||||||
|
return dynamic.multi(static, setter, texts)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make_n = function(point, num_rows, config, format, spacing, init_text)
|
||||||
|
local dummy = pure.rep(num_rows, init_text or ti.NULL_TEXT_STRING)
|
||||||
|
return M.make(point, dummy, config, format, spacing)
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- impure
|
||||||
|
|
||||||
|
M.set = function(obj, row_num, text)
|
||||||
|
obj.var[row_num] = obj.setter(text)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.draw = function(obj, cr)
|
||||||
|
local static = obj.static
|
||||||
|
ti.set_font_spec(cr, static.font, static.source)
|
||||||
|
impure.each2(ti.draw_vtext_at_y, obj.var, static.y_positions, cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,165 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local err = require 'err'
|
||||||
|
local geom = require 'geom'
|
||||||
|
|
||||||
|
local __string_sub = string.sub
|
||||||
|
local __cairo_toy_font_face_create = cairo_toy_font_face_create
|
||||||
|
local __cairo_font_extents = cairo_font_extents
|
||||||
|
local __cairo_set_font_face = cairo_set_font_face
|
||||||
|
local __cairo_set_font_size = cairo_set_font_size
|
||||||
|
local __cairo_text_extents = cairo_text_extents
|
||||||
|
local __cairo_set_source = cairo_set_source
|
||||||
|
local __cairo_move_to = cairo_move_to
|
||||||
|
local __cairo_show_text = cairo_show_text
|
||||||
|
|
||||||
|
M.NULL_TEXT_STRING = '<null>'
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- pure
|
||||||
|
|
||||||
|
local trim_to_length = function(text, len)
|
||||||
|
if #text > len then
|
||||||
|
return __string_sub(text, 1, len)..'...'
|
||||||
|
else
|
||||||
|
return text
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make_format_function = function(format)
|
||||||
|
if type(format) == "function" then
|
||||||
|
return format
|
||||||
|
elseif type(format) == "number" and format > 0 then
|
||||||
|
return function(_text) return trim_to_length(_text, format) end
|
||||||
|
elseif type(format) == "string" then
|
||||||
|
return function(_text) return string.format(format, _text) end
|
||||||
|
elseif format == nil or format == false then
|
||||||
|
return function(_text) return _text end
|
||||||
|
else
|
||||||
|
local msg = "format must be a printf string, positive int, or function: got "
|
||||||
|
local t = type(format)
|
||||||
|
if t == "number" or t == "string" then
|
||||||
|
msg = msg..format
|
||||||
|
else
|
||||||
|
msg = msg.."a "..t
|
||||||
|
end
|
||||||
|
err.assert_trace(nil, msg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make_font_face = function(font_spec)
|
||||||
|
return __cairo_toy_font_face_create(
|
||||||
|
font_spec.family,
|
||||||
|
font_spec.slant,
|
||||||
|
font_spec.weight
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make_font = function(font_spec)
|
||||||
|
return {
|
||||||
|
face = M.make_font_face(font_spec),
|
||||||
|
size = font_spec.size,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make_text = function(x, y, chars)
|
||||||
|
return err.safe_table({x = x, y = y, chars = chars})
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make_htext = function(y, delta_x, chars)
|
||||||
|
return err.safe_table({y = y, delta_x = delta_x, chars = chars})
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make_vtext = function(x, delta_x_fun, chars)
|
||||||
|
return err.safe_table({x = x + delta_x_fun(chars), chars = chars})
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- impure
|
||||||
|
|
||||||
|
local dummy_text_extents = cairo_text_extents_t:create()
|
||||||
|
tolua.takeownership(dummy_text_extents)
|
||||||
|
|
||||||
|
local dummy_font_extents = cairo_font_extents_t:create()
|
||||||
|
tolua.takeownership(dummy_font_extents)
|
||||||
|
|
||||||
|
local set_font_extents = function(font)
|
||||||
|
__cairo_set_font_size(geom.CR_DUMMY, font.size)
|
||||||
|
__cairo_set_font_face(geom.CR_DUMMY, font.face)
|
||||||
|
__cairo_font_extents(geom.CR_DUMMY, dummy_font_extents)
|
||||||
|
return dummy_font_extents
|
||||||
|
end
|
||||||
|
|
||||||
|
local set_text_extents = function(chars, font)
|
||||||
|
__cairo_set_font_size(geom.CR_DUMMY, font.size)
|
||||||
|
__cairo_set_font_face(geom.CR_DUMMY, font.face)
|
||||||
|
__cairo_text_extents(geom.CR_DUMMY, chars, dummy_text_extents)
|
||||||
|
return dummy_text_extents
|
||||||
|
end
|
||||||
|
|
||||||
|
M.get_width = function(chars, font)
|
||||||
|
return set_text_extents(chars, font).width
|
||||||
|
end
|
||||||
|
|
||||||
|
M.font_height = function(font)
|
||||||
|
return set_font_extents(font).height
|
||||||
|
end
|
||||||
|
|
||||||
|
M.x_align_function = function(x_align, font)
|
||||||
|
if x_align == 'left' then
|
||||||
|
return function(text)
|
||||||
|
local te = set_text_extents(text, font)
|
||||||
|
return -te.x_bearing
|
||||||
|
end
|
||||||
|
elseif x_align == 'center' then
|
||||||
|
return function(text)
|
||||||
|
local te = set_text_extents(text, font)
|
||||||
|
return -(te.x_bearing + te.width * 0.5)
|
||||||
|
end
|
||||||
|
elseif x_align == 'right' then
|
||||||
|
return function(text)
|
||||||
|
local te = set_text_extents(text, font)
|
||||||
|
return -(te.x_bearing + te.width)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
err.assert_trace(nil, "invalid x_align")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.get_delta_y = function(y_align, font)
|
||||||
|
local fe = set_font_extents(font)
|
||||||
|
if y_align == 'bottom' then
|
||||||
|
return -fe.descent
|
||||||
|
elseif y_align == 'top' then
|
||||||
|
return fe.height
|
||||||
|
elseif y_align == 'center' then
|
||||||
|
return 0.92 * fe.height * 0.5 - fe.descent
|
||||||
|
else
|
||||||
|
err.assert_trace(nil, "invalid y_align")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.set_font_spec = function(cr, font, source)
|
||||||
|
__cairo_set_font_face(cr, font.face)
|
||||||
|
__cairo_set_font_size(cr, font.size)
|
||||||
|
__cairo_set_source(cr, source)
|
||||||
|
end
|
||||||
|
|
||||||
|
local draw_text_at = function(cr, x, y, chars)
|
||||||
|
__cairo_move_to(cr, x, y)
|
||||||
|
__cairo_show_text(cr, chars)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.draw_text = function(obj, cr)
|
||||||
|
draw_text_at(cr, obj.x, obj.y, obj.chars)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.draw_htext_at_x = function(obj, x, cr)
|
||||||
|
draw_text_at(cr, obj.delta_x + x, obj.y, obj.chars)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.draw_vtext_at_y = function(obj, y, cr)
|
||||||
|
draw_text_at(cr, obj.x, y, obj.chars)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,183 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local source = require 'source'
|
||||||
|
local rect = require 'rect'
|
||||||
|
local err = require 'err'
|
||||||
|
local ti = require 'text_internal'
|
||||||
|
local geom = require 'geom'
|
||||||
|
local pure = require 'pure'
|
||||||
|
local impure = require 'impure'
|
||||||
|
local style = require 'style'
|
||||||
|
local path = require 'path'
|
||||||
|
local shape = require 'shape'
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- pure
|
||||||
|
|
||||||
|
M.config = function(border_config, sep_config, header_config, body_config, padding)
|
||||||
|
return err.safe_table(
|
||||||
|
{
|
||||||
|
border_config = border_config,
|
||||||
|
sep_config = sep_config,
|
||||||
|
header_config = header_config,
|
||||||
|
body_config = body_config,
|
||||||
|
padding = padding,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.body_config = function(font_spec, color, columns)
|
||||||
|
return err.safe_table(
|
||||||
|
{
|
||||||
|
font_spec = font_spec,
|
||||||
|
color = color,
|
||||||
|
columns = columns,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.column_config = function(header, format)
|
||||||
|
return err.safe_table({header = header, format = format})
|
||||||
|
end
|
||||||
|
|
||||||
|
M.header_config = function(font_spec, color, bottom_padding)
|
||||||
|
return err.safe_table(
|
||||||
|
{
|
||||||
|
font_spec = font_spec,
|
||||||
|
color = color,
|
||||||
|
bottom_padding = bottom_padding,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.padding = function(l, t, r, b)
|
||||||
|
return err.safe_table(
|
||||||
|
{
|
||||||
|
left = l,
|
||||||
|
right = r,
|
||||||
|
top = t,
|
||||||
|
bottom = b,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TODO this is basically the same as that from ylabels
|
||||||
|
local make_header_text = function(x, y, chars, font)
|
||||||
|
return ti.make_text(
|
||||||
|
x + ti.x_align_function('center', font)(chars),
|
||||||
|
y + ti.get_delta_y('center', font),
|
||||||
|
chars
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- M.make = function(box, num_rows, columns, table_config)
|
||||||
|
M.make = function(box, num_rows, table_config)
|
||||||
|
local bc = table_config.body_config
|
||||||
|
local hs = table_config.header_config
|
||||||
|
local columns = bc.columns
|
||||||
|
local header_font = ti.make_font(hs.font_spec)
|
||||||
|
local body_font = ti.make_font(bc.font_spec)
|
||||||
|
local p = table_config.padding
|
||||||
|
local tbl_width = box.width - p.left - p.right
|
||||||
|
local tbl_height = box.height - p.top - p.bottom
|
||||||
|
local tbl_x = box.corner.x + p.left
|
||||||
|
local tbl_y = box.corner.y + p.top
|
||||||
|
local body_delta_y = ti.get_delta_y('center', body_font)
|
||||||
|
local body_y = tbl_y + hs.bottom_padding + body_delta_y
|
||||||
|
local column_width = tbl_width / #columns
|
||||||
|
local spacing = (tbl_height - hs.bottom_padding) / (num_rows - 1)
|
||||||
|
local headers = pure.imap(
|
||||||
|
function (i, conf)
|
||||||
|
local x = tbl_x + column_width * (i - 0.5)
|
||||||
|
return make_header_text(x, tbl_y, conf.header, header_font)
|
||||||
|
end,
|
||||||
|
columns
|
||||||
|
)
|
||||||
|
local sep_paths = pure.map_n(
|
||||||
|
function(i)
|
||||||
|
local x = tbl_x + column_width * i
|
||||||
|
return path.create_line(
|
||||||
|
geom.CR_DUMMY,
|
||||||
|
geom.make_point(x, tbl_y),
|
||||||
|
geom.make_point(x, tbl_y + tbl_height)
|
||||||
|
)
|
||||||
|
end,
|
||||||
|
#columns - 1
|
||||||
|
)
|
||||||
|
|
||||||
|
local get_delta_x = ti.x_align_function('center', body_font)
|
||||||
|
local make_setter = function(i, conf)
|
||||||
|
local column_x = tbl_x + column_width * (i - 0.5)
|
||||||
|
return pure.memoize(
|
||||||
|
pure.compose(
|
||||||
|
pure.partial(ti.make_vtext, column_x, get_delta_x),
|
||||||
|
ti.make_format_function(conf.format)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
local setters = pure.imap(make_setter, columns)
|
||||||
|
|
||||||
|
return {
|
||||||
|
static = {
|
||||||
|
header = {
|
||||||
|
font = header_font,
|
||||||
|
source = source.solid_color(hs.color),
|
||||||
|
texts = headers,
|
||||||
|
},
|
||||||
|
body = {
|
||||||
|
font = body_font,
|
||||||
|
source = source.solid_color(bc.color),
|
||||||
|
y_positions = pure.map_n(
|
||||||
|
function(i) return body_y + spacing * (i - 1) end,
|
||||||
|
num_rows
|
||||||
|
),
|
||||||
|
},
|
||||||
|
separators = {
|
||||||
|
style = table_config.sep_config.style,
|
||||||
|
shapes = {
|
||||||
|
source = source.solid_color(table_config.sep_config.pattern),
|
||||||
|
paths = sep_paths,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
border = rect.make(box, table_config.border_config),
|
||||||
|
},
|
||||||
|
setters = setters,
|
||||||
|
var = pure.map_n(
|
||||||
|
function(c) return pure.rep(num_rows, setters[c]("NULL")) end,
|
||||||
|
#columns
|
||||||
|
)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- impure
|
||||||
|
|
||||||
|
M.set = function(obj, col_num, row_num, text)
|
||||||
|
obj.var[col_num][row_num] = obj.setters[col_num](text)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.draw_static = function(obj, cr)
|
||||||
|
local static = obj.static
|
||||||
|
local seps = static.separators
|
||||||
|
local header = static.header
|
||||||
|
|
||||||
|
rect.draw(static.border, cr)
|
||||||
|
|
||||||
|
style.set_line_style(seps.style, cr)
|
||||||
|
shape.draw_shapes(seps.shapes, cr)
|
||||||
|
|
||||||
|
ti.set_font_spec(cr, header.font, header.source)
|
||||||
|
impure.each(ti.draw_text, header.texts, cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
local draw_column = function(rows, ys, cr)
|
||||||
|
impure.each2(ti.draw_vtext_at_y, rows, ys, cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.draw_dynamic = function(obj, cr)
|
||||||
|
local body = obj.static.body
|
||||||
|
ti.set_font_spec(cr, body.font, body.source)
|
||||||
|
impure.each(draw_column, obj.var, body.y_positions, cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,79 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local err = require 'err'
|
||||||
|
local pure = require 'pure'
|
||||||
|
local source = require 'source'
|
||||||
|
local ti = require 'text_internal'
|
||||||
|
local dynamic = require 'dynamic'
|
||||||
|
|
||||||
|
local __tonumber = tonumber
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- pure
|
||||||
|
|
||||||
|
local _make = function(point, chars, config, threshold_config, format)
|
||||||
|
local font = ti.make_font(config.font_spec)
|
||||||
|
local get_delta_x = ti.x_align_function(config.x_align, font)
|
||||||
|
local make_vtext = pure.partial(ti.make_vtext, point.x, get_delta_x)
|
||||||
|
local format_chars = ti.make_format_function(format)
|
||||||
|
local source_chooser = source.solid_color_chooser(
|
||||||
|
config.color,
|
||||||
|
threshold_config.high_color,
|
||||||
|
threshold_config.threshold
|
||||||
|
)
|
||||||
|
local _setter = pure.memoize(
|
||||||
|
function(cs)
|
||||||
|
local _cs = cs or 0
|
||||||
|
return {
|
||||||
|
text = make_vtext(format_chars(_cs)),
|
||||||
|
source = source_chooser(_cs),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
)
|
||||||
|
local f = threshold_config.pre_function
|
||||||
|
local setter
|
||||||
|
if f then
|
||||||
|
setter = function(x) return _setter(f(x)) end
|
||||||
|
else
|
||||||
|
setter = _setter
|
||||||
|
end
|
||||||
|
local static = {
|
||||||
|
y = point.y + ti.get_delta_y(config.y_align, font),
|
||||||
|
font = font,
|
||||||
|
}
|
||||||
|
return dynamic.single(static, setter, chars or 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.config = function(high_color, threshold, pre_function)
|
||||||
|
return err.safe_table(
|
||||||
|
{
|
||||||
|
high_color = high_color,
|
||||||
|
threshold = threshold,
|
||||||
|
pre_function = pre_function,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make_formatted = function(point, text, config, format, threshold_config)
|
||||||
|
return _make(point, text, config, threshold_config, format)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make_plain = function(point, text, config, threshold_config)
|
||||||
|
return M.make_formatted(point, text, config, threshold_config, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- impure
|
||||||
|
|
||||||
|
M.set = function(obj, x)
|
||||||
|
obj.var = obj.setter(x)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.draw = function(obj, cr)
|
||||||
|
local st = obj.static
|
||||||
|
local var = obj.var
|
||||||
|
ti.set_font_spec(cr, st.font, var.source)
|
||||||
|
ti.draw_vtext_at_y(var.text, st.y, cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,269 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local timeseries = require 'timeseries'
|
||||||
|
local tsi = require 'timeseries_internal'
|
||||||
|
local ti = require 'text_internal'
|
||||||
|
local err = require 'err'
|
||||||
|
local ylabels = require 'ylabels'
|
||||||
|
local xlabels = require 'xlabels'
|
||||||
|
local geom = require 'geom'
|
||||||
|
local source = require 'source'
|
||||||
|
local pure = require 'pure'
|
||||||
|
local impure = require 'impure'
|
||||||
|
|
||||||
|
local __table_remove = table.remove
|
||||||
|
local __math_ceil = math.ceil
|
||||||
|
local __math_log = math.log
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- pure
|
||||||
|
|
||||||
|
local choose_scale_factor = function(timers, new_sf)
|
||||||
|
if #timers == 0 then
|
||||||
|
return new_sf
|
||||||
|
else
|
||||||
|
local cur_sf = timers[1].factor
|
||||||
|
if new_sf < cur_sf then
|
||||||
|
return new_sf
|
||||||
|
else
|
||||||
|
return cur_sf
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.scaling_parameters = function(base, min_domain, threshold)
|
||||||
|
return err.safe_table(
|
||||||
|
{
|
||||||
|
base = base,
|
||||||
|
min_domain = min_domain,
|
||||||
|
threshold = threshold,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
local tick_timer = function(timer)
|
||||||
|
timer.remaining = timer.remaining - 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ASSUME
|
||||||
|
-- 1. this is a FIFO queue
|
||||||
|
-- 2. timers will be sorted from highest scale to lowest scale going from
|
||||||
|
-- back to front
|
||||||
|
-- 3. no timers will share a time slot
|
||||||
|
-- 4. no timers will have the same scale factor
|
||||||
|
-- 5. the table will always be a sequence
|
||||||
|
-- NOTE: scale factor is inversely related to scale, so higher scale -> lower
|
||||||
|
-- factor (hence the inequalities)
|
||||||
|
local update_timers = function(timers, prev_sf, new_sf, init_timer)
|
||||||
|
if timers[1] and timers[1].remaining == 0 then
|
||||||
|
__table_remove(timers, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
impure.each(tick_timer, timers)
|
||||||
|
local n = #timers
|
||||||
|
|
||||||
|
if new_sf < prev_sf then
|
||||||
|
while n > 0 and timers[n].factor >= new_sf do
|
||||||
|
timers[n] = nil
|
||||||
|
n = n - 1
|
||||||
|
end
|
||||||
|
elseif new_sf > prev_sf and (n == 0 or prev_sf > timers[1].factor) then
|
||||||
|
timers[n + 1] = init_timer(prev_sf)
|
||||||
|
end
|
||||||
|
return timers
|
||||||
|
end
|
||||||
|
|
||||||
|
local scale_point = function(value, y, h, new_factor, old_factor)
|
||||||
|
return y + h * (1 - (1 - (value - y) / h) * (new_factor / old_factor))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- local debug_timers = function(timers, sf, value)
|
||||||
|
-- print('----------------------------------------------------------------------')
|
||||||
|
-- print('value', value, 'scale_factor', sf)
|
||||||
|
-- for i, v in pairs(timers) do
|
||||||
|
-- print('timers', 'i', i, 't', v.remaining, 'f', v.factor)
|
||||||
|
-- end
|
||||||
|
-- print('length', #timers)
|
||||||
|
-- end
|
||||||
|
|
||||||
|
M.make = function(box, samplefreq, plot_config, label_config, scaling_params)
|
||||||
|
local t = scaling_params.threshold
|
||||||
|
local m = scaling_params.min_domain
|
||||||
|
local b = scaling_params.base
|
||||||
|
|
||||||
|
local get_scale_factor = function(x)
|
||||||
|
local domain = m
|
||||||
|
if x > 0 then
|
||||||
|
domain = __math_ceil(__math_log(x / t) / __math_log(b))
|
||||||
|
end
|
||||||
|
if domain < m then domain = m end
|
||||||
|
return b ^ -domain
|
||||||
|
end
|
||||||
|
|
||||||
|
local x = box.corner.x
|
||||||
|
local gconf = plot_config.grid_config
|
||||||
|
local x_label_format = timeseries.make_format_timecourse_x_label(plot_config.num_points, samplefreq)
|
||||||
|
local label_font = ti.make_font(label_config.font_spec)
|
||||||
|
local x_label_data = xlabels.make(box.bottom_y, gconf.num_x + 1, x_label_format, label_font)
|
||||||
|
local axis_x = box.corner.x
|
||||||
|
local right_x = axis_x + box.width
|
||||||
|
local plot_y = box.corner.y
|
||||||
|
local total_width = box.width
|
||||||
|
local n_y_labels = gconf.num_y + 1
|
||||||
|
|
||||||
|
local plot_height = box.height - xlabels.get_x_axis_height(label_font)
|
||||||
|
|
||||||
|
local make_y_labels = pure.memoize(
|
||||||
|
function(scale_factor)
|
||||||
|
return ylabels.make(
|
||||||
|
box.corner,
|
||||||
|
plot_height,
|
||||||
|
n_y_labels,
|
||||||
|
label_font,
|
||||||
|
label_config.y_format,
|
||||||
|
scale_factor
|
||||||
|
)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
local make_grid_paths = pure.memoize(
|
||||||
|
function(y_axis_width)
|
||||||
|
return tsi.make_plotarea_paths(
|
||||||
|
geom.CR_DUMMY,
|
||||||
|
right_x,
|
||||||
|
plot_y,
|
||||||
|
total_width - y_axis_width,
|
||||||
|
plot_height,
|
||||||
|
gconf.num_x,
|
||||||
|
gconf.num_y
|
||||||
|
)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
local get_x_label_positions = pure.memoize(
|
||||||
|
function(y_axis_width)
|
||||||
|
local w = total_width - y_axis_width
|
||||||
|
return xlabels.get_x_label_positions(right_x, w, x_label_data)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
local init_timer = function(scale_factor)
|
||||||
|
return {
|
||||||
|
factor = scale_factor,
|
||||||
|
remaining = plot_config.num_points
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local scale_data_points = function(series, old_factor, new_factor)
|
||||||
|
if old_factor == new_factor then
|
||||||
|
return series
|
||||||
|
else
|
||||||
|
return pure.map(scale_point, series, plot_y, plot_height, new_factor, old_factor)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local insert_data_point = function(series, value, scale_factor)
|
||||||
|
return tsi.insert_data_point(plot_y, plot_height, plot_config.num_points, series, value * scale_factor)
|
||||||
|
end
|
||||||
|
|
||||||
|
local inner_setter = function(value, series, cur_sf, prev_sf, timers)
|
||||||
|
local new_sf = get_scale_factor(value)
|
||||||
|
local new_timers = update_timers(timers, prev_sf, new_sf, init_timer)
|
||||||
|
local new_cur_sf = choose_scale_factor(new_timers, new_sf)
|
||||||
|
local y_labels = make_y_labels(1 / new_cur_sf)
|
||||||
|
local width = total_width - y_labels.width
|
||||||
|
-- debug_timers(new_timers, new_sf, value)
|
||||||
|
return {
|
||||||
|
axis = {
|
||||||
|
x = {
|
||||||
|
positions = get_x_label_positions(y_labels.width),
|
||||||
|
},
|
||||||
|
y = {
|
||||||
|
labels = y_labels,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
current_scale_factor = new_cur_sf,
|
||||||
|
prev_scale_factor = new_sf,
|
||||||
|
plotarea = {
|
||||||
|
dx = width / plot_config.num_points,
|
||||||
|
paths = make_grid_paths(y_labels.width)
|
||||||
|
},
|
||||||
|
timers = new_timers,
|
||||||
|
series = insert_data_point(
|
||||||
|
scale_data_points(series, cur_sf, new_cur_sf),
|
||||||
|
value,
|
||||||
|
new_cur_sf
|
||||||
|
)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local setter = function(value, var)
|
||||||
|
return inner_setter(
|
||||||
|
value,
|
||||||
|
var.series,
|
||||||
|
var.current_scale_factor,
|
||||||
|
var.prev_scale_factor,
|
||||||
|
var.timers
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
return err.safe_table(
|
||||||
|
{
|
||||||
|
static = {
|
||||||
|
box = box,
|
||||||
|
axis = {
|
||||||
|
font = label_font,
|
||||||
|
source = source.solid_color(label_config.color),
|
||||||
|
x = {
|
||||||
|
label_data = x_label_data,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plotarea = {
|
||||||
|
bottom_y = plot_height + plot_y,
|
||||||
|
sources = tsi.make_sources(x, plot_y, total_width, plot_config),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setter = setter,
|
||||||
|
var = inner_setter(0, {}, 1, 1, {}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- impure
|
||||||
|
|
||||||
|
M.update = function(obj, value)
|
||||||
|
obj.var = obj.setter(value, obj.var)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- nothing here is "static" because we cannot assume that
|
||||||
|
-- any object will remain the same shape (can shift in both x and y)
|
||||||
|
M.draw_static = function(_, _)
|
||||||
|
-- stub
|
||||||
|
end
|
||||||
|
|
||||||
|
M.draw_dynamic = function(obj, cr)
|
||||||
|
local var = obj.var
|
||||||
|
local static = obj.static
|
||||||
|
local box = static.box
|
||||||
|
local saxis = static.axis
|
||||||
|
local vaxis = var.axis
|
||||||
|
local vplotarea = var.plotarea
|
||||||
|
local splotarea = static.plotarea
|
||||||
|
local sources = splotarea.sources
|
||||||
|
local paths = vplotarea.paths
|
||||||
|
local right_x = box.right_x
|
||||||
|
timeseries.draw_labels(
|
||||||
|
cr,
|
||||||
|
saxis.font,
|
||||||
|
saxis.source,
|
||||||
|
vaxis.x.positions,
|
||||||
|
saxis.x.label_data,
|
||||||
|
vaxis.y.labels
|
||||||
|
)
|
||||||
|
tsi.draw_grid(cr, paths.grid, sources.grid)
|
||||||
|
tsi.draw_series(cr, right_x, splotarea.bottom_y, vplotarea.dx, var.series, sources.series)
|
||||||
|
tsi.draw_outline(cr, paths.outline, sources.outline)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,163 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local err = require 'err'
|
||||||
|
local xlabels = require 'xlabels'
|
||||||
|
local ylabels = require 'ylabels'
|
||||||
|
local source = require 'source'
|
||||||
|
local ti = require 'text_internal'
|
||||||
|
local tsi = require 'timeseries_internal'
|
||||||
|
local geom = require 'geom'
|
||||||
|
local impure = require 'impure'
|
||||||
|
|
||||||
|
local __string_format = string.format
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- pure
|
||||||
|
|
||||||
|
-- TODO group these better
|
||||||
|
M.config = function(num_points, outline_color, data_line_pattern,
|
||||||
|
data_fill_pattern, grid_config)
|
||||||
|
return err.safe_table(
|
||||||
|
{
|
||||||
|
num_points = num_points,
|
||||||
|
outline_color = outline_color,
|
||||||
|
data_line_pattern = data_line_pattern,
|
||||||
|
data_fill_pattern = data_fill_pattern,
|
||||||
|
grid_config = grid_config,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.grid_config = function(num_x, num_y, color)
|
||||||
|
return err.safe_table(
|
||||||
|
{
|
||||||
|
num_x = num_x,
|
||||||
|
num_y = num_y,
|
||||||
|
pattern = color,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.label_config = function(color, font_spec, y_format)
|
||||||
|
return err.safe_table(
|
||||||
|
{
|
||||||
|
color = color,
|
||||||
|
font_spec = font_spec,
|
||||||
|
y_format = y_format
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make_format_timecourse_x_label = function(n, freq)
|
||||||
|
return function(x) return __string_format('%.0fs', (1 - x) * n / freq) end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make = function(box, samplefreq, config, label_config)
|
||||||
|
local x = box.corner.x
|
||||||
|
local y = box.corner.y
|
||||||
|
local w = box.width
|
||||||
|
local h = box.height
|
||||||
|
local right_x = x + w
|
||||||
|
local bottom_y = y + h
|
||||||
|
local gconf = config.grid_config
|
||||||
|
local label_font = ti.make_font(label_config.font_spec)
|
||||||
|
|
||||||
|
local plot_height = box.height - xlabels.get_x_axis_height(label_font)
|
||||||
|
local y_labels = ylabels.make(
|
||||||
|
box.corner,
|
||||||
|
plot_height,
|
||||||
|
gconf.num_y + 1,
|
||||||
|
label_font,
|
||||||
|
label_config.y_format,
|
||||||
|
1
|
||||||
|
)
|
||||||
|
|
||||||
|
local plot_width = w - y_labels.width
|
||||||
|
|
||||||
|
local x_label_format = M.make_format_timecourse_x_label(config.num_points, samplefreq)
|
||||||
|
|
||||||
|
local x_labels = xlabels.make(bottom_y, gconf.num_x + 1, x_label_format, label_font)
|
||||||
|
|
||||||
|
local setter = function(value, series)
|
||||||
|
return tsi.insert_data_point(y, plot_height, config.num_points, series, value)
|
||||||
|
end
|
||||||
|
|
||||||
|
return err.safe_table(
|
||||||
|
{
|
||||||
|
static = {
|
||||||
|
box = box,
|
||||||
|
axis = {
|
||||||
|
font = label_font,
|
||||||
|
source = source.solid_color(label_config.color),
|
||||||
|
x = {
|
||||||
|
label_data = x_labels,
|
||||||
|
positions = xlabels.get_x_label_positions(right_x, plot_width, x_labels),
|
||||||
|
},
|
||||||
|
y = {
|
||||||
|
labels = y_labels,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plotarea = {
|
||||||
|
dx = plot_width / config.num_points,
|
||||||
|
bottom_y = y + plot_height,
|
||||||
|
paths = tsi.make_plotarea_paths(
|
||||||
|
geom.CR_DUMMY,
|
||||||
|
right_x,
|
||||||
|
y,
|
||||||
|
plot_width,
|
||||||
|
plot_height,
|
||||||
|
gconf.num_x,
|
||||||
|
gconf.num_y
|
||||||
|
),
|
||||||
|
sources = tsi.make_sources(x, y, w, config),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setter = setter,
|
||||||
|
var = setter(0, {}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- impure
|
||||||
|
|
||||||
|
M.update = function(obj, value)
|
||||||
|
obj.var = obj.setter(value, obj.var)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.draw_labels = function(cr, font, _source, x_positions, x_label_data, y_labels)
|
||||||
|
ti.set_font_spec(cr, font, _source)
|
||||||
|
impure.each2(ti.draw_htext_at_x, x_label_data, x_positions, cr)
|
||||||
|
impure.each(ti.draw_text, y_labels, cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
M.draw_static = function(obj, cr)
|
||||||
|
local static = obj.static
|
||||||
|
local axis = static.axis
|
||||||
|
local ax = axis.x
|
||||||
|
local plotarea = static.plotarea
|
||||||
|
local paths = plotarea.paths
|
||||||
|
local sources = plotarea.sources
|
||||||
|
M.draw_labels(
|
||||||
|
cr,
|
||||||
|
axis.font,
|
||||||
|
axis.source,
|
||||||
|
ax.positions,
|
||||||
|
ax.label_data,
|
||||||
|
axis.y.labels
|
||||||
|
)
|
||||||
|
tsi.draw_grid(cr, paths.grid, sources.grid)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.draw_dynamic = function(obj, cr)
|
||||||
|
local static = obj.static
|
||||||
|
local box = static.box
|
||||||
|
local plotarea = static.plotarea
|
||||||
|
local right_x = box.right_x
|
||||||
|
local sources = plotarea.sources
|
||||||
|
tsi.draw_series(cr, right_x, plotarea.bottom_y, plotarea.dx, obj.var, sources.series)
|
||||||
|
tsi.draw_outline(cr, plotarea.paths.outline, sources.outline)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,132 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local source = require 'source'
|
||||||
|
local geom = require 'geom'
|
||||||
|
local pure = require 'pure'
|
||||||
|
local style = require 'style'
|
||||||
|
local path = require 'path'
|
||||||
|
local shape = require 'shape'
|
||||||
|
|
||||||
|
local __cairo_move_to = cairo_move_to
|
||||||
|
local __cairo_line_to = cairo_line_to
|
||||||
|
local __cairo_set_source = cairo_set_source
|
||||||
|
local __cairo_fill_preserve = cairo_fill_preserve
|
||||||
|
local __cairo_stroke = cairo_stroke
|
||||||
|
local __table_insert = table.insert
|
||||||
|
|
||||||
|
local DATA_STYLE = style.open_poly(1, CAIRO_LINE_CAP_BUTT, CAIRO_LINE_JOIN_MITER)
|
||||||
|
local GRID_CONFIG = style.line(1, CAIRO_LINE_CAP_BUTT)
|
||||||
|
local OUTLINE_STYLE = style.open_poly(2, CAIRO_LINE_CAP_BUTT, CAIRO_LINE_JOIN_MITER)
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- pure
|
||||||
|
|
||||||
|
local make_x_grid = function(cr, x, y, w, h, n)
|
||||||
|
local y1 = y - 0.5
|
||||||
|
local y2 = y1 + h + 0.5
|
||||||
|
local grid_line_spacing = w / n
|
||||||
|
local f = function(i)
|
||||||
|
local x1 = x - w + grid_line_spacing * i - 0.5
|
||||||
|
local p1 = geom.make_point(x1, y1)
|
||||||
|
local p2 = geom.make_point(x1, y2)
|
||||||
|
return path.create_line(cr, p1, p2)
|
||||||
|
end
|
||||||
|
return pure.map_n(f, n)
|
||||||
|
end
|
||||||
|
|
||||||
|
local make_y_grid = function(cr, x, y, w, h, n)
|
||||||
|
local x1 = x
|
||||||
|
local x2 = x - w
|
||||||
|
local grid_line_spacing = h / n
|
||||||
|
local f = function(i)
|
||||||
|
local y1 = y + (i - 1) * grid_line_spacing - 0.5
|
||||||
|
local p1 = geom.make_point(x1, y1)
|
||||||
|
local p2 = geom.make_point(x2, y1)
|
||||||
|
return path.create_line(cr, p1, p2)
|
||||||
|
end
|
||||||
|
return pure.map_n(f, n)
|
||||||
|
end
|
||||||
|
|
||||||
|
local make_grid_paths = function(cr, x, y, w, h, nx, ny)
|
||||||
|
return {
|
||||||
|
x = make_x_grid(cr, x, y, w, h, nx),
|
||||||
|
y = make_y_grid(cr, x, y, w, h, ny),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local make_outline = function(cr, right_x, y, w, h)
|
||||||
|
local x1 = right_x - w
|
||||||
|
local y1 = y - 0.5
|
||||||
|
local x2 = right_x + 0.5
|
||||||
|
local y2 = y + h + 1.0
|
||||||
|
local p1 = geom.make_point(x1, y1)
|
||||||
|
local p2 = geom.make_point(x1, y2)
|
||||||
|
local p3 = geom.make_point(x2, y2)
|
||||||
|
return path.create_open_poly(cr, {p1, p2, p3})
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make_plotarea_paths = function(cr, right_x, y, w, h, num_x, num_y)
|
||||||
|
return {
|
||||||
|
grid = make_grid_paths(cr, right_x, y, w, h, num_x, num_y),
|
||||||
|
outline = make_outline(cr, right_x, y, w, h),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make_sources = function(x, y, width, config)
|
||||||
|
local p1 = geom.make_point(x, y)
|
||||||
|
local p2 = geom.make_point(x + width, y)
|
||||||
|
return {
|
||||||
|
grid = source.solid_color(config.grid_config.pattern),
|
||||||
|
outline = source.solid_color(config.outline_color),
|
||||||
|
series = {
|
||||||
|
line = source.linear_pattern(config.data_line_pattern, p1, p2),
|
||||||
|
fill = source.linear_pattern(config.data_fill_pattern, p1, p2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- impure
|
||||||
|
|
||||||
|
M.insert_data_point = function(y, h, n, series, value)
|
||||||
|
__table_insert(series, 1, y + h * (1 - value))
|
||||||
|
if #series == n + 2 then
|
||||||
|
series[#series] = nil
|
||||||
|
end
|
||||||
|
return series
|
||||||
|
end
|
||||||
|
|
||||||
|
M.draw_grid = function(cr, grid, _source)
|
||||||
|
style.set_line_style(GRID_CONFIG, cr)
|
||||||
|
-- TODO this sets the same source twice and strokes twice
|
||||||
|
shape.draw_paths_with_source(grid.x, cr, _source)
|
||||||
|
shape.draw_paths_with_source(grid.y, cr, _source)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.draw_outline = function(cr, _path, _source)
|
||||||
|
style.set_open_poly_style(OUTLINE_STYLE, cr)
|
||||||
|
shape.draw_path_with_source(_path, cr, _source)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.draw_series = function(cr, right_x, bottom_y, dx, series, sources)
|
||||||
|
style.set_open_poly_style(DATA_STYLE, cr)
|
||||||
|
local n = #series
|
||||||
|
|
||||||
|
-- TODO this accounts for up to 20% of the execution time once the series
|
||||||
|
-- reaches its max length
|
||||||
|
__cairo_move_to(cr, right_x, series[1])
|
||||||
|
|
||||||
|
for j = 2, n do
|
||||||
|
__cairo_line_to(cr, right_x - (j - 1) * dx, series[j])
|
||||||
|
end
|
||||||
|
|
||||||
|
__cairo_line_to(cr, right_x - (n - 1) * dx, bottom_y)
|
||||||
|
__cairo_line_to(cr, right_x, bottom_y)
|
||||||
|
__cairo_set_source(cr, sources.fill)
|
||||||
|
__cairo_fill_preserve(cr)
|
||||||
|
|
||||||
|
__cairo_set_source(cr, sources.line)
|
||||||
|
__cairo_stroke(cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,46 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local ti = require 'text_internal'
|
||||||
|
local pure = require 'pure'
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- pure
|
||||||
|
|
||||||
|
local make_x_label_text = function(y, chars, font)
|
||||||
|
return ti.make_htext(
|
||||||
|
y + ti.get_delta_y('bottom', font),
|
||||||
|
ti.x_align_function('center', font)(chars),
|
||||||
|
chars
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
local X_LABEL_PAD = 8
|
||||||
|
|
||||||
|
M.get_x_axis_height = function(font)
|
||||||
|
return ti.font_height(font) + X_LABEL_PAD
|
||||||
|
end
|
||||||
|
|
||||||
|
M.make = function(y, n, format_fun, font)
|
||||||
|
local f = function(i)
|
||||||
|
return make_x_label_text(y, format_fun((i - 1) / (n - 1)), font)
|
||||||
|
end
|
||||||
|
return pure.map_n(f, n)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.get_x_label_positions = function(right_x, w, x_labels)
|
||||||
|
local n = #x_labels
|
||||||
|
local f = function(i)
|
||||||
|
return right_x - w * (1 - (i - 1) / (n - 1))
|
||||||
|
end
|
||||||
|
return pure.map_n(f, n)
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- impure
|
||||||
|
|
||||||
|
M.draw = function(obj, cr, positions)
|
||||||
|
ti.set_font_spec(cr, obj.font, obj.source)
|
||||||
|
pure.each2(ti.draw_htext_at, obj.labels, positions, cr)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
|
@ -0,0 +1,47 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local geom = require 'geom'
|
||||||
|
local text = require 'text'
|
||||||
|
local ti = require 'text_internal'
|
||||||
|
|
||||||
|
local Y_LABEL_PAD = 5
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- pure
|
||||||
|
|
||||||
|
local make_y_label_text = function(point, chars, font)
|
||||||
|
return ti.make_text(
|
||||||
|
point.x,
|
||||||
|
point.y + ti.get_delta_y('center', font),
|
||||||
|
chars
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- TODO this function smells funny
|
||||||
|
M.make = function(point, h, n, font, y_format, scale_factor)
|
||||||
|
local y_labels = {width = 0}
|
||||||
|
local f = y_format(scale_factor)
|
||||||
|
for i = 1, n do
|
||||||
|
local z = (i - 1) / (n - 1)
|
||||||
|
local l = make_y_label_text(
|
||||||
|
geom.make_point(point.x, point.y + z * h),
|
||||||
|
f((1 - z) * scale_factor),
|
||||||
|
font
|
||||||
|
)
|
||||||
|
local w = ti.get_width(l.chars, font)
|
||||||
|
if w > y_labels.width then
|
||||||
|
y_labels.width = w
|
||||||
|
end
|
||||||
|
y_labels[i] = l
|
||||||
|
end
|
||||||
|
y_labels.width = y_labels.width + Y_LABEL_PAD
|
||||||
|
return y_labels
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- impure
|
||||||
|
|
||||||
|
M.draw = text.draw
|
||||||
|
|
||||||
|
return M
|
Loading…
Reference in New Issue