Files
cell/prosperon/clay.cm
2025-08-04 21:29:33 -05:00

311 lines
8.6 KiB
Plaintext

// 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('prosperon/draw2d')
var graphics = use('graphics')
var util = use('util')
var input = use('input')
var prosperon = use('prosperon')
function normalizeSpacing(spacing) {
if (typeof spacing == 'number') {
return {l: spacing, r: spacing, t: spacing, b: spacing}
} else if (Array.isArray(spacing)) {
if (spacing.length == 2) {
return {l: spacing[0], r: spacing[0], t: spacing[1], b: spacing[1]}
} else if (spacing.length == 4) {
return {l: spacing[0], r: spacing[1], t: spacing[2], b: spacing[3]}
}
} else if (typeof spacing == 'object') {
return {l: spacing.l || 0, r: spacing.r || 0, t: spacing.t || 0, b: spacing.b || 0}
} else {
return {l:0, r:0, t:0, b:0}
}
}
var lay_ctx = layout.make_context();
var clay_base = {
font: null,
background_image: null,
slice: 0,
font: 'smalle.16',
font_size: null,
color: {r:1,g:1,b:1,a:1},
spacing:0,
padding:0,
margin:0,
offset:{x:0, y:0},
size:null,
background_color: null
};
var root_item;
var root_config;
var boxes = [];
var clay = {}
var focused_textbox = null
clay.behave = layout.behave;
clay.contain = layout.contain;
clay.draw = function draw(fn)
{
var size = [prosperon.logical.width, prosperon.logical.height]
lay_ctx.reset();
boxes = [];
var root = lay_ctx.item();
// Accept both array and object formats
if (Array.isArray(size)) {
size = {width: size[0], height: size[1]};
}
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 = normalizeSpacing(box.config.padding || 0);
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 = 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.height;
box.boundingbox.y *= -1;
box.boundingbox.y += size.height;
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.width * (img.rect?.width || 1), img.height * (img.rect?.height || 1)];
}
function add_item(config)
{
// Normalize the child's margin
var margin = normalizeSpacing(config.margin || 0);
var padding = normalizeSpacing(config.padding || 0);
var childGap = root_config.child_gap || 0;
// Adjust for child_gap
root_config._childIndex ??= 0
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);
use_config.size ??= {width:0, height:0}
// Convert array to object if needed
if (Array.isArray(use_config.size)) {
use_config.size = {width: use_config.size[0], height: use_config.size[1]};
}
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 = path; // Store the path string, not the texture object
config.size ??= {width: image.width, height: image.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 = graphics.font_text_size(config.font, str, 0, config.size.x);
tsize.x = Math.ceil(tsize.x)
tsize.y = Math.ceil(tsize.y)
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 = graphics.font_text_size(config.font, str, 0, 0)
add_item(config);
config.text = str;
config.action = action;
}
clay.textbox = function(str, on_change, ...configs) {
var config = rectify_configs(configs)
config.on_change = on_change
config.text = str
config.font = graphics.get_font(config.font)
var tsize = graphics.font_text_size(config.font, str, 0, 0)
config.size ??= [0,0]
config.size = [Math.ceil(tsize.x), Math.ceil(tsize.y)]
config.size = [Math.max(config.size[0], config.size[0]), Math.max(config.size[1], config.size[1])]
add_item(config)
}
var point = use('point')
// Pure rendering function - no input handling
clay.draw_commands = function draw_commands(cmds, pos = {x:0,y:0})
{
for (var cmd of cmds) {
var config = cmd.config
var boundingbox = geometry.rect_move(cmd.boundingbox,point.add(pos,config.offset))
var content = geometry.rect_move(cmd.content,point.add(pos, config.offset))
// Check if this box should use hover styling
if (cmd.state && cmd.state.hovered && config.hovered) {
config.hovered.__proto__ = config
config = config.hovered
}
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.color, config.size.width)
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 = {x:0, y: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);
}
}
return clay