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]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
dev_power:
|
||||
description: the sysfs path to the graphics card power indicator
|
||||
type: string
|
||||
show_temp:
|
||||
description: show the GPU temp
|
||||
type: boolean
|
||||
|
|
28
conky.conf
28
conky.conf
|
@ -3,16 +3,14 @@
|
|||
|
||||
local conky_dir = debug.getinfo(1).source:match("@?(.*/)")
|
||||
local subdirs = {
|
||||
'?.lua',
|
||||
'drawing/?.lua',
|
||||
'schema/?.lua',
|
||||
'core/?.lua',
|
||||
'core/widget/?.lua',
|
||||
'core/widget/arc/?.lua',
|
||||
'core/widget/text/?.lua',
|
||||
'core/widget/timeseries/?.lua',
|
||||
'core/widget/rect/?.lua',
|
||||
'core/widget/line/?.lua',
|
||||
'src/?.lua',
|
||||
'src/modules/?.lua',
|
||||
'src/widget/?.lua',
|
||||
'src/widget/arc/?.lua',
|
||||
'src/widget/text/?.lua',
|
||||
'src/widget/timeseries/?.lua',
|
||||
'src/widget/rect/?.lua',
|
||||
'src/widget/line/?.lua',
|
||||
'lib/share/lua/5.4/?.lua',
|
||||
'lib/share/lua/5.4/?/init.lua',
|
||||
}
|
||||
|
@ -41,7 +39,7 @@ if i_o.exe_exists('yajsv') then
|
|||
end
|
||||
else
|
||||
validate_config = function(_)
|
||||
print('WARNING: could not validate config')
|
||||
i_o.warnf('could not validate config')
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
@ -52,16 +50,16 @@ local find_valid_config = function(paths)
|
|||
local r = i_o.read_file(path)
|
||||
if r ~= nil 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)
|
||||
else
|
||||
i_o.printf('WARNING: %s did not pass; trying next', path)
|
||||
i_o.warnf('%s did not pass; trying next', path)
|
||||
end
|
||||
else
|
||||
i_o.printf('INFO: could not find %s; trying next', path)
|
||||
i_o.infof('could not find %s; trying next', path)
|
||||
end
|
||||
end
|
||||
assert(false, 'ERROR: could not load valid config')
|
||||
i_o.assertf(false, 'ERROR: could not load valid config')
|
||||
end
|
||||
|
||||
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 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)
|
||||
local M = {}
|
||||
|
||||
local patterns = compile_patterns(config.theme.patterns)
|
||||
local patterns = color(config.theme.patterns)
|
||||
local font = config.theme.font
|
||||
local font_sizes = font.sizes
|
||||
local font_family = font.family
|
|
@ -10,7 +10,7 @@ return function(config, main_state, common, width, point)
|
|||
-----------------------------------------------------------------------------
|
||||
-- smartd
|
||||
|
||||
i_o.exe_assert('pidof')
|
||||
i_o.assert_exe_exists('pidof')
|
||||
|
||||
local mk_smart = function(y)
|
||||
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 paths = pure.map_keys('path', 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(
|
||||
pure.partial(string.format, '${fs_used_perc %s}', true),
|
||||
paths
|
|
@ -14,7 +14,8 @@ return function(update_freq, config, common, width, point)
|
|||
-----------------------------------------------------------------------------
|
||||
-- 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
|
||||
--
|
||||
|
@ -38,8 +39,6 @@ return function(update_freq, config, common, width, point)
|
|||
'(%d+),(%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 = {
|
||||
error = false,
|
||||
used_memory = 0,
|
||||
|
@ -52,7 +51,7 @@ return function(update_freq, config, common, width, point)
|
|||
}
|
||||
|
||||
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)
|
||||
if nvidia_settings_glob == '' then
|
||||
mod_state.error = 'Error'
|
|
@ -39,12 +39,10 @@ return function(update_freq, config, main_state, common, width, point)
|
|||
if math.fmod(ncores, config.core_rows) == 0 then
|
||||
show_cores = true
|
||||
else
|
||||
print(
|
||||
string.format(
|
||||
'WARNING: could not evenly distribute %i cores over %i rows',
|
||||
ncores,
|
||||
config.core_rows
|
||||
)
|
||||
i_o.warnf(
|
||||
'could not evenly distribute %i cores over %i rows; disabling',
|
||||
ncores,
|
||||
config.core_rows
|
||||
)
|
||||
end
|
||||
end
|
|
@ -1,6 +1,8 @@
|
|||
local format = require 'format'
|
||||
local pure = require 'pure'
|
||||
local sys = require 'sys'
|
||||
local i_o = require 'i_o'
|
||||
local impure = require 'impure'
|
||||
|
||||
return function(update_freq, config, common, width, point)
|
||||
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 device_paths = sys.get_disk_paths(config.devices)
|
||||
|
||||
impure.each(i_o.assert_file_exists, device_paths)
|
||||
|
||||
local update_state = function()
|
||||
mod_state.read, mod_state.write = sys.get_total_disk_io(device_paths)
|
||||
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