Files
Ronald Caesar 5f0791e8b1 extern/luajit-imgui: add source code properly
This adds luajit-imgui without submodules.

Signed-off-by: Ronald Caesar <github43132@proton.me>
2026-06-11 14:12:55 -04:00

555 lines
17 KiB
Lua

local igwin = require"imgui.window"
--local win = igwin:SDL(800,400, "compute graph",{vsync=true})
local win = igwin:GLFW(800,600, "compute graph",{vsync=false})
local ig = win.ig
local ffi = require"ffi"
local serializer = require"libs.serializer"
local function DFS(G,v, editor)
local is_root = editor.nodes[v] and editor.nodes[v].is_root
G.nodes_explored[v] = true
local values = {}
for i,id in ipairs(G.nodes[v].fromedges) do
if is_root then G.nodes_explored = {v=true} end
local value
if not G.nodes_explored[id] then
value = G.nodes_values[id]
if not value then
value = DFS(G,id, editor)
G.nodes_values[id] = value
--else
--print"already calc from other root"
end
else
value = G.nodes_values[id]
print("cicle found--",value,v,id,G.nodes[v].linkids[i])
editor:deleteLink(G.nodes[v].linkids[i])
if not value then
value = 0 --G.old_nodes_values[id]
print("cicle found",value)
end
end
table.insert(values,value)
end
return G.nodes[v].compute(values)
end
local function Graph()
local G = {nodes={},edges={}}
function G:insert_node(id, compute)
self.nodes[id] = {fromedges={},linkids = {},compute=compute,kind="?"}
end
function G:delete_node(id)
local fromedges = self.nodes[id].fromedges
for i,id1 in ipairs(fromedges) do
self:delete_edge(id1,id)
end
self.nodes[id] = nil
end
function G:insert_edge(id1,id2, linkid)
local fromedgs = self.nodes[id2].fromedges
local linkids = self.nodes[id2].linkids
table.insert(fromedgs,id1)
table.insert(linkids,linkid or 0)
end
function G:delete_edge(id1,id2)
local fromedgs = self.nodes[id2].fromedges
local linkids = self.nodes[id2].linkids
for i,id in ipairs(fromedgs) do
if id == id1 then
table.remove(fromedgs,i)
table.remove(linkids,i)
end
end
end
function G:DFS_prepare()
G.nodes_explored = {}
G.old_nodes_values = G.nodes_values or {}
G.nodes_values = {}
end
function G:DFS(root, editor)
return DFS(self,root, editor)
end
return G
end
local function Link()
local link = {id=0,start_attr=ffi.new("int[?]",1),end_attr=ffi.new("int[?]",1)}
function link:save_str()
return "{id = " .. self.id ..
", start_attr = " .. self.start_attr[0] ..
", end_attr = " .. self.end_attr[0] .. "}"
end
function link:loadT(t)
self.id = t.id
self.start_attr[0] = t.start_attr
self.end_attr[0] = t.end_attr
end
return link
end
local function Node(value,editor,typen,loadT)
local node
if not loadT then
node = {
id = editor:newid(),
type = typen.name,
is_root = typen.is_root
}
node.inputs = {}
node.input_names = {}
for i,iname in ipairs(typen.input_names) do
node.inputs[i] = editor:newid()
node.input_names[i] = iname
end
if not typen.is_root then
node.output_id = editor:newid() --node.id
end
-- create static_id
node.values = {}
for i ,input_id in ipairs(node.inputs) do
node.values[i] = ffi.new("float[?]",1,value)
end
else
node = loadT
end
local typename = node.type
for i,typ in ipairs(editor.nodetypes) do
if typename==typ.name then
node.compute = typ.compute
node.show = typ.show
break
end
end
-------------add pins to Graph
local function computeIn(i)
return function(t)
if t[1] then
return t[1]
else
return node.values[i][0]
end
end
end
for i ,input_id in ipairs(node.inputs) do
editor.G:insert_node(input_id,computeIn(i))
end
if node.output_id then
editor.G:insert_node(node.output_id,node.compute)
for _ ,input_id in ipairs(node.inputs) do
editor.G:insert_edge(input_id,node.output_id)
end
end
--add root node
if node.is_root then
editor.G:insert_node(node.id,node.compute)
for _ ,input_id in ipairs(node.inputs) do
editor.G:insert_edge(input_id,node.id)
end
end
----------------------
function node:delete()
--delete pins from graph
for _ ,input_id in ipairs(self.inputs) do
editor.G:delete_node(input_id)
end
if node.output_id then
editor.G:delete_node(self.output_id)
end
if node.type=="output" then
editor.G:delete_node(node.id)
end
end
function node:hasLink(link)
for i ,input_id in ipairs(self.inputs) do
if link.end_attr[0] == input_id then return true end
end
return link.start_attr[0] == self.output_id
end
function node:save_str(name)
self.pos = ig.imnodes_GetNodeGridSpacePos(self.id)
return serializer(name,self)
end
function node:draw()
if self.pos then -- for reset position of saved and loaded node
ig.imnodes_SetNodeGridSpacePos(self.id, self.pos)
self.pos = nil
end
ig.imnodes_BeginNode(node.id);
ig.imnodes_BeginNodeTitleBar();
ig.TextUnformatted(node.type);
ig.imnodes_EndNodeTitleBar();
for i, input_id in ipairs(node.inputs) do
ig.imnodes_BeginInputAttribute(input_id)
ig.TextUnformatted(node.input_names[i]);
ig.imnodes_EndInputAttribute();
--if there is no input
local orig = editor.G.nodes[input_id].fromedges
if #orig==0 then
ig.SameLine()
ig.imnodes_BeginStaticAttribute(input_id)
ig.PushItemWidth(80.0);
ig.DragFloat("##value"..i, node.values[i], 0.01);
ig.PopItemWidth();
ig.imnodes_EndStaticAttribute();
end
end
for i,root in ipairs(editor.root_nodes) do
if root == self.id and editor.outs then
if editor.outs[i] then self:show(editor.outs[i],i) end
end
end
if node.output_id then
ig.imnodes_BeginOutputAttribute(node.output_id)
local text_width = ig.CalcTextSize("output").x;
ig.Indent(80. + ig.CalcTextSize("value").x - text_width);
ig.TextUnformatted("output");
ig.imnodes_EndOutputAttribute();
end
ig.imnodes_EndNode();
end
return node
end
local function show_editor(editor)
--Submit a window filling the entire viewport
local viewport = ig.GetMainViewport();
ig.SetNextWindowPos(viewport.WorkPos);
ig.SetNextWindowSize(viewport.WorkSize);
ig.SetNextWindowViewport(viewport.ID);
ig.Begin(editor.name);
ig.TextUnformatted("A -- add node");
ig.TextUnformatted("X -- delete selected node or link");
ig.imnodes_EditorContextSet(editor.context);
ig.imnodes_BeginNodeEditor();
local user_key = ig.lib.ImGuiKey_A
local open_popup
if (ig.IsWindowFocused(ig.lib.ImGuiFocusedFlags_RootAndChildWindows) and
ig.imnodes_IsEditorHovered() and ig.IsKeyReleased(user_key))
then
open_popup = true
end
ig.PushStyleVar(ig.lib.ImGuiStyleVar_WindowPadding, ig.ImVec2(8, 8))
if open_popup then ig.OpenPopup("add node") end
if ig.BeginPopup"add node" then
local click_pos = ig.GetMousePosOnOpeningCurrentPopup();
for i,ntype in ipairs(editor.nodetypes) do
if ig.MenuItem(ntype.name) then
local newnode = editor:Node(0,ntype)
if newnode then
ig.imnodes_SetNodeScreenSpacePos(newnode.id, click_pos)
end
end
end
ig.EndPopup()
end
ig.PopStyleVar()
for _, node in pairs(editor.nodes) do
node:draw()
end
for _, link in pairs(editor.links) do
ig.imnodes_Link(link.id, link.start_attr[0], link.end_attr[0]);
end
ig.imnodes_MiniMap()
ig.imnodes_EndNodeEditor();
--[[
local hovid = ffi.new("int[1]")
if ig.imnodes_IsNodeHovered(hovid) then
print(hovid[0],"hovered")
end
local hovid = ffi.new("int[1]")
if ig.imnodes_IsLinkHovered(hovid) then
print(hovid[0],"hovered link")
end
local hovid = ffi.new("int[1]")
if ig.imnodes_IsPinHovered(hovid) then
print(hovid[0],"hovered pin")
end
--]]
local link = Link()
if (ig.imnodes_IsLinkCreated(link.start_attr, link.end_attr)) then
--print("imnodes_IsLinkCreated",link.start_attr[0], link.end_attr[0])
editor:addLink(link)
end
local link_id = ffi.new("int[?]",1)
if (ig.imnodes_IsLinkDestroyed(link_id)) then
editor:deleteLink(link_id[0])
end
local dodelete = false
local user_key = ig.lib.ImGuiKey_X
if ig.IsWindowFocused(ig.lib.ImGuiFocusedFlags_RootAndChildWindows) and
ig.IsKeyReleased(user_key)
then
dodelete = true
end
local num_selected = ig.imnodes_NumSelectedLinks();
if (num_selected > 0 and dodelete) then
local selected_links = ffi.new("int[?]",num_selected)
ig.imnodes_GetSelectedLinks(selected_links);
for i=0,num_selected-1 do
editor:deleteLink(selected_links[i])
end
end
local num_selected = ig.imnodes_NumSelectedNodes();
if (num_selected > 0 and dodelete) then
local selected_nodes = ffi.new("int[?]",num_selected)
ig.imnodes_GetSelectedNodes(selected_nodes);
for i=0,num_selected-1 do
editor:deleteNode(selected_nodes[i])
end
end
ig.End();
-- The outputs
editor.outs = editor:evaluate()
end
local function Editor(name, nodetypes)
local E = {nodes={},links={},current_id=0,name=name,root_nodes={}, nodetypes= nodetypes}
E.G = Graph()
function E:evaluate()
local outs = {}
self.G:DFS_prepare()
for i,root in ipairs(self.root_nodes) do
self.G.nodes_explored = {}
outs[i] = self.G:DFS(root, self)
end
return outs
end
function E:newid()
E.current_id = E.current_id + 1
return E.current_id
end
function E:Node(value,typen)
local newnode = Node(value,self,typen)
self.nodes[newnode.id] = newnode
if newnode.is_root then
table.insert(self.root_nodes, newnode.id)
end
return newnode
end
function E:deleteNode(node_id)
--delete links from this node
local node = self.nodes[node_id]
for _,link in pairs(self.links) do
if node:hasLink(link) then
self:deleteLink(link.id)
end
end
node:delete() --delete pins in graph
if node.is_root then
for i,v in ipairs(self.root_nodes) do
if v == node_id then
table.remove(self.root_nodes,i)
end
end
end
self.nodes[node_id] = nil
end
function E:addLink(link)
--only one link for input
local dest = self.G.nodes[link.end_attr[0]].fromedges
if #dest==0 then
link.id = self:newid();
self.links[link.id] = link
self.G:insert_edge(link.start_attr[0],link.end_attr[0],link.id)
return link
end
end
function E:deleteLink(link_id)
local link = self.links[link_id]
self.G:delete_edge(link.start_attr[0],link.end_attr[0])
self.links[link_id] = nil
end
E.draw = show_editor
function E:free()
ig.imnodes_EditorContextFree(self.context);
end
function E:save_str()
local str = [[local ffi = require"ffi"]]
str = str .. "\n"
for k,node in pairs(self.nodes) do
str = str .. node:save_str("node"..k) .. "\n"
end
str = str .. serializer("root_nodes",self.root_nodes)
str = str .. "return {nodes = {"
for k,node in pairs(self.nodes) do
local kst = type(k)=="number" and "["..k.."]" or k
str = str .. kst .. "=" .. ("node"..k) .. ","
end
str = str .. "},links = {"
for k,link in pairs(self.links) do
local kst = type(k)=="number" and "["..k.."]" or k
str = str .. kst .. "=" .. link:save_str() .. ","
end
str = str .. "},name='"..self.name
str = str .. "',current_id = " .. self.current_id
str = str .. ",root_nodes = root_nodes}"
return str
end
function E:save()
local str = self:save_str()
local file,err = io.open(self.name.."_saved","w")
if not file then print(err);error"opening file" end
file:write(str)
file:close()
end
function E:load()
local file,err = io.open(self.name.."_saved","r")
if file then
local str = file:read"*a"
file:close()
self:load_str(str)
end
end
function E:load_str(str)
self.nodes = {}
self.links = {}
local f = loadstring(str)
setfenv(f,setmetatable({ig=ig},{ __index = _G}))
local loadedE = f()
for k,v in pairs(loadedE.nodes) do
local node = Node(0,self,nil,v)
self.nodes[node.id] = node
end
for k,v in pairs(loadedE.links) do
local link = Link()
link:loadT(v)
self.links[link.id] = link
self.G:insert_edge(link.start_attr[0],link.end_attr[0],link.id)
end
self.current_id = loadedE.current_id
--self.name = loadedE.name
self.root_nodes = loadedE.root_nodes
end
E.context = ig.imnodes_EditorContextCreate();
return E
end
-----------------------------------use it!!--------------------------------------------
ig.imnodes_CreateContext()
local function clamp(v)
return math.max(0,math.min(1,v))
end
local nodetypes = {
{ name = "add",
input_names = {"lhs","rhs"},
compute = function(t)
return t[1] + t[2]
end
},{
name = "multiply",
input_names = {"lhs","rhs"},
compute = function(t)
return t[1] * t[2]
end
},{
name = "output",
input_names = {"r","g","b"},
is_root = true,
show = function(self,v,i)
local canvas_p0 = ig.GetCursorScreenPos();
local canvas_sz = ig.ImVec2(150,150)
local canvas_p1 = canvas_p0 + canvas_sz
local draw_list = ig.GetWindowDrawList();
ig.Dummy(canvas_sz)
if v then draw_list:AddRectFilled(canvas_p0, canvas_p1, v) end
end,
compute = function(t)
local a,b,c = clamp(t[1]),clamp(t[2]),clamp(t[3])
return ig.U32(a,b,c)
end
},{
name = "lisa",
input_names = {"x","y"},
is_root = true,
show = function(self,v,i)
local lisaS = 30
self.lisamem = self.lisamem or {}
local lisamem = self.lisamem
ig.Text("x: %f, y: %f",v[1],v[2])
local canvas_p0 = ig.GetCursorScreenPos(); -- ImDrawList API uses screen coordinates!
local canvas_sz = ig.ImVec2(150,150)--ig.GetContentRegionAvail();
local canvas_p1 = canvas_p0 + canvas_sz
local draw_list = ig.GetWindowDrawList();
ig.Dummy(canvas_sz)
draw_list:AddRectFilled(canvas_p0, canvas_p1, ig.U32(50/255, 50/255, 50/255, 1));
draw_list:AddRect(canvas_p0, canvas_p1, ig.U32(1, 1, 1, 1));
table.insert(lisamem ,1,v)
table.remove(lisamem,lisaS+1)
for i=1,lisaS do
local u = lisamem[i] or v
draw_list:AddCircleFilled(ig.ImVec2(u[1]*canvas_sz.x,u[2]*canvas_sz.y)+canvas_p0, 3, ig.U32(1,1,1,1));
end
end,
compute = function(t)
local x,y = clamp(t[1]),clamp(t[2])
return {x,y}
end
},{
name = "sine",
input_names = {"input"},
compute = function(t)
return math.sin(t[1])*0.5+0.5
end
},{
name = "time",
input_names = {},
compute = function(t)
return os.clock()
end
}
}
local editor1 = Editor("compute_graph", nodetypes)
editor1:load()
ig.imnodes_PushAttributeFlag(ig.lib.ImNodesAttributeFlags_EnableLinkDetachWithDragClick);
local iog = ig.imnodes_GetIO();
local KeyCtrlPtr = ffi.cast("bool*", ffi.cast("char*",ig.GetIO()) + ffi.offsetof("ImGuiIO","KeyCtrl"))
iog.LinkDetachWithModifierClick.Modifier = KeyCtrlPtr --ig.lib.getIOKeyCtrlPtr();
iog.EmulateThreeButtonMouse.Modifier = KeyCtrlPtr
function win:draw(ig)
editor1:draw()
ig.ShowDemoWindow()
end
local function clean()
editor1:save()
ig.imnodes_PopAttributeFlag();
editor1:free()
ig.imnodes_DestroyContext()
end
win:start(clean)