// Layout code // Contain is for how it will treat its children. If they should be laid out as a row, or column, or in a flex style, etc. var layout = use('layout') var geometry = use('geometry') var draw = use('draw2d') var graphics = use('graphics') var util = use('util') var input = use('input') var lay_ctx = layout.make_context(); var clay_base = { font: null, background_image: null, slice: 0, font: 'smalle.16', font_size: null, color: [1,1,1,1], spacing:0, padding:0, margin:0, offset:[0,0], size:null, background_color: null }; var root_item; var root_config; var boxes = []; var clay = {} clay.behave = layout.behave; clay.contain = layout.contain; clay.draw = function draw(size, fn, config = {}) { lay_ctx.reset(); boxes = []; var root = lay_ctx.item(); lay_ctx.set_size(root,size); lay_ctx.set_contain(root,layout.contain.row); root_item = root; root_config = Object.assign({}, clay_base); boxes.push({ id:root, config:root_config }); fn(); lay_ctx.run(); // Adjust bounding boxes for padding for (var i = 0; i < boxes.length; i++) { var box = boxes[i]; box.content = lay_ctx.get_rect(box.id); box.boundingbox = Object.assign({}, box.content); var padding = util.normalizeSpacing(box.config.padding || 0); if (padding.l || padding.r || padding.t || padding.b) { // Adjust the boundingbox to include the padding box.boundingbox.x -= padding.l; box.boundingbox.y -= padding.t; box.boundingbox.width += padding.l + padding.r; box.boundingbox.height += padding.t + padding.b; } box.marginbox = Object.assign({}, box.content); var margin = util.normalizeSpacing(box.config.margin || 0); box.marginbox.x -= margin.l; box.marginbox.y -= margin.t; box.marginbox.width += margin.l+margin.r; box.marginbox.height += margin.t+margin.b; box.content.y *= -1; box.content.y += size.y; box.boundingbox.y *= -1; box.boundingbox.y += size.y; box.content.anchor_y = 1; box.boundingbox.anchor_y = 1; } return boxes; } function create_view_fn(base_config) { var base = Object.assign(Object.create(clay_base), base_config); return function view(config = {}, fn) { config.__proto__ = base; var item = add_item(config); var prev_item = root_item; var prev_config = root_config; root_item = item; root_config = config; root_config._childIndex = 0; // Initialize child index fn?.(); root_item = prev_item; root_config = prev_config; } } clay.vstack = create_view_fn({ contain: layout.contain.column | layout.contain.start, }); clay.hstack = create_view_fn({ contain: layout.contain.row | layout.contain.start, }); clay.spacer = create_view_fn({ behave: layout.behave.hfill | layout.behave.vfill }); function image_size(img) { return [img.rect[2]*img.texture.width, img.rect[3]*img.texture.height]; } function add_item(config) { // Normalize the child's margin var margin = util.normalizeSpacing(config.margin || 0); var padding = util.normalizeSpacing(config.padding || 0); var childGap = root_config.child_gap || 0; // Adjust for child_gap if (root_config._childIndex > 0) { var parentContain = root_config.contain || 0; var isVStack = (parentContain & layout.contain.column) != 0; var isHStack = (parentContain & layout.contain.row) != 0; if (isVStack) { margin.t += childGap; } else if (isHStack) { margin.l += childGap; } } var use_config = Object.create(config); use_config.margin = { t: margin.t+padding.t, b: margin.b+padding.b, r:margin.r+padding.r, l:margin.l+padding.l }; var item = lay_ctx.item(); lay_ctx.set_margins(item, use_config.margin); lay_ctx.set_size(item,use_config.size); lay_ctx.set_contain(item,use_config.contain); lay_ctx.set_behave(item,use_config.behave); boxes.push({ id:item, config:use_config }); lay_ctx.insert(root_item,item); // Increment the parent's child index root_config._childIndex++; return item; } function rectify_configs(config_array) { if (config_array.length == 0) config_array = [{}]; for (var i = config_array.length-1; i > 0; i--) config_array[i].__proto__ = config_array[i-1]; config_array[0].__proto__ = clay_base; var cleanobj = Object.create(config_array[config_array.length-1]); return cleanobj; } clay.image = function image(path, ...configs) { var config = rectify_configs(configs); var image = graphics.texture(path); config.image = image; config.size ??= [image.texture.width, image.texture.height]; add_item(config); } clay.text = function text(str, ...configs) { var config = rectify_configs(configs); config.size ??= [0,0]; config.font = graphics.get_font(config.font) var tsize = config.font.text_size(str, 0, 0, config.size.x); config.size = config.size.map((x,i) => Math.max(x, tsize[i])); config.text = str; add_item(config); } /* For a given size, the layout engine should "see" size + margin but its interior content should "see" size - padding hence, the layout box should be size-padding, with margin of margin+padding */ var button_base = Object.assign(Object.create(clay_base), { padding:0, hovered:{ } }); clay.button = function button(str, action, config = {}) { config.__proto__ = button_base; config.font = graphics.get_font(config.font) config.size = config.font.text_size(str) add_item(config); config.text = str; config.action = action; } var hovered = null; clay.newframe = function() { hovered = null; } // mousepos given in hud coordinates clay.draw_commands = function draw_commands(cmds, pos = [0,0], mousepos = prosperon.camera.screen2hud(input.mouse.screenpos())) { for (var cmd of cmds) { var config = cmd.config; var boundingbox = geometry.rect_move(cmd.boundingbox,pos.add(config.offset)); var content = geometry.rect_move(cmd.content,pos.add(config.offset)); if (config.hovered && geometry.rect_point_inside(boundingbox, mousepos)) { config.hovered.__proto__ = config; config = config.hovered; hovered = config; } if (config.background_image) if (config.slice) draw.slice9(config.background_image, boundingbox, config.slice, config.background_color); else draw.image(config.background_image, boundingbox, 0, config.color); else if (config.background_color) draw.rectangle(boundingbox, config.background_color); if (config.text) draw.text(config.text, content, config.font, config.font_size, config.color, config.size.x); if (config.image) draw.image(config.image, content, 0, config.color); } } var dbg_colors = {}; clay.debug_colors = dbg_colors; dbg_colors.content = [1,0,0,0.1]; dbg_colors.boundingbox = [0,1,0,0,0.1]; dbg_colors.margin = [0,0,1,0.1]; clay.draw_debug = function draw_debug(cmds, pos = [0,0]) { for (var i = 0; i < cmds.length; i++) { var cmd = cmds[i]; var boundingbox = geometry.rect_move(cmd.boundingbox,pos); var content = geometry.rect_move(cmd.content,pos); draw.rectangle(content, dbg_colors.content); draw.rectangle(boundingbox, dbg_colors.boundingbox); // draw.rectangle(geometry.rect_move(cmd.marginbox,pos), dbg_colors.margin); } } clay.inputs = {}; clay.inputs.mouse = {} clay.inputs.mouse.left = function() { if (hovered && hovered.action) hovered.action(); } return clay