package.cpath = package.cpath .. ";../../target/debug/?.so" package.path = package.path .. ";../../lua-bindings/?.lua" local dia = require("diaphragm") -- TODO: add a way to have modified prototypes of these functions -- e.g.: local my_blocks = Blocks:new({ default_color = my_color }) -- my_blocks:new({ elements = ... }):draw() dia.util.eprint("--------------------") -- TODO: Fanwood has a high baseline, which renders weirdly in the GroupCommit layer, -- and "Goudy Bookletter 1911" doesn't work -- local serif_family = "Fanwood" -- local serif_family = "Latin Modern Math" -- local serif_family = "Goudy Bookletter 1911 Normal" local serif_family = "OFL Sorts Mill Goudy" -- local mono_family = "Fira Mono" local mono_family = "Iosevka" local upstream_colors = { base04 = "#cccccc", base06 = "#ffffff", base0B = "#5959ff", base0F = "#7f7f7f", } local nord_colors = { base00 = "#2E3440", base01 = "#3B4252", base02 = "#434C5E", base03 = "#4C566A", base04 = "#D8DEE9", base05 = "#E5E9F0", base06 = "#ECEFF4", base07 = "#8FBCBB", base08 = "#BF616A", base09 = "#D08770", base0A = "#EBCB8B", base0B = "#A3BE8C", base0C = "#88C0D0", base0D = "#81A1C1", base0E = "#B48EAD", base0F = "#5E81AC", } -- local colors = upstream_colors local colors = nord_colors local active_color = colors.base0B local background = colors.base06 local light_gray = colors.base04 local dark_gray = colors.base0F local block_unit_width = 24 local block_height = 36 local block_stroke_width = 2 local normal_margin = 20 local small_margin = 10 local function blocks(params) local result = dia.shape(params) result.unit_width = result.unit_width or dia.float.new() result.default_color = result.default_color or light_gray result.default_stroke_width = result.default_stroke_width or block_stroke_width result.blocks = dia.util.tbl_map(function(el) return dia.rectangle.new({ fill_color = el.color or result.default_color, stroke_width = result.default_stroke_width, height = result.height, width = result.unit_width * (el.grow or 1), }) end, params.elements) function result:draw() local len = #self.elements assert(len >= 1, "Blocks must have at least 1 element") local total_grow = 0 for _, el in pairs(self.elements) do total_grow = total_grow + (el.grow or 1) end dia.constrain(self.width:eq(self.unit_width * total_grow)) dia.layout .hstack({ elements = self.blocks, top_left = self.top_left, bottom_right = self.bottom_right, width = self.width, }) :draw() end return result end local function active_txn(params) return blocks(dia.util.tbl_extend({ elements = { {}, {}, {}, {}, {} }, height = block_height, default_color = active_color, }, params)) end local function fixed_blocks(params) local elements = {} for _ = 1, params.count do table.insert(elements, {}) end return blocks({ elements = elements, height = block_height, unit_width = block_unit_width, default_color = params.color or dark_gray, }) end local function block_list(params) local result = dia.shape(params) result.delimiter_height = result.delimiter_height or dia.float.new() result.font = result.font or dia.text.font({ family = serif_family }) function result:draw() local opening_delimiter = dia.text.new({ content = "[", height = self.delimiter_height, font = self.font, }) local closing_delimiter = dia.text.new({ content = "]", height = self.delimiter_height, font = self.font, }) local function comma() return dia.text.new({ content = ", ", font = self.font, }) end local elements = dia.util.list_intersperse_with(comma, self.elements) elements = { opening_delimiter, table.unpack(elements) } table.insert(elements, closing_delimiter) -- TODO: not really elegant dia.constrain(self.delimiter_height:eq(self.elements[1].height * 2.5)) dia.layout .hstack({ elements = elements, align = "center", top_left = self.top_left, bottom_right = self.bottom_right, }) :draw() end return result end local function disk_log_legend(params) local result = dia.shape(params) local function lines(params) local result = dia.shape(params) result.ticks = {} for _, tick in ipairs(params.ticks) do table.insert( result.ticks, dia.straight_path.new({ points = { { x = tick, y = result.top }, { x = tick, y = result.bottom }, }, }) ) end result.baseline = dia.straight_path.new({ points = { result.middle_left, result.middle_right }, width = result.width, }) function result:draw() for _, tick in ipairs(self.ticks) do tick:draw() end self.baseline:draw() end return result end result.lines = lines({ ticks = params.ticks, top_left = result.top_left, height = result.height / 2, width = result.width, }) result.font = dia.text.font({ family = serif_family }) result.labels = {} for i = 1, #params.ticks - 1 do local label = dia.text.new({ content = params.labels[i], height = (result.height / 2) + result.height / 6, top = result.lines.bottom - result.height / 6, horiz_center = (params.ticks[i] + params.ticks[i + 1]) / 2, font = result.font, }) table.insert(result.labels, label) end function result:draw() self.lines:draw() for _, label in ipairs(self.labels) do label:draw() end end return result end local function disk_log(params) local result = dia.shape(params) local anchor_ids = {} local last_group_id = 0 local labels = {} local elements = {} for _, global_spec in pairs(params.elements) do for _, spec in pairs(global_spec.elements) do local spec_params = spec.params or {} for _ = 1, spec.count do table.insert(elements, spec_params) end last_group_id = last_group_id + spec.count end table.insert(anchor_ids, last_group_id) table.insert(labels, global_spec.label) end result.blocks = blocks({ elements = elements, height = block_height, top_left = result.top_left, width = result.width, }) result.anchors = { result.left } for _, id in pairs(anchor_ids) do table.insert(result.anchors, result.blocks.blocks[id].right) end result.legend = disk_log_legend({ ticks = result.anchors, labels = labels, height = normal_margin * 2, width = result.width, bottom_right = result.bottom_right, }) dia.constraint.below(result.legend, result.blocks, normal_margin) function result:draw() result.blocks:draw() result.legend:draw() end return result end local function layer_entry(params) local result = dia.shape(params) result.text = params.text or dia.text.new({ content = params.name .. ": ", font = params.font, }) result.text_vert_center = params.text_vert_center or params.content.vert_center function result:draw() dia.constraint.left_of(self.text, self.content) dia.constrain(self.text.vert_center:eq(self.text_vert_center)) dia.constrain(self.left:eq(self.text.left)) dia.constrain(self.right:eq(self.content.right)) dia.constrain(self.top:eq(dia.float.min({ self.text.top, self.content.top }))) dia.constrain(self.bottom:eq(dia.float.max({ self.text.bottom, self.content.bottom }))) self.text:draw() self.content:draw() end return result end local function layer(params) local result = dia.shape(params) result.name = result.name or "Layer" result.title = dia.text.new({ content = result.name, font = result.title_font, top_left = result.top_left + { x = normal_margin, y = normal_margin }, }) result.content = result.content or dia.rectangle.new({ height = 100, width = 100, }) -- TODO: seems hacky result.right:minimize() function result:draw() dia.rectangle .new({ top_left = self.top_left, bottom_right = self.bottom_right, fill_color = background, }) :draw() dia.constrain(self.right:eq(self.content.right + normal_margin)) dia.constraint.below(self.content, self.title) dia.constrain(self.left:lt(self.content.left - 150)) dia.constrain(self.bottom:eq(dia.float.max({ self.title.bottom, self.content.bottom }) + normal_margin)) self.title:draw() self.content:draw() end return result end dia.draw({ draw = function(self) local title_font = dia.text.font({ family = serif_family, size = 48 }) local entry_font = dia.text.font({ family = mono_family, size = 20 }) local action_font = dia.text.font({ family = mono_family, height = normal_margin }) local function action(name) return dia.layout.margin_top({ margin_top = small_margin, content = dia.text.new({ content = '' .. name .. "", font = action_font, height = normal_margin, }), }) end local layer_right = dia.float.new() local log_layer_entry_left = dia.float.new() local group_commit_entry_left = dia.float.new() local disk_log_entry_left = dia.float.new() local layers = { layer({ name = "LogAPI", title_font = title_font, content = dia.layout.margin_right({ content = layer_entry({ name = "activeTxn", font = entry_font, content = active_txn({ unit_width = block_unit_width, left = log_layer_entry_left, }), }), }), }), action("commit"), layer({ name = "GroupCommit", title_font = title_font, -- top_left = { x = 20, y = 100 + 40 }, -- right = layer_right, content = dia.layout.margin_right({ -- HACK: because I know that "GroupCommit" + "commitedTxns:" -- takes the most space margin_right = 0, content = layer_entry({ name = "commitedTxns", font = entry_font, content = block_list({ elements = { fixed_blocks({ count = 2 }), fixed_blocks({ count = 7 }), fixed_blocks({ count = 4 }), active_txn({ unit_width = block_unit_width }), }, left = group_commit_entry_left, }), }), }), }), action("flush"), layer({ name = "DiskLog", title_font = title_font, -- top_left = { x = 20, y = 100 + 40 }, right = layer_right, content = dia.layout.margin_right({ margin_right = 0, content = layer_entry({ -- TODO: align to blocks name = "disk log", font = entry_font, content = disk_log({ elements = { { -- TODO: seems to fail solving if using other font size within text, or line returns label = "header", elements = { { count = 1, params = { color = "#000", grow = 11 } }, }, }, { label = "data", elements = { { count = 2, params = { color = light_gray, grow = 13 + 5 } }, { count = 13, params = { color = dark_gray } }, { count = 5, params = { color = active_color } }, }, }, { label = "available", elements = { { count = 1, params = { color = "#fff", grow = 13 + 5 } }, }, }, }, left = disk_log_entry_left, }), }), }), }), action("apply"), layer({ name = "Applier", title_font = title_font, right = layer_right, }), } local figure = dia.layout.vstack({ elements = layers, align = "right" }) figure:draw() dia.constraint.inset(figure, self, { margin = normal_margin }) local layer_entries_text_right = dia.float.new() for _, entry_left in pairs({ log_layer_entry_left, group_commit_entry_left, disk_log_entry_left }) do dia.constrain(layer_entries_text_right:eq(entry_left)) end end, -- TODO: It currently silently panics if output is not there output = {}, }) -- TODO: anyone can use metatable? --[[ local Blocks = diaphragm.Drawable:new() Blocks.elements = {} Blocks.unit_width = diaphragm.float() function Blocks:draw() local len = #self.elements assert(len >= 1, "Blocks must have at least 1 element") local total_grow = 0 for el in self.elements do total_grow = el.grow or 1 end diaphragm.constrain(self.width == self.unit_width * total_grow) local param = self.elements[0] -- TODO: this would be written with layout.hstack local block = diaphragm.shape.Rectangle:new() block.fill(param.color) diaphragm.constrain(block.left() == self.left()) diaphragm.constrain(block.top() == self.top()) diaphragm.constrain(block.bottom() == self.bottom()) diaphragm.constrain(block.width() == (param.grow or 1) * self.unit_width) block.draw() local previous_right = block.right() for i = 2, len do param = self.elements[i] block = diaphragm.shape.Rectangle:new() block.fill(param.color) diaphragm.constrain(block.left() == previous_right) diaphragm.constrain(block.top() == self.top()) diaphragm.constrain(block.bottom() == self.bottom()) diaphragm.constrain(block.width() == (param.grow or 1) * self.unit_width) block.draw() previous_right = block.right() end end -- Library ---------- local function block(color) local res = diaphragm.shape.Rectangle:new() res.fill(color) return res end local function blocks_by_len(count, color) local res = {} for i = 1, count do res[i] = block(color) end -- TODO: rename cons? Misleading diaphragm.cons.same_size(res) return diaphragm.layout.hstack(res) end local function layer(config) local name = config.name or "" local entries = config.entries or {} -- TODO: how to do it without box? -- TODO: implement union or combine -- probably combine because -- union could remove some -- strokes return diaphragm.layout.box({}) end -- Parameters ------------- local blue = diaphragm.color.from_rgb(0.35, 0.35, 1.) local active_txn_count = 5 -- Log API ---------- local log_api = layer({ name = "LogAPI", entries = { { name = "activeTxn", content = blocks_by_len(active_txn_count, blue), }, }, }) -- Group Commit --------------- -- local group_commit = nil -- Disk Log ----------- -- local disk_log = nil -- Applier ---------- -- local applier = nil diaphragm.draw(diaphragm.layout.vstack({ log_api, -- group_commit, -- disk_log, -- applier, })) ]]