diff --git a/lua/autorun/wire_load.lua b/lua/autorun/wire_load.lua index 404705a750..b8ade49b5e 100644 --- a/lua/autorun/wire_load.lua +++ b/lua/autorun/wire_load.lua @@ -69,6 +69,10 @@ if SERVER then -- node editor AddCSLuaFile("wire/client/node_editor/nodeeditor.lua") AddCSLuaFile("wire/client/node_editor/wire_fpga_editor.lua") + + -- node editor + AddCSLuaFile("wire/client/segment_editor/mslcdeditor.lua") + AddCSLuaFile("wire/client/segment_editor/wire_mslcd_editor.lua") -- hl-zasm AddCSLuaFile("wire/client/hlzasm/hc_compiler.lua") @@ -150,6 +154,8 @@ if CLIENT then include("wire/client/customspawnmenu.lua") include("wire/client/node_editor/nodeeditor.lua") include("wire/client/node_editor/wire_fpga_editor.lua") + include("wire/client/segment_editor/mslcdeditor.lua") + include("wire/client/segment_editor/wire_mslcd_editor.lua") include("wire/client/hlzasm/hc_compiler.lua") end diff --git a/lua/entities/gmod_wire_addressbus.lua b/lua/entities/gmod_wire_addressbus.lua index efadda46f5..9afb40b31d 100644 --- a/lua/entities/gmod_wire_addressbus.lua +++ b/lua/entities/gmod_wire_addressbus.lua @@ -95,7 +95,7 @@ end function ENT:TriggerInput(iname, value) for i = 1,4 do if iname == "Memory"..i then - self.Memory[i] = self.Inputs["Memory"..i].Src + self.Memory[i] = self.Inputs["Memory"..i].Src and self.Inputs["Memory"..i].Src:GetTable() or nil end end end diff --git a/lua/entities/gmod_wire_multisegmentlcd/cl_init.lua b/lua/entities/gmod_wire_multisegmentlcd/cl_init.lua new file mode 100644 index 0000000000..7e6b83b793 --- /dev/null +++ b/lua/entities/gmod_wire_multisegmentlcd/cl_init.lua @@ -0,0 +1,505 @@ +include("shared.lua") + + +function ENT:Initialize() + self.Memory = {} + self.Fade = {} + + self.InteractiveData = {} + self.LastButtons = {} + self.Buttons = {} + local interactive_model = WireLib.GetInteractiveModel(self:GetModel()) + self.IsInteractive = false + if interactive_model then + self.IsInteractive = true + for i=1, #WireLib.GetInteractiveModel(self:GetModel()).widgets do + self.InteractiveData[i] = 0 + end + end + + self.Fgblue = 45 + self.Fggreen = 91 + self.Fgred = 45 + self.Fgalpha = 255 + self.Bgblue = 15 + self.Bggreen = 178 + self.Bgred = 148 + self.Bgalpha = 255 + self.Colors = {} + + self.GPU = WireGPU(self) + self.ResolutionW = 1024 + self.ResolutionH = 1024 + + GPULib.ClientCacheCallback(self,function(Address,Value) + self:WriteCell(Address,Value) + end) + + self.TreeMesh = {} + + WireLib.netRegister(self) +end + + +function ENT:SendData() + net.Start("wire_interactiveprop_action") + + local data = WireLib.GetInteractiveModel(self:GetModel()).widgets + net.WriteEntity(self) + for i=1, #data do + net.WriteFloat(self.InteractiveData[i]) + end + net.SendToServer() +end + +function ENT:GetPanel() + if not self.IsInteractive then return end + local data = WireLib.GetInteractiveModel(self:GetModel()) + return WireLib.GetInteractiveWidgetBody(self, data) +end + + +function ENT:AddButton(id,button) + if not self.IsInteractive then return end + self.Buttons[id] = button +end + +function ENT:OnRemove() + self.GPU:Finalize() + for i=1,#self.TreeMesh do + if self.TreeMesh[i] and self.TreeMesh[i]:IsValid() then + self.TreeMesh[i]:Destroy() + end + end +end + +function ENT:ReadCell(Address) + return self.Memory[math.floor(Address)] +end + +function ENT:WriteCell(Address,value) + self.Memory[math.floor(Address)] = value +end + +function ENT:Transform(x,y) + return { + x*self.LocalXX+y*self.LocalXY+self.LocalX, + x*self.LocalYX+y*self.LocalYY+self.LocalY + } +end + +function ENT:TransformOffset(x,y) + return { + x*self.LocalXX+y*self.LocalXY, + x*self.LocalYX+y*self.LocalYY + } +end + +function ENT:GetTransformMatrix(x,y) + return Matrix({ + {x*self.LocalXX,y*self.LocalXY,0,self.LocalX}, + {x*self.LocalYX,y*self.LocalYY,0,self.LocalY}, + {0,0,1,0}, + {0,0,0,1} + }) +end + +function ENT:PushTransform(XX,XY,YX,YY) + self.TransformStack[#self.TransformStack + 1] = {self.LocalXX,self.LocalXY,self.LocalYX,self.LocalYY} + local oXX = self.LocalXX + local oXY = self.LocalXY + local oYX = self.LocalYX + local oYY = self.LocalYY + + local nXX = oXX*XX + oXY*YX + local nXY = oXY*YY + oXX*XY + local nYX = oYX*XX + oYY*YX + local nYY = oYY*YY + oYX*XY + + self.LocalXX = nXX + self.LocalXY = nXY + self.LocalYX = nYX + self.LocalYY = nYY +end + +function ENT:PopTransform() + self.LocalXX,self.LocalXY,self.LocalYX,self.LocalYY = unpack(self.TransformStack[#self.TransformStack]) + self.TransformStack[#self.TransformStack] = nil +end + +function ENT:AddPoly(poly) + local u = (((bit.bxor(self.BitIndex,self.XorMask)+1)%1024)+0.5)/1024 + local v = (math.floor((bit.bxor(self.BitIndex,self.XorMask)+1)/1024)+0.5)/1024 + for i = 1,#poly do + if self.CurTris >= 10922*3 then + mesh.End() + self.TreeMesh[#self.TreeMesh + 1] = Mesh() + mesh.Begin(self.TreeMesh[#self.TreeMesh],MATERIAL_TRIANGLES,math.min(10922,self.Tris)) + self.Tris = self.Tris - 10922 + self.CurTris = 0 + end + mesh.Position(Vector(poly[i][1],poly[i][2],self.ZOffset)) + mesh.TexCoord(0, u, v, u ,v) + mesh.Color(255,255,255,255) + mesh.AdvanceVertex() + self.CurTris = self.CurTris + 1 + end + for i = 1,#poly do + if self.CurTris >= 10922*3 then + mesh.End() + self.TreeMesh[#self.TreeMesh + 1] = Mesh() + mesh.Begin(self.TreeMesh[#self.TreeMesh],MATERIAL_TRIANGLES,math.min(10922,self.Tris)) + self.Tris = self.Tris - 10922 + self.CurTris = 0 + end + mesh.Position(Vector(poly[i][1],poly[i][2],0)) + mesh.TexCoord(0, u, v, u ,v) + mesh.Color(255,255,255,127) + mesh.AdvanceVertex() + self.CurTris = self.CurTris + 1 + end + +end + +function ENT:DrawSegment(segment) + --surface.SetDrawColor(self.Cr,self.Cg,self.Cb,self.Fade[self.BitIndex]*255) + local transformedLocal = self:TransformOffset(segment.X or 0,segment.Y or 0) + self.LocalX = self.LocalX + transformedLocal[1] + self.LocalY = self.LocalY + transformedLocal[2] + local angle = math.rad(segment.Rotation or 0) + self:PushTransform(math.cos(angle), + math.sin(angle)-(segment.SkewX or 0), + -math.sin(angle)+(segment.SkewY or 0), + math.cos(angle)) + --self:Transform(,segment.H/2+(segment.H*(segment.BevelSkew or 0))), + local bevel = math.min(segment.H,segment.W)/2*(segment.Bevel or 0) + local rect = { + self:Transform(bevel,segment.H), + self:Transform(0,segment.H-bevel), + self:Transform(0,bevel), + self:Transform(bevel,0), + self:Transform(segment.W-bevel,0), + self:Transform(segment.W,bevel), + self:Transform(segment.W,segment.H-bevel), + self:Transform(segment.W-bevel,segment.H) + } + local poly = { + rect[1],rect[2],rect[3], + rect[1],rect[3],rect[4], + rect[1],rect[4],rect[5], + rect[1],rect[5],rect[6], + rect[1],rect[6],rect[7], + rect[1],rect[7],rect[8] + } + self:AddPoly(poly) + --surface.DrawPoly(Rect) + self:PopTransform() + --surface.DrawRect(self.LocalX,self.LocalY,segment.W,segment.H) + self.LocalX = self.LocalX - transformedLocal[1] + self.LocalY = self.LocalY - transformedLocal[2] + self.Colors[self.BitIndex] = {self.Cr,self.Cg,self.Cb,self.Ca} + self.BitIndex = self.BitIndex+1 +end + +function ENT:DrawPoly(poly) + --surface.SetDrawColor(self.Cr,self.Cg,self.Cb,self.Fade[self.BitIndex]*255) + local transformedLocal = self:TransformOffset(poly.X or 0,poly.Y or 0) + self.LocalX = self.LocalX + transformedLocal[1] + self.LocalY = self.LocalY + transformedLocal[2] + local angle = math.rad(poly.Rotation or 0) + local tp = {} + tp = LoopToTris(poly.Poly) + for i=1,#tp do + tp[i] = self:Transform(tp[i].x,tp[i].y) + end + self:AddPoly(tp) + self.LocalX = self.LocalX - transformedLocal[1] + self.LocalY = self.LocalY - transformedLocal[2] + self.Colors[self.BitIndex] = {self.Cr,self.Cg,self.Cb,self.Ca} + self.BitIndex = self.BitIndex+1 +end + +function ENT:DrawMatrix(matrix) + for y = 0,matrix.H-1 do + for x = 0,matrix.W-1 do + + local transformedLocal = self:TransformOffset(matrix.X+x*matrix.OffsetX,matrix.Y+y*matrix.OffsetY) + self.LocalX = self.LocalX + transformedLocal[1] + self.LocalY = self.LocalY + transformedLocal[2] + --surface.SetDrawColor(self.Cr,self.Cg,self.Cb,self.Fade[self.BitIndex]*255) + local rect = { + self:Transform(0,matrix.ScaleH), + self:Transform(0,0), + self:Transform(matrix.ScaleW,0), + self:Transform(matrix.ScaleW,matrix.ScaleH) + } + local poly = { + rect[1],rect[2],rect[3], + rect[1],rect[3],rect[4], + } + self:AddPoly(poly) + + + --surface.DrawRect(self.LocalX,self.LocalY,matrix.ScaleW,matrix.ScaleH) + self.LocalX = self.LocalX - transformedLocal[1] + self.LocalY = self.LocalY - transformedLocal[2] + self.Colors[self.BitIndex] = {self.Cr,self.Cg,self.Cb,self.Ca} + self.BitIndex = self.BitIndex+1 + end + end + + +end + + +function ENT:DrawUnion(group) + local oCr = self.Cr + local oCg = self.Cg + local oCb = self.Cb + local oCa = self.Ca + if group.HasColor then + self.Cr = group.R + self.Cg = group.G + self.Cb = group.B + self.Ca = group.A or 255 + end + local transformedLocal = self:TransformOffset(group.X or 0,group.Y or 0) + self.LocalX = self.LocalX + transformedLocal[1] + self.LocalY = self.LocalY + transformedLocal[2] + local savedindex = self.BitIndex + local biggestindex = savedindex + for k,v in ipairs(group.Children) do + if v.Type == GROUP then + self:DrawGroup(v) + elseif v.Type == UNION then + self:DrawUnion(v) + elseif v.Type == SEGMENT then + self:DrawSegment(v) + elseif v.Type == POLY then + self:DrawPoly(v) + elseif v.Type == MATRIX then + self:DrawMatrix(v) + elseif v.Type == ALIGN then + self.BitIndex = math.ceil(self.BitIndex/v.Size)*v.Size + elseif v.Type == OFFSET then + self.BitIndex = self.BitIndex + v.Size + end + biggestindex = math.max(biggestindex,self.BitIndex) + self.BitIndex = savedindex + end + self.BitIndex = biggestindex + self.LocalX = self.LocalX - transformedLocal[1] + self.LocalY = self.LocalY - transformedLocal[2] + self.Cr = oCrq + self.Cg = oCg + self.Cb = oCb + self.Ca = oCa +end + +function ENT:DrawGroup(group) + local oCr = self.Cr + local oCg = self.Cg + local oCb = self.Cb + local oCa = self.Ca + if group.HasColor then + self.Cr = group.R + self.Cg = group.G + self.Cb = group.B + self.Ca = group.A or 255 + --surface.SetDrawColor(self.Cr,self.Cg,self.Cb,255) + end + + local angle = math.rad(group.Rotation or 0) + local transformedLocal = self:TransformOffset(group.X or 0,group.Y or 0) + self.LocalX = self.LocalX + transformedLocal[1] + self.LocalY = self.LocalY + transformedLocal[2] + self:PushTransform(math.cos(angle), + math.sin(angle), + -math.sin(angle), + math.cos(angle)) + self:PushTransform(1, + -(group.SkewX or 0), + (group.SkewY or 0), + 1) + for k,v in ipairs(group.Children) do + if v.Type == GROUP then + self:DrawGroup(v) + elseif v.Type == UNION then + self:DrawUnion(v) + elseif v.Type == SEGMENT then + self:DrawSegment(v) + elseif v.Type == POLY then + self:DrawPoly(v) + elseif v.Type == MATRIX then + self:DrawMatrix(v) + elseif v.Type == ALIGN then + self.BitIndex = math.ceil(self.BitIndex/v.Size)*v.Size + elseif v.Type == OFFSET then + self.BitIndex = self.BitIndex + v.Size + end + end + self:PopTransform() + self.LocalX = self.LocalX - transformedLocal[1] + self.LocalY = self.LocalY - transformedLocal[2] + self.Cr = oCr + self.Cg = oCg + self.Cb = oCb + self.Ca = oCa +end + +function ENT:CountTris(node) + if node.Type == GROUP or node.Type == UNION then + local sum = 0 + for i=1,#node.Children do + sum = sum + self:CountTris(node.Children[i]) + end + return sum + elseif node.Type == MATRIX then + return node.W*node.H*4 + elseif node.Type == POLY then + --print(#node.Poly) + return (#node.Poly-2)*3 + end + return 12 +end + +function ENT:Draw() + self:DrawModel() + + local self2 = self:GetTable() + + + + + if self2.Tree then + + local oldw = ScrW() + local oldh = ScrH() + + local NewRT = self2.GPU.RT + local OldRT = render.GetRenderTarget() + + render.SetRenderTarget(NewRT) + render.SetViewPort(0, 0, 1024, 1024) + if self:GetPos():DistToSqr(EyePos()) < 262144 then + local fade = self2.Fade + cam.Start2D() + render.OverrideBlend( true, BLEND_ONE, BLEND_ZERO, BLENDFUNC_ADD ) + surface.SetDrawColor(self2.Bgred,self2.Bggreen,self2.Bgblue,self2.Bgalpha) + surface.DrawRect( 0, 0, 1, 1 ) + for i=0,self2.BitIndex-1 do + local x = (bit.bxor(i,self.XorMask)+1)%1024 + local y = math.floor((bit.bxor(i,self.XorMask)+1)/1024) + fade[i] = (fade[i] or 0)*0.92 + 0.01 + if bit.band(self2.Memory[bit.rshift(i,3)] or 0,bit.lshift(1,bit.band(i,7))) ~= 0 then + fade[i] = fade[i] + 0.07 + end + + --if fade[i] < 0.1 or (fade[i] > 0.14 and fade[i] < 0.95) then + local color = self2.Colors[i] or {self2.Fgred,self2.Fggreen,self2.Fgblue,self2.Fgalpha} + surface.SetDrawColor(color[1]*fade[i]+self2.Bgred*(1-fade[i]),color[2]*fade[i]+self2.Bggreen*(1-fade[i]),color[3]*fade[i]+self2.Bgblue*(1-fade[i]),fade[i]*color[4]+self2.Bgalpha*(1-fade[i])*0.15) + if x == 0 and y == 0 then + --print("x,y = 0,0") + break + end + surface.DrawRect( x, y, 1, 1 ) + --end + end + render.OverrideBlend( false ) + cam.End2D() + end + render.SetViewPort(0, 0, oldw, oldh) + render.SetRenderTarget(OldRT) + + + local OldTex = WireGPU_matSegment:GetTexture("$basetexture") + WireGPU_matSegment:SetTexture("$basetexture", self2.GPU.RT) + render.SetMaterial( WireGPU_matSegment ) + + local monitor, pos, ang = self2.GPU:GetInfo() + local h = self2.ResolutionH + local scale = monitor.RS*1024/h + local m = Matrix() + m:SetAngles( ang ) + m:SetTranslation( pos ) + m:SetScale( Vector( scale, -scale, 1 ) ) + --cam.PushModelMatrix( self:GetWorldTransformMatrix() ) + cam.PushModelMatrix( m ) + + + for i=1,#self2.TreeMesh do + if self2.TreeMesh[i] and self2.TreeMesh[i]:IsValid() then + self2.TreeMesh[i]:Draw() + end + end + cam.PopModelMatrix() + + + --cam.PopModelMatrix() + + WireGPU_matSegment:SetTexture("$basetexture", OldTex) + end + + Wire_Render(self) +end + +function ENT:IsTranslucent() + return true +end + +function ENT:Receive() + local ent = net.ReadEntity() + local sz = net.ReadUInt(16) + self.Tree = WireLib.von.deserialize(net.ReadData(sz)) + self.ResolutionW = net.ReadUInt(16) + self.ResolutionH = net.ReadUInt(16) + self.Fgblue = net.ReadUInt(8) + self.Fggreen = net.ReadUInt(8) + self.Fgred = net.ReadUInt(8) + self.Fgalpha = net.ReadUInt(8) + self.Bgblue = net.ReadUInt(8) + self.Bggreen = net.ReadUInt(8) + self.Bgred = net.ReadUInt(8) + self.Bgalpha = net.ReadUInt(8) + self.XorMask = net.ReadUInt(8) + + self.Cr = self.Fgred + self.Cg = self.Fggreen + self.Cb = self.Fgblue + self.Ca = self.Fgalpha + self.LocalXX = 1 + self.LocalXY = 0 + self.LocalYX = 0 + self.LocalYY = 1 + self.BitIndex = 0 + self.TransformStack = {} + self.Colors = {} + local monitor, pos, ang = self.GPU:GetInfo() + local h = self.ResolutionH + local w = h/monitor.RatioX + + self.ZOffset = monitor.RS*1024/h + self.LocalX = -w/2 + self.LocalY = -h/2 + self.TreeMesh = self.TreeMesh or {} + for i=#self.TreeMesh,1,-1 do + if self.TreeMesh[i] and self.TreeMesh[i]:IsValid() then + self.TreeMesh[i]:Destroy() + end + self.TreeMesh[i] = nil + end + self.TreeMesh[#self.TreeMesh + 1] = Mesh() + self.Tris = self:CountTris(self.Tree) + 2 + mesh.Begin(self.TreeMesh[#self.TreeMesh],MATERIAL_TRIANGLES,math.min(10922,self.Tris)) + self.Tris = self.Tris - 10922 + mesh.Position(self.LocalX,self.LocalY,0) mesh.Color(255,255,255,255) mesh.TexCoord(0, 1/2048, 1/2048, 1/2048, 1/2048) mesh.AdvanceVertex() + mesh.Position(self.LocalX+w,self.LocalY,0) mesh.Color(255,255,255,255) mesh.TexCoord(0, 1/2048, 1/2048, 1/2048, 1/2048) mesh.AdvanceVertex() + mesh.Position(self.LocalX+w,self.LocalY+h,0) mesh.Color(255,255,255,255) mesh.TexCoord(0, 1/2048, 1/2048, 1/2048, 1/2048) mesh.AdvanceVertex() + + mesh.Position(self.LocalX,self.LocalY,0) mesh.Color(255,255,255,255) mesh.TexCoord(0, 1/2048, 1/2048, 1/2048, 1/2048) mesh.AdvanceVertex() + mesh.Position(self.LocalX+w,self.LocalY+h,0) mesh.Color(255,255,255,255) mesh.TexCoord(0, 1/2048, 1/2048, 1/2048, 1/2048) mesh.AdvanceVertex() + mesh.Position(self.LocalX,self.LocalY+h,0) mesh.Color(255,255,255,255) mesh.TexCoord(0, 1/2048, 1/2048, 1/2048, 1/2048) mesh.AdvanceVertex() + self.CurTris = 6 + self:DrawGroup(self.Tree) + mesh.End() +end diff --git a/lua/entities/gmod_wire_multisegmentlcd/init.lua b/lua/entities/gmod_wire_multisegmentlcd/init.lua new file mode 100644 index 0000000000..9761d0ec6b --- /dev/null +++ b/lua/entities/gmod_wire_multisegmentlcd/init.lua @@ -0,0 +1,191 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") + +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.WireDebugName = "MultiSegmentLcdScreen" + + + +function ENT:InitInteractive() + local model = self:GetModel() + local outputs = {"Memory"} + local interactivemodel = WireLib.GetInteractiveModel(model) + for i=1, #interactivemodel.widgets do + outputs[i+1] = interactivemodel.widgets[i].name + end + self.BlockInput = false + self.NextPrompt = 0 + self.Outputs=WireLib.CreateOutputs(self,outputs) + self.IsInteractive = true + self:UpdateOverlay() +end + +util.AddNetworkString("wire_multisegmentlcd_init") + +function ENT:Initialize() + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + self.InteractiveData = {} + self.IsInteractive = false + if WireLib.IsValidInteractiveModel(self:GetModel()) then + self:InitInteractive() + else + self.Outputs = WireLib.CreateOutputs(self, { "Memory" }) + end + + self.ResolutionW = 1024 + self.ResolutionH = 1024 + + self.Memory = {} + self.Cache = GPUCacheManager(self,true) +end + +function ENT:SendSerializedTree(ply) + if self.Tree == nil then return end + local serialized = WireLib.von.serialize(self.Tree) + if #serialized > 65535 then + return + end + WireLib.netStart(self) + net.WriteEntity(self) + net.WriteUInt(#serialized,16) + net.WriteData(serialized) + net.WriteUInt(self.ResolutionW,16) + net.WriteUInt(self.ResolutionH,16) + net.WriteUInt(self.Fgblue,8) + net.WriteUInt(self.Fggreen,8) + net.WriteUInt(self.Fgred,8) + net.WriteUInt(self.Fgalpha,8) + net.WriteUInt(self.Bgblue,8) + net.WriteUInt(self.Bggreen,8) + net.WriteUInt(self.Bgred,8) + net.WriteUInt(self.Bgalpha,8) + net.WriteUInt(self.XorMask,8) + WireLib.netEnd(ply) +end + +function ENT:Retransmit(ply) + self:SendSerializedTree(ply) + + self.Cache:Flush() + for address,value in pairs(self.Memory) do + self.Cache:Write(address,value) + end + self.Cache:Flush(ply) +end + +function ENT:Setup(IsInteractive, ResolutionW, ResolutionH, Bgred,Bggreen,Bgblue,Bgalpha,Fgred,Fggreen,Fgblue,Fgalpha,XorMask) + self.IsInteractive = WireLib.IsValidInteractiveModel(self:GetModel()) and (IsInteractive) + self.ResolutionW = ResolutionW + self.ResolutionH = ResolutionH + self.Fgblue = Fgblue or 45 + self.Fggreen = Fggreen or 91 + self.Fgred = Fgred or 45 + self.Fgalpha = Fgalpha or 255 + self.Bgblue = Bgblue or 15 + self.Bggreen = Bggreen or 178 + self.Bgred = Bgred or 148 + self.Bgalpha = Bgalpha or 255 + self.XorMask = XorMask or 0 + self:Retransmit() +end + +function ENT:ReadCell(Address) + Address = math.floor(Address) + if Address < 0 then return nil end + + return self.Memory[Address] +end + +function ENT:WriteCell(Address, value) + Address = math.floor(Address) + if Address < 0 then return false end + + self.Memory[Address] = value + self.Cache:Write(Address, value) + return true +end + +function ENT:TriggerInput(iname, value) + +end + +function ENT:Think() + self.Cache:Flush() + self:NextThink(CurTime()+0.01) + return true +end + + +function ENT:ReceiveData() + if not self.IsInteractive then return end + local data = WireLib.GetInteractiveModel(self:GetModel()).widgets + for i = 1, #data do + WireLib.TriggerOutput(self, data[i].name, net.ReadFloat()) + end +end + + +function ENT:UpdateOverlay() + if not self.IsInteractive then + return + end + + txt = "" + if IsValid(self.User) then + txt = "In use by: " .. self.User:Nick() + end + + self:SetOverlayText(txt) +end + + + +function ENT:Prompt( ply ) + if not self.IsInteractive then return end + if ply then + if CurTime() < self.NextPrompt then return end -- anti spam + self.NextPrompt = CurTime() + 0.1 + + if IsValid( self.User ) then + WireLib.AddNotify(ply,"That interactive prop is in use by another player!",NOTIFY_ERROR,5,6) + return + end + + self.User = ply + + net.Start( "wire_interactiveprop_show" ) + net.WriteEntity( self ) + net.Send( ply ) + else + self:Prompt( self:GetPlayer() ) -- prompt for owner + end +end + +function ENT:Use(ply) + if not IsValid( ply ) then return end + self:Prompt( ply ) +end + +function ENT:Unprompt() + if not self.IsInteractive then return end + self.User = nil +end + +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo( self ) or {} + info.Tree = self.Tree + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + self.Tree = info.Tree + ent.Tree = info.Tree + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + ent:Retransmit() +end + +duplicator.RegisterEntityClass("gmod_wire_multisegmentlcd", WireLib.MakeWireEnt, "Data", "IsInteractive", "ResolutionW", "ResolutionH", "Bgred", "Bggreen", "Bgblue", "Bgalpha", "Fgred", "Fggreen", "Fgblue", "Fgalpha", "XorMask") diff --git a/lua/entities/gmod_wire_multisegmentlcd/shared.lua b/lua/entities/gmod_wire_multisegmentlcd/shared.lua new file mode 100644 index 0000000000..971661f7d0 --- /dev/null +++ b/lua/entities/gmod_wire_multisegmentlcd/shared.lua @@ -0,0 +1,21 @@ +ENT.Type = "anim" +ENT.Base = "base_wire_entity" + + +ENT.PrintName = "Wire Multi-segment LCD" +ENT.Author = "" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" + +ENT.Spawnable = false + +ENT.RenderGroup = RENDERGROUP_BOTH + +OFFSET = -3 +ALIGN = -2 +GROUP = -1 +UNION = 0 +SEGMENT = 1 +TEXT = 2 +MATRIX = 3 \ No newline at end of file diff --git a/lua/wire/client/segment_editor/mslcdeditor.lua b/lua/wire/client/segment_editor/mslcdeditor.lua new file mode 100644 index 0000000000..50278bbf16 --- /dev/null +++ b/lua/wire/client/segment_editor/mslcdeditor.lua @@ -0,0 +1,869 @@ +local Editor = {} + +OFFSET = -3 +ALIGN = -2 +GROUP = -1 +UNION = 0 +SEGMENT = 1 +TEXT = 2 +POLY = 2 +MATRIX = 3 +SegmentTypeNames = { +[GROUP] = "Group", +[UNION] = "Union", +[SEGMENT] = "Segment", +[POLY] = "Poly", +[MATRIX] = "Matrix", +[ALIGN] = "Align", +[OFFSET] = "Offset", +} + +function Editor:Init() + self.SegmentTree = { + Type=GROUP, + X=0, + Y=0, + Children= + { + + } + } + + self.DraggingWorld = false + self.DraggingNode = nil + self.DraggingOffset = { 0, 0 } + self.DraggingPolyVert = nil + + + + self.SelectedSegments = nil + self.SelectedSegment = nil + self.SelectedVert = nil + self.Selecting = nil + + self.LastMousePos = { 0, 0 } + self.MouseDown = false + + self.GateSize = FPGANodeSize + self.GridSize = self.GateSize * 2 + self.GridEnabled = true + + self.IOSize = 2 + + self.BackgroundColor = Color(40, 40, 40, 255) + self.GridColor = Color(50, 50, 50, 255) + self.SelectionColor = Color(220, 220, 100, 255) + + self.ZoomHideThreshold = 2 + self.ZoomThreshold = 7 + + self.LastFrameTime = SysTime() + + self.Mode = POLY + self.Zoom = 5 + self.Position = {0,0} + + self.LocalXX = self.Zoom + self.LocalXY = 0 + self.LocalYX = 0 + self.LocalYY = self.Zoom + self.LocalX = self:GetWide() / 2 - self.Position[1]*self.Zoom + self.LocalY = self:GetTall() / 2 - self.Position[2]*self.Zoom +end + +function Editor:SetMode(mode) + self.Mode = mode +end + +function Transform(self,x,y) + return x*self.LocalXX+y*self.LocalXY+self.LocalX, x*self.LocalYX+y*self.LocalYY+self.LocalY +end + +function TransformOffset(self,x,y) + return { + x*self.LocalXX+y*self.LocalXY, + x*self.LocalYX+y*self.LocalYY + } +end + + +function PushTransform(self,XX,XY,YX,YY) + self.TransformStack[#self.TransformStack + 1] = {self.LocalXX,self.LocalXY,self.LocalYX,self.LocalYY} + local oXX = self.LocalXX + local oXY = self.LocalXY + local oYX = self.LocalYX + local oYY = self.LocalYY + + local nXX = oXX*XX + oXY*YX + local nXY = oXY*YY + oXX*XY + local nYX = oYX*XX + oYY*YX + local nYY = oYY*YY + oYX*XY + + self.LocalXX = nXX + self.LocalXY = nXY + self.LocalYX = nYX + self.LocalYY = nYY +end + +function PopTransform(self) + self.LocalXX,self.LocalXY,self.LocalYX,self.LocalYY = unpack(self.TransformStack[#self.TransformStack]) + self.TransformStack[#self.TransformStack] = nil +end + +function PolyDimensions(self,poly,tlocal) + self.LocalX = self.LocalX + tlocal[1] + self.LocalY = self.LocalY + tlocal[2] + local minx, miny = Transform(self, poly[1].x, poly[1].y) + local maxx, maxy = minx, miny + for i, v in ipairs(poly) do + x, y = Transform(self, v.x, v.y) + minx, miny = math.min(minx, x), math.min(miny, y) + maxx, maxy = math.max(maxx, x), math.max(maxy, y) + end + self.LocalX = self.LocalX - tlocal[1] + self.LocalY = self.LocalY - tlocal[2] + return minx, miny, maxx, maxy +end + + +function DrawSegment(self,segment) + local transformedLocal = TransformOffset(self,segment.X or 0,segment.Y or 0) + local angle = math.rad(segment.Rotation or 0) + PushTransform(self,math.cos(angle), + math.sin(angle)-(segment.SkewX or 0), + -math.sin(angle)+(segment.SkewY or 0), + math.cos(angle)) + --self:Transform(,segment.H/2+(segment.H*(segment.BevelSkew or 0))), + local bevel = math.min(segment.H,segment.W)/2*(segment.Bevel or 0) + + local rect = { + {x=bevel,y=segment.H}, + {x=0,y=segment.H-bevel}, + {x=0,y=bevel}, + {x=bevel,y=0}, + {x=segment.W-bevel,y=0}, + {x=segment.W,y=bevel}, + {x=segment.W,y=segment.H-bevel}, + {x=segment.W-bevel,y=segment.H} + } + local x, y = self:LocalToScreen(0,0) + local m = Matrix() + m:Translate(Vector(x,y,0)) + m:Mul(Matrix({ + {self.LocalXX,self.LocalXY,0,self.LocalX + transformedLocal[1]}, + {self.LocalYX,self.LocalYY,0,self.LocalY + transformedLocal[2]}, + {0,0,1,0}, + {0,0,0,1} + })) + m:Translate(Vector(-x,-y,0)) + cam.PushModelMatrix(m) + surface.DrawPoly(rect) + cam.PopModelMatrix() + PopTransform(self) + --surface.DrawRect(self.LocalX,self.LocalY,segment.W,segment.H) + + return PolyDimensions(self,rect,transformedLocal) +end + + +function LoopToTris(poly) + poly = table.Copy(poly) + if #poly == 3 then + return poly + end + local tries = 0 + local tris = {} + local i = 0 + while #poly > 3 do + tries = tries + 1 + i = i%#poly+1 + local a = poly[i] + local b = poly[i%#poly+1] + local c = poly[(i+1)%#poly+1] + local lax = a.x-b.x + local lay = a.y-b.y + local la = a.y*lax-a.x*lay + + local lbx = b.x-c.x + local lby = b.y-c.y + local lb = b.y*lbx-b.x*lby + + local lcx = c.x-a.x + local lcy = c.y-a.y + local lc = c.y*lcx-c.x*lcy + + if (c.y*lax - c.x*lay) > la then + goto fail + end + + + for j,p in ipairs(poly) do + if j == i or j == (i%#poly+1) or j == ((i+1)%#poly+1) then + goto skip + end + local lpa = p.y*lax - p.x*lay + local lpb = p.y*lbx - p.x*lby + local lpc = p.y*lcx - p.x*lcy + if lpa < la and lpb < lb and lpc < lc then + goto fail + end + ::skip:: + end + + tris[#tris+1] = a + tris[#tris+1] = b + tris[#tris+1] = c + table.remove(poly,i%#poly+1) + tries = 0 + --i = (i-2)%#poly+1 + --i = 0 + + ::fail:: + if tries > #poly then + break + end + end + + tris[#tris+1] = poly[1] + tris[#tris+1] = poly[2] + tris[#tris+1] = poly[3] + return tris +end + + +function DrawPoly(self,poly) + local selected = poly == self.SelectedSegment + local transformedLocal = TransformOffset(self,poly.X or 0,poly.Y or 0) + + local x, y = self:LocalToScreen(0,0) + local m = Matrix() + m:Translate(Vector(x,y,0)) + m:Mul(Matrix({ + {self.LocalXX,self.LocalXY,0,self.LocalX + transformedLocal[1]}, + {self.LocalYX,self.LocalYY,0,self.LocalY + transformedLocal[2]}, + {0,0,1,0}, + {0,0,0,1} + })) + m:Translate(Vector(-x,-y,0)) + cam.PushModelMatrix(m) + --surface.DrawPoly(poly.Poly) + if selected then + surface.SetDrawColor(255,192,192,255) + else + surface.SetDrawColor(255,255,255,255) + end + + for i,p in ipairs(poly.Poly) do + local op = poly.Poly[i%#poly.Poly+1] + surface.DrawLine(p.x-0.5,p.y-0.5,op.x-0.5,op.y-0.5) + end + + surface.SetDrawColor(255,255,0,255) + for i,p in ipairs(poly.Poly) do + local selectedvert = i == self.SelectedVert and selected + local m = Matrix() + m:Translate(Vector(x+p.x,y+p.y,0)) + m:Scale(Vector(1/self.Zoom,1/self.Zoom,0)) + m:Translate(Vector(-x,-y,0)) + cam.PushModelMatrix(m, true) + if selectedvert then + surface.SetDrawColor(255,0,0,255) + surface.DrawRect(-4,-4,8,8) + surface.SetDrawColor(255,255,0,255) + else + surface.DrawRect(-4,-4,8,8) + end + cam.PopModelMatrix() + end + if selected then + surface.SetDrawColor(0,255,255,255) + else + surface.SetDrawColor(0,255,0,255) + end + m = Matrix() + m:Translate(Vector(x-4.0/self.Zoom,y-4.0/self.Zoom,0)) + m:Scale(Vector(1/self.Zoom,1/self.Zoom,0)) + m:Translate(Vector(-x,-y,0)) + cam.PushModelMatrix(m, true) + surface.DrawRect(0,0,8,8) + cam.PopModelMatrix() + surface.SetDrawColor(255,255,255,255) + cam.PopModelMatrix() + + return PolyDimensions(self,poly.Poly,transformedLocal) +end + +function DrawMatrix(self,matrix) + +end + +function DrawUnion(self,union) + for k,v in ipairs(union.Children) do + if v.Type == GROUP then + DrawGroup(self,v) + elseif v.Type == UNION then + DrawUnion(self,v) + elseif v.Type == SEGMENT then + DrawSegment(self,v) + elseif v.Type == POLY then + DrawPoly(self,v) + elseif v.Type == MATRIX then + DrawMatrix(self,v) + end + end +end + +function DrawGroup(self,group) + if #group.Children == 0 then + return + end + + + local angle = math.rad(group.Rotation or 0) + local transformedLocal = TransformOffset(self,group.X or 0,group.Y or 0) + self.LocalX = self.LocalX + transformedLocal[1] + self.LocalY = self.LocalY + transformedLocal[2] + PushTransform(self,math.cos(angle), + math.sin(angle), + -math.sin(angle), + math.cos(angle)) + PushTransform(self,1, + -(group.SkewX or 0), + (group.SkewY or 0), + 1) + local minx, miny = Transform(self, group.Children[1].X, group.Children[1].Y) + local maxx, maxy = minx, miny + + for k,v in ipairs(group.Children) do + local nminx, nminy, nmaxx, nmaxy + if v.Type == GROUP then + nminx, nminy, nmaxx, nmaxy = DrawGroup(self,v) + elseif v.Type == UNION then + nminx, nminy, nmaxx, nmaxy = DrawGroup(self,v) + elseif v.Type == SEGMENT then + nminx, nminy, nmaxx, nmaxy = DrawSegment(self,v) + elseif v.Type == POLY then + nminx, nminy, nmaxx, nmaxy = DrawPoly(self,v) + elseif v.Type == MATRIX then + DrawMatrix(self,v) + end + --if nminx == nil then + --PrintTable(group) + --end + minx, miny = math.min(nminx, minx), math.min(nminy, miny) + maxx, maxy = math.max(nmaxx, maxx), math.max(nmaxy, maxy) + end + --m:Translate(Vector(x+p.x-4.0/self.Zoom,y+p.y-4.0/self.Zoom,0)) + --m:Scale(Vector(1/self.Zoom,1/self.Zoom,0)) + --m:Translate(Vector(-x,-y,0)) + local x, y = self:LocalToScreen(0,0) + local m = Matrix() + m:Translate(Vector(x,y,0)) + m:Mul(Matrix({ + {self.LocalXX/self.Zoom,self.LocalXY/self.Zoom,0,self.LocalX + transformedLocal[1]}, + {self.LocalYX/self.Zoom,self.LocalYY/self.Zoom,0,self.LocalY + transformedLocal[2]}, + {0,0,1,0}, + {0,0,0,1} + })) + m:Translate(Vector(-x,-y,0)) + --cam.PushModelMatrix(m,true) + if group.HasColor then + surface.SetDrawColor(group.R or 255,group.G or 255,group.B or 255,group.A or 255) + end + surface.DrawOutlinedRect(minx,miny,(maxx-minx),(maxy-miny)) + --cam.PopModelMatrix() + PopTransform(self) + self.LocalX = self.LocalX - transformedLocal[1] + self.LocalY = self.LocalY - transformedLocal[2] + + + return minx,miny,maxx,maxy +end + +function Editor:Paint() + local width = self:GetWide() + local height = self:GetTall() + local snapincrement = GetConVar("wire_multisegmentlcd_snapinc"):GetFloat() + -- Update animation frame time + self.LastFrameTime = SysTime() + + surface.SetDrawColor(self.BackgroundColor) + draw.NoTexture() + surface.DrawRect(0, 0, width, height) + + -- detects if mouse is let go outside of the window + if not input.IsMouseDown(MOUSE_RIGHT) then + self.DraggingWorld = nil + end + if not input.IsMouseDown(MOUSE_LEFT) then + self.DraggingNode = nil + self.DrawingConnection = nil + self.DrawingSelection = nil + end + + local x, y = self:CursorPos() + + local dx, dy = self.LastMousePos[1] - x, self.LastMousePos[2] - y + -- moving the plane + if self.DraggingWorld then + self.Position = { self.Position[1] + dx * (1 / self.Zoom), self.Position[2] + dy * (1 / self.Zoom) } + end + local wx, wy = self:ScrToPos(x, y) + + if self.DraggingPolyVert then + + if self.DraggingPolyVert[2] == 0 then + local poly = self.DraggingPolyVert[1] + poly.X = wx-self.DraggingPolyVert[3] + poly.Y = wy-self.DraggingPolyVert[4] + if snapincrement > 0.001 then + poly.X = math.floor(poly.X/snapincrement + 0.5)*snapincrement + poly.Y = math.floor(poly.Y/snapincrement + 0.5)*snapincrement + end + else + local vert = self.DraggingPolyVert[1].Poly[self.DraggingPolyVert[2]] + vert.x = wx-self.DraggingPolyVert[3] + vert.y = wy-self.DraggingPolyVert[4] + if snapincrement > 0.001 then + vert.x = math.floor(vert.x/snapincrement + 0.5)*snapincrement + vert.y = math.floor(vert.y/snapincrement + 0.5)*snapincrement + end + end + end + + self:PaintGrid() + + + self.LocalXX = self.Zoom + self.LocalXY = 0 + self.LocalYX = 0 + self.LocalYY = self.Zoom + self.LocalX = self:GetWide() / 2 - self.Position[1]*self.Zoom + self.LocalY = self:GetTall() / 2 - self.Position[2]*self.Zoom + + surface.SetDrawColor(255, 255, 255, 255) + self.TransformStack = {} + DisableClipping(true) + self.SegmentTree.X = 0 + self.SegmentTree.Y = 0 + self.minx,self.miny,self.maxx,self.maxy = DrawGroup(self,self.SegmentTree) + + if self.SelectedSegments then + self.SelectedSegments.HasColor = true + self.SelectedSegments.G = 0 + DrawGroup(self,self.SelectedSegments) + self.SelectedSegments.HasColor = nil + self.SelectedSegments.G = nil + end + if self.Selecting ~= nil then + local sminx = math.min(self.Selecting.x,wx) + local sminy = math.min(self.Selecting.y,wy) + local smaxx = math.max(self.Selecting.x,wx) + local smaxy = math.max(self.Selecting.y,wy) + sminx,sminy = self:PosToScr(sminx,sminy) + smaxx,smaxy = self:PosToScr(smaxx,smaxy) + surface.DrawOutlinedRect(sminx,sminy,smaxx-sminx,smaxy-sminy) + end + + DisableClipping(false) + --self:GetPolyVertAt(wx,wy) + local x, y = self:CursorPos() + self.LastMousePos = { x, y } + + if self.SelectedSegment then + self.ParentPanel.C.Prop_X:SetValue(self.SelectedSegment.X) + self.ParentPanel.C.Prop_Y:SetValue(self.SelectedSegment.Y) + if self.SelectedVert ~= 0 then + self.ParentPanel.C.Vert_X:SetValue(self.SelectedSegment.Poly[self.SelectedVert].x) + self.ParentPanel.C.Vert_Y:SetValue(self.SelectedSegment.Poly[self.SelectedVert].y) + end + end + +end + +function Editor:SetData(data) + local ok, data = pcall(WireLib.von.deserialize, data) + if not ok then + self:ClearData() + self.C.Name:SetValue("corrupt") + return + end + + if data.Position then self.Position = data.Position else self.Position = { 0, 0 } end + if data.Zoom then self.Zoom = data.Zoom else self.Zoom = 5 end + if data.SegmentTree then self.SegmentTree = data.SegmentTree end +end + +function Editor:GetData() + self.SegmentTree.X = -((self.minx or 0)-self.LocalX)/self.Zoom + self.SegmentTree.Y = -((self.miny or 0)-self.LocalY)/self.Zoom + return WireLib.von.serialize({ + SegmentTree = self.SegmentTree, + Position = self.Position, + Zoom = self.Zoom + }, false) +end + +function Editor:ClearData() + self.Position = { 0, 0 } + self.Zoom = 5 + self.SegmentTree = { + Type=GROUP, + X=0, + Y=0, + Children= + { + + } + } +end + +function Editor:PaintGrid() + if not self.GridEnabled then return end + + local gridSize = self.GridSize * self.Zoom + if gridSize < 5 then return end + + local screenW = self:GetWide() + local screenH = self:GetTall() + + local startX = math.floor((self.Position[1] - screenW / (2 * self.Zoom)) / self.GridSize) * self.GridSize + local endX = math.ceil((self.Position[1] + screenW / (2 * self.Zoom)) / self.GridSize) * self.GridSize + local startY = math.floor((self.Position[2] - screenH / (2 * self.Zoom)) / self.GridSize) * self.GridSize + local endY = math.ceil((self.Position[2] + screenH / (2 * self.Zoom)) / self.GridSize) * self.GridSize + + surface.SetDrawColor(self.GridColor) + + for x = startX, endX, self.GridSize do + local sx, sy1 = self:PosToScr(x, startY) + local _, sy2 = self:PosToScr(x, endY) + surface.DrawLine(sx, sy1, sx, sy2) + end + + for y = startY, endY, self.GridSize do + local sx1, sy = self:PosToScr(startX, y) + local sx2, _ = self:PosToScr(endX, y) + surface.DrawLine(sx1, sy, sx2, sy) + end +end + +-- EDITING + +function Editor:CreateSegment(x, y) + local group = nil + local children = nil + if group ~= nil then + children = group.Children + end + + if children == nil then + children = self.SegmentTree.Children + group = self.SegmentTree + end + local n = {Type=SEGMENT, X=x,Y=y,W=70,H=70,Bevel = 0.1} + children[#children+1] = n +end + +function Editor:CreatePoly(x, y) + local group = nil + local children = nil + if group ~= nil then + children = group.Children + end + + if children == nil then + children = self.SegmentTree.Children + group = self.SegmentTree + end + local n = {Type=POLY, X=x,Y=y, Poly={{x=0,y=0},{x=10,y=0},{x=0,y=10}}} + children[#children+1] = n +end + +-- KEYBOARD + +function Editor:OnKeyCodePressed(code) + local x, y = self:CursorPos() + local gx, gy = self:ScrToPos(x, y) + local snapincrement = GetConVar("wire_multisegmentlcd_snapinc"):GetFloat() + if snapincrement > 0.001 then + gx = math.floor(gx/snapincrement + 0.5)*snapincrement + gy = math.floor(gy/snapincrement + 0.5)*snapincrement + end + local control = input.IsKeyDown(KEY_LCONTROL) or input.IsKeyDown(KEY_RCONTROL) + local shift = input.IsKeyDown(KEY_LSHIFT) or input.IsKeyDown(KEY_RSHIFT) + if control then + if code == KEY_C then + if self.SelectedSegments then + self.ParentPanel.Clipboard = table.Copy(self.SelectedSegments) + else + self.ParentPanel.Clipboard = table.Copy(self.SelectedSegment) + end + elseif code == KEY_V then + if self.ParentPanel.Clipboard ~= nil then + self.ParentPanel.Clipboard.X = gx + self.ParentPanel.Clipboard.Y = gy + self.SegmentTree.Children[#self.SegmentTree.Children+1] = table.Copy(self.ParentPanel.Clipboard) + end + end + elseif code == KEY_C then + --Create + if self.Mode == SEGMENT then + self:CreateSegment(gx, gy) + elseif self.Mode == POLY then + self:CreatePoly(gx, gy) + end + end +end + +-- MOUSE + +function Editor:OnMouseWheeled(delta) + local sx, sy = self:CursorPos() + + self.Zoom = self.Zoom + delta * 0.1 * self.Zoom + if self.Zoom < 0.1 then self.Zoom = 0.1 end + if self.Zoom > 100 then self.Zoom = 100 end +end + + +function Editor:GetPolyEdgeAtPoly(x, y, poly) + --local x,y = x/self.Zoom, y/self.Zoom + for i,v in ipairs(poly.Poly) do + local o = poly.Poly[i%#poly.Poly+1] + local lx = o.x-v.x + local ly = o.y-v.y + local d = math.sqrt(lx*lx+ly*ly) + local ls = (v.x*ly - v.y*lx)/d + local lf = (v.x*lx + v.y*ly)/d + local f = (x*lx + y*ly)/d - lf + local s = (x*ly - y*lx)/d - ls + if f > 0 and f < d and s >= -10/self.Zoom and s <= 10/self.Zoom then + return poly, i + end + end + return nil, nil +end + +function Editor:GetPolyEdgeAtGroup(x, y, group) + local x = x-group.X + local y = y-group.Y + for i,v in ipairs(group.Children) do + if v.Type == GROUP then + ri, rv, ex, ey = self:GetPolyEdgeAtGroup(x, y, v) + if ri then + return ri, rv, ex, ey + end + elseif v.Type == POLY then + ri, rv = self:GetPolyEdgeAtPoly(x-v.X, y-v.Y, v) + if ri then + return ri, rv, x-v.X, y-v.Y + end + end + + end + return nil, nil +end + +function Editor:GetPolyEdgeAt(x, y) + return self:GetPolyEdgeAtGroup(x, y, self.SegmentTree) +end + + +function Editor:GetPolyVertAtPoly(x, y, poly) + local x,y = (x)*self.Zoom, (y)*self.Zoom + for i,v in ipairs(poly.Poly) do + local vx = v.x*self.Zoom + local vy = v.y*self.Zoom + surface.DrawRect(vx-x-4,vy-y-4,8,8) + if math.abs(vx-x) < 4 and math.abs(vy-y) < 4 then + return poly, i + end + + end + if math.abs(x) < 4 and math.abs(y) < 4 then + return poly, 0 + end + return nil, nil +end + +function Editor:GetPolyVertAtGroup(x, y, group) + for i,v in ipairs(group.Children) do + if v.Type == GROUP then + ri, rv, g, gi = self:GetPolyVertAtGroup(x-v.X, y-v.Y, v) + if ri then + return ri, rv, g, gi + end + elseif v.Type == POLY then + ri, rv = self:GetPolyVertAtPoly(x-v.X, y-v.Y, v) + if ri then + return ri, rv, group, i + end + end + + end + return nil, nil, nil, nil +end + +function Editor:GetPolyVertAt(x, y) + return self:GetPolyVertAtGroup(x, y, self.SegmentTree) +end + + +function Editor:SelectSegmentsAtGroup(x1, y1, x2, y2, group) + local sel = { + X = group.X, + Y = group.Y, + Type = GROUP, + Children = {} + } + local x1 = x1-group.X + local y1 = y1-group.Y + local x2 = x2-group.X + local y2 = y2-group.Y + for i,v in ipairs(group.Children) do + if v.Type == GROUP then + sel.Children[#sel.Children+1] = self:SelectSegmentsAtGroup(x1, y1, x2, y2, v) + elseif v.Type == POLY then + if v.X >= x1 and v.Y >= y1 and v.X <= x2 and v.Y <= y2 then + sel.Children[#sel.Children+1] = v + end + end + end + if #sel.Children > 0 then + return sel + end + --if #sel.Children == 1 then + -- return sel.Children[1] + --end + return nil +end + +function Editor:SelectSegments(x1, y1, x2, y2) + local minx = math.min(x1,x2) + local miny = math.min(y1,y2) + local maxx = math.max(x1,x2) + local maxy = math.max(y1,y2) + local grp = self:SelectSegmentsAtGroup(minx, miny, maxx, maxy, self.SegmentTree) + if grp == nil then + self.SelectedSegments = nil + elseif grp.Type ~= GROUP then + self.SelectedSegments = { + X = 0, + Y = 0, + Type = GROUP, + Children = {grp} + } + else + self.SelectedSegments = grp + end +end + +function Editor:PruneGroups(children) + for i=#children,1,-1 do + local v = children[i] + if v.Type == GROUP then + if #v.Children == 0 then + table.remove(children,i) + else + self:PruneGroups(v.Children) + end + end + end +end + +-- MOUSE + +function Editor:OnMousePressed(code) + self:RequestFocus() --Fix for weird bug, remove once resolved + + if code == MOUSE_LEFT then + self.MouseDown = true + + --double click detection + local doubleClick + if self.LastClick then + doubleClick = SysTime() - self.LastClick < 0.3 + else doubleClick = false end + self.LastClick = SysTime() + + local x, y = self:ScrToPos(self:CursorPos()) + local control = input.IsKeyDown(KEY_LCONTROL) or input.IsKeyDown(KEY_RCONTROL) + + local pvKey, pvIndex, pvGroup, pvGroupIndex = self:GetPolyVertAt(x, y) + + if pvKey then + if control then + if pvIndex ~= 0 then + table.remove(pvKey.Poly,pvIndex) + if #pvKey.Poly < 3 then + table.remove(pvGroup.Children,pvGroupIndex) + self:PruneGroups(self.SegmentTree.Children) + end + end + else + if pvIndex == 0 then + self.DraggingPolyVert = {pvKey,pvIndex,x-pvKey.X,y-pvKey.Y} + else + local point = pvKey.Poly[pvIndex] + self.DraggingPolyVert = {pvKey,pvIndex,x - point.x, y - point.y} + end + + end + elseif not control then + local peKey, peIndex, ex, ey = self:GetPolyEdgeAt(x, y) + + if peKey then + table.insert(peKey.Poly,peIndex+1,{x=ex,y=ey}) + self.DraggingPolyVert = {peKey,peIndex+1,x-ex,y-ey} + end + end + self.SelectedSegment = nil + self.SelectedVert = nil + if self.DraggingPolyVert then + self.SelectedSegment = self.DraggingPolyVert[1] + self.SelectedVert = self.DraggingPolyVert[2] + else + self.Selecting = {x=x,y=y} + end + elseif code == MOUSE_RIGHT then + -- PLANE DRAGGING + self.DraggingWorld = true + end +end + +function Editor:OnMouseReleased(code) + local x, y = self:ScrToPos(self:CursorPos()) + + if code == MOUSE_LEFT then + self.MouseDown = false + self.DraggingNode = nil + self.DraggingPolyVert = nil + if self.Selecting ~= nil then + self:SelectSegments(self.Selecting.x,self.Selecting.y,x,y) + end + self.Selecting = nil + elseif code == MOUSE_RIGHT then + self.DraggingWorld = false + end + +end + +-- UTILITY + +function Editor:PosToScr(x, y) + return (self:GetWide()) / 2 - (self.Position[1] - x) * self.Zoom, self:GetTall() / 2 - (self.Position[2] - y) * self.Zoom +end + +function Editor:ScrToPos(x, y) + return self.Position[1] - ((self:GetWide()) / 2 - x) / self.Zoom, self.Position[2] - (self:GetTall() / 2 - y) / self.Zoom +end + +function Editor:AlignPosToGrid(x, y) + return math.Round(x / self.GateSize) * self.GateSize, math.Round(y / self.GateSize) * self.GateSize +end + +function Editor:DragHoverClick(hoverTime) + --print(hoverTime) +end + +vgui.Register("MSLCDEditor", Editor, "Panel"); diff --git a/lua/wire/client/segment_editor/mslcdeditor.lua.unused b/lua/wire/client/segment_editor/mslcdeditor.lua.unused new file mode 100644 index 0000000000..05fdfc3624 --- /dev/null +++ b/lua/wire/client/segment_editor/mslcdeditor.lua.unused @@ -0,0 +1,2757 @@ +local Editor = {} + +FPGATypeColor = { + NORMAL = Color(190, 190, 255, 255), --Very light blue nearing white + VECTOR2 = Color(150, 255, 255, 255), --Light blue + VECTOR = Color(70, 160, 255, 255), --Blue + VECTOR4 = Color(0, 50, 255, 255), --Dark blue + ANGLE = Color(100, 200, 100, 255), --Light green + STRING = Color(250, 160, 90, 255), --Orange + ARRAY = Color(20, 110, 20, 255), --Dark green + ENTITY = Color(255, 100, 100, 255), --Dark red + RANGER = Color(130, 100, 60, 255), --Brown + WIRELINK = Color(200, 80, 200, 255), --Deep purple +} + +--GATE HELPERS +local function getGate(node) + if node.type == "wire" then + return GateActions[node.gate] + elseif node.type == "fpga" then + return FPGAGateActions[node.gate] + elseif node.type == "cpu" then + return CPUGateActions[node.gate] + end +end + +local function getInputType(gate, inputNum) + if gate.inputtypes then + return gate.inputtypes[inputNum] or "NORMAL" + else + return "NORMAL" + end +end + +local function getOutputType(gate, outputNum) + if gate.outputtypes then + return gate.outputtypes[outputNum] or "NORMAL" + else + return "NORMAL" + end +end + +function Editor:Init() + self.Nodes = {} + + self.AlignToGrid = false + + self.DraggingWorld = false + self.DraggingNode = nil + self.DraggingOffset = { 0, 0 } + + self.DraggingWaypoint = nil + self.HoveringWaypoint = nil + + self.DrawingConnection = false + self.DrawingFromInput = false + self.DrawingFromOutput = false + self.DrawingConnectionFrom = nil + + self.DrawingSelection = nil + self.SelectedNodes = {} + self.SelectedNodeCount = 0 + + self.SelectedWaypoints = {} -- Format: {connectionKey_waypointIndex = true} + self.SelectedWaypointCount = 0 + + self.LastMousePos = { 0, 0 } + self.MouseDown = false + + self.SelectedInMenu = nil + + self.GateSize = FPGANodeSize + self.GridSize = self.GateSize * 2 + self.GridEnabled = true + + self.IOSize = 2 + + self.BackgroundColor = Color(40, 40, 40, 255) + self.GridColor = Color(50, 50, 50, 255) + self.SelectionColor = Color(220, 220, 100, 255) + self.WaypointColor = Color(255, 198, 109, 255) + self.WaypointSelectedColor = Color(0, 122, 204, 255) + self.WaypointHoverColor = Color(255, 198, 109, 200) + + self.NodeColor = Color(100, 100, 100, 255) + self.InputNodeColor = Color(80, 90, 80, 255) + self.OutputNodeColor = Color(80, 80, 90, 255) + self.TimedNodeColor = Color(110, 70, 70, 255) + self.SelectedNodeColor = Color(0, 122, 204, 255) + + self.NodeOutlineColor = Color(80, 80, 80, 255) -- Subtle outline + self.NodeShadowColor = Color(0, 0, 0, 100) + + self.ZoomHideThreshold = 2 + self.ZoomThreshold = 7 + + self.UndoStack = {} + self.RedoStack = {} + self.MaxUndoSteps = 50 + self.UndoEnabled = true + + self.MinimapConVar = CreateClientConVar("wire_fpga_editor_minimap", "1", true, false) + self.MinimapSize = 200 + self.MinimapPadding = 10 + self.MinimapAlpha = 200 + self.MinimapConnectionAlpha = 50 + self.MinimapBackgroundColor = Color(20, 20, 20, self.MinimapAlpha) + self.MinimapBorderColor = Color(60, 60, 60, self.MinimapAlpha) + + self.Animations = {} + self.LastFrameTime = SysTime() + + self.C = {} + self:InitComponents() +end + +function Editor:GetParent() + return self.ParentPanel +end + +surface.CreateFont( "FPGAText", { + font = "Tahoma", + extended = false, + size = 16, + weight = 500, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +}) +surface.CreateFont( "FPGAIO", { + font = "Tahoma", + extended = false, + size = 12, + weight = 500, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +}) +surface.CreateFont( "FPGATextBig", { + font = "Tahoma", + extended = false, + size = 20, + weight = 1000, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +}) +surface.CreateFont( "FPGAIOBig", { + font = "Tahoma", + extended = false, + size = 18, + weight = 200, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +}) +surface.CreateFont( "FPGALabel", { + font = "Bahnschrift", + extended = false, + size = 25, + weight = 1000, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = true, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +}) + +-------------------------------------------------------- +--COMPONENTS +-------------------------------------------------------- +function Editor:InitComponents() + local this = self + + self.C = {} + + self.C.TopBar = vgui.Create("DPanel", self) + self.C.TopBar:Dock(TOP) + self.C.TopBar:SetHeight(36) + self.C.TopBar:DockPadding(5, 18, 5, 4) + self.C.TopBar:SetBackgroundColor(Color(176.5, 180, 185, 255)) + + local x = 7 + self.C.NameLabel = vgui.Create("DLabel", self.C.TopBar) + self.C.NameLabel:SetText("Chip Name") + self.C.NameLabel:SizeToContents() + self.C.NameLabel:SetTextColor(Color(255, 255, 255, 255)) + self.C.NameLabel:SetPos(x, 4) + self.C.Name = vgui.Create("DTextEntry", self.C.TopBar) + self.C.Name:SetEditable(true) + self.C.Name:SetSize(140, 15) + self.C.Name:SetPos(x - 2, 18) + + self.C.Name.OnLoseFocus = function (pnl) + if string.len(pnl:GetValue()) == 0 then + pnl:SetText("gate") + end + this:RequestFocus() + end + x = x + 160 + + self.C.ExecutionIntervalLabel = vgui.Create("DLabel", self.C.TopBar) + self.C.ExecutionIntervalLabel:SetText("Execution Interval") + self.C.ExecutionIntervalLabel:SizeToContents() + self.C.ExecutionIntervalLabel:SetTextColor(Color(255, 255, 255, 255)) + self.C.ExecutionIntervalLabel:SetPos(x, 4) + self.C.ExecutionIntervalLabel2 = vgui.Create("DLabel", self.C.TopBar) + self.C.ExecutionIntervalLabel2:SetText("every s") + self.C.ExecutionIntervalLabel2:SizeToContents() + self.C.ExecutionIntervalLabel2:SetTextColor(Color(255, 255, 255, 255)) + self.C.ExecutionIntervalLabel2:SetPos(x, 18) + self.C.ExecutionInterval = vgui.Create("DNumberWang", self.C.TopBar) + self.C.ExecutionInterval:SetInterval(0.01) + self.C.ExecutionInterval:SetDecimals(3) + self.C.ExecutionInterval:SetMax(1) + self.C.ExecutionInterval:SetMin(0.001) + self.C.ExecutionInterval:SetValue(0.1) + self.C.ExecutionInterval:SetSize(40, 15) + self.C.ExecutionInterval:SetPos(x + 31, 18) + + self.C.ExecutionInterval.OnLoseFocus = function (pnl) + this:RequestFocus() + end + x = x + 110 + + self.C.ExecuteOnLabel = vgui.Create("DLabel", self.C.TopBar) + self.C.ExecuteOnLabel:SetText("Execute on") + self.C.ExecuteOnLabel:SizeToContents() + self.C.ExecuteOnLabel:SetTextColor(Color(255, 255, 255, 255)) + self.C.ExecuteOnLabel:SetPos(x, 4) + self.C.ExecuteOnInputs = vgui.Create("DCheckBoxLabel", self.C.TopBar) + self.C.ExecuteOnInputs:SetPos(x, 18) + self.C.ExecuteOnInputs:SetText("Inputs") + self.C.ExecuteOnInputs:SetTextColor(Color(240, 240, 240, 255)) + self.C.ExecuteOnInputs:SetValue(true) + self.C.ExecuteOnInputs:SizeToContents() + self.C.ExecuteOnTimed = vgui.Create("DCheckBoxLabel", self.C.TopBar) + self.C.ExecuteOnTimed:SetPos(x + 60, 18) + self.C.ExecuteOnTimed:SetText("Timed") + self.C.ExecuteOnTimed:SetTextColor(Color(240, 240, 240, 255)) + self.C.ExecuteOnTimed:SetValue(true) + self.C.ExecuteOnTimed:SizeToContents() + self.C.ExecuteOnTrigger = vgui.Create("DCheckBoxLabel", self.C.TopBar) + self.C.ExecuteOnTrigger:SetPos(x + 120, 18) + self.C.ExecuteOnTrigger:SetText("Trigger In") + self.C.ExecuteOnTrigger:SetTextColor(Color(240, 240, 240, 255)) + self.C.ExecuteOnTrigger:SetValue(false) + self.C.ExecuteOnTrigger:SizeToContents() + + --Gate spawning + self.C.Holder = vgui.Create("DPanel", self) + self.C.Holder:SetWidth(300) + self.C.Holder:Dock(RIGHT) + self.C.Holder:SetBackgroundColor(Color(170, 174, 179, 255)) + + self.C.Tree = vgui.Create("DTree", self.C.Holder) + self.C.Tree:Dock(FILL) + self.C.Tree:DockMargin(2, 0, 2, 2) + + + --Gate searching + self.C.Search = vgui.Create("DTextEntry", self.C.Holder) + self.C.Search:Dock(TOP) + self.C.Search:DockMargin(2, 0, 2, 0) + self.C.Search:SetValue("Search...") + + local oldOnGetFocus = self.C.Search.OnGetFocus + function self.C.Search:OnGetFocus() + if self:GetValue() == "Search..." then -- If "Search...", erase it + self:SetValue("") + end + oldOnGetFocus(self) + end + + -- On lose focus + local oldOnLoseFocus = self.C.Search.OnLoseFocus + function self.C.Search:OnLoseFocus() + if self:GetValue() == "" then -- if empty, reset "Search..." text + timer.Simple(0, function() self:SetValue("Search...") end) + end + oldOnLoseFocus(self) + this:RequestFocus() + end + + self.C.SearchList = vgui.Create("DListView", self.C.Holder) + self.C.SearchList:AddColumn("Gate Name") + self.C.SearchList:AddColumn("Type"):SetWidth(10) + self.C.SearchList:AddColumn("Category"):SetWidth(35) + + -- Searching algorithm + local function Search(text) + text = string.lower(text) + + local results = {} + for action, gate in pairs(FPGAGateActions) do + local name = gate.name + local lowname = string.lower(name) + if string.find(lowname, text, 1, true) then -- If it has ANY match at all + results[#results + 1] = { name = gate.name, group = gate.group, type = "fpga", action = action, dist = WireLib.levenshtein(text, lowname) } + end + end + for action, gate in pairs(CPUGateActions) do + local name = gate.name + local lowname = string.lower(name) + if string.find(lowname, text, 1, true) then -- If it has ANY match at all + results[#results + 1] = { name = gate.name, group = gate.group, type = "cpu", action = action, dist = WireLib.levenshtein(text, lowname) } + end + end + for action, gate in pairs(GateActions) do + local name = gate.name + local lowname = string.lower(name) + if string.find(lowname, text, 1, true) then -- If it has ANY match at all + results[#results + 1] = { name = gate.name, group = gate.group, type = "wire", action = action, dist = WireLib.levenshtein(text, lowname) } + end + end + + table.SortByMember(results, "dist", true) + + return results + end + + -- Main searching + local searching + function self.C.Search:OnTextChanged() + local text = self:GetValue() + if text ~= "" then + if not searching then + searching = true + local x, y = this.C.Tree:GetPos() + local w, h = this.C.Tree:GetSize() + this.C.SearchList:SetPos(x + w, y) + this.C.SearchList:MoveTo(x, y, 0.1, 0, 1) + this.C.SearchList:SetSize(w, h) + this.C.SearchList:SetVisible(true) + end + local results = Search(text) + this.C.SearchList:Clear() + for i = 1, #results do + local result = results[i] + + local type + if result.type == "wire" then type = "Wire" + elseif result.type == "fpga" then type = "FPGA" + elseif result.type == "cpu" then type = "CPU" + end + local line = this.C.SearchList:AddLine(result.name, type, result.group) + + if this.SelectedInMenu then + if this.SelectedInMenu.type == result.type and this.SelectedInMenu.gate == result.action then + line:SetSelected(true) + end + end + + line.action = result.action + line.type = result.type + end + else + if searching then + searching = false + local x, y = this.C.Tree:GetPos() + local w, h = this.C.Tree:GetSize() + this.C.SearchList:SetPos(x, y) + this.C.SearchList:MoveTo(x + w, y, 0.1, 0, 1) + this.C.SearchList:SetSize(w, h) + timer.Create("fpga_customspawnmenu_hidesearchlist", 0.1, 1, function() + if IsValid(this.C.SearchList) then + this.C.SearchList:SetVisible(false) + end + end) + end + this.C.SearchList:Clear() + end + end + + function self.C.SearchList:OnClickLine(line) + -- Deselect old + local t = self:GetSelected() + if t and next(t) then + t[1]:SetSelected(false) + end + + line:SetSelected(true) -- Select new + this.SelectedInMenu = { type = line.type, gate = line.action } + this:RequestFocus() + end + + function self.C.Search:OnEnter() + if #this.C.SearchList:GetLines() > 0 then + this.C.SearchList:OnClickLine(this.C.SearchList:GetLine(1)) + end + this:RequestFocus() + end + + -- Set sizes & other settings + self.C.SearchList:SetVisible(false) + self.C.SearchList:SetMultiSelect(false) + + --utility + local function FillSubTree(editor, tree, node, temp, type, sortByName) + node.Icon:SetImage("icon16/folder.png") + + local subtree = {} + for k, v in pairs(temp) do + subtree[#subtree + 1] = { action = k, gate = v, name = v.name, order = v.order } + end + + if sortByName then + table.SortByMember(subtree, "name", true) + else + table.SortByMember(subtree, "order", true) + end + + for index = 1, #subtree do + local action, gate = subtree[index].action, subtree[index].gate + local node2 = node:AddNode(gate.name or "No name found :(") + node2.name = gate.name + node2.action = action + if gate.description then + node2:SetTooltip(gate.description) + end + function node2:DoClick() + editor.SelectedInMenu = { type = type, gate = self.action } + end + node2.Icon:SetImage("icon16/newspaper.png") + end + tree:InvalidateLayout() + end + + local function addGates(editor, gates, name, key, icon) + local CategoriesSorted = {} + + for gatetype, gatefuncs in pairs(gates) do + local allowed_gates = {} + local any_allowed = false + for k, v in pairs(gatefuncs) do + if not v.is_banned then + allowed_gates[k] = v + any_allowed = true + end + end + if any_allowed then + CategoriesSorted[#CategoriesSorted + 1] = { gatetype = gatetype, gatefuncs = allowed_gates } + end + end + + table.sort(CategoriesSorted, function(a, b) return a.gatetype < b.gatetype end) + + local parentNode = self.C.Tree:AddNode(name, icon) + function parentNode:DoClick() + self:SetExpanded(not self.m_bExpanded) + end + + for i = 1, #CategoriesSorted do + local gatetype = CategoriesSorted[i].gatetype + local gatefuncs = CategoriesSorted[i].gatefuncs + + local node = parentNode:AddNode(gatetype) + node.Icon:SetImage("icon16/folder.png") + FillSubTree(self, self.C.Tree, node, gatefuncs, key, name == "Wire") + function node:DoClick() + self:SetExpanded(not self.m_bExpanded) + end + end + end + + --EDITOR extras + local labelNode = self.C.Tree:AddNode("Label", "icon16/text_allcaps.png") + function labelNode:DoClick() + this.SelectedInMenu = { type = "editor", visual = "label", gate = nil } + end + local commentNode = self.C.Tree:AddNode("Comment", "icon16/comment.png") + function commentNode:DoClick() + this.SelectedInMenu = { type = "editor", visual = "comment", gate = nil } + end + + --FPGA gates + addGates(self, FPGAGatesSorted, "FPGA", "fpga", "icon16/bricks.png") + + --CPU gates + addGates(self, CPUGatesSorted, "CPU", "cpu", "icon16/computer.png") + + --WIREMOD gates + addGates(self, WireGatesSorted, "Wire", "wire", "icon16/connect.png") +end + + +-------------------------------------------------------- +--INTERACTION +-------------------------------------------------------- +function Editor:GetData() + return WireLib.von.serialize({ + Name = self.C.Name:GetValue(), + Nodes = self.Nodes, + Position = self.Position, + Zoom = self.Zoom, + ExecutionInterval = self.C.ExecutionInterval:GetValue(), + ExecuteOn = { + Inputs = self.C.ExecuteOnInputs:GetChecked(), + Timed = self.C.ExecuteOnTimed:GetChecked(), + Trigger = self.C.ExecuteOnTrigger:GetChecked() + } + }, false) +end + +function Editor:SetData(data) + local ok, data = pcall(WireLib.von.deserialize, data) + if not ok then + self:ClearData() + self.C.Name:SetValue("corrupt") + return + end + + if data.Nodes then self.Nodes = data.Nodes else self.Nodes = {} end + + if data.Name then self.C.Name:SetValue(data.Name) else self.C.Name:SetValue("gate") end + + if data.ExecutionInterval then + self.C.ExecutionInterval:SetValue(data.ExecutionInterval) + else + self.C.ExecutionInterval:SetValue(0.01) + end + + if data.ExecuteOn then + self.C.ExecuteOnInputs:SetValue(data.ExecuteOn.Inputs) + self.C.ExecuteOnTimed:SetValue(data.ExecuteOn.Timed) + self.C.ExecuteOnTrigger:SetValue(data.ExecuteOn.Trigger) + else + self.C.ExecuteOnInputs:SetValue(true) + self.C.ExecuteOnTimed:SetValue(true) + self.C.ExecuteOnTrigger:SetValue(false) + end + + if data.Position then self.Position = data.Position else self.Position = { 0, 0 } end + if data.Zoom then self.Zoom = data.Zoom else self.Zoom = 5 end + + self.InputNameCounter = 0 + self.OutputNameCounter = 0 + for nodeId, node in pairs(self.Nodes) do + local gate = getGate(node) + if not node.visual then + if not gate then self:DeleteNode(nodeId) + elseif gate.isInput then self.InputNameCounter = self.InputNameCounter + 1 + elseif gate.isOutput then self.OutputNameCounter = self.OutputNameCounter + 1 end + end + end +end + +function Editor:ClearData() + self.C.Name:SetValue("gate") + self.Nodes = {} + self.Position = { 0, 0 } + self.Zoom = 5 + self.InputNameCounter = 0 + self.OutputNameCounter = 0 +end + +function Editor:GetName() + return self.C.Name:GetValue() +end + +function Editor:HasNodes() + return next(self.Nodes) ~= nil +end + +-------------------------------------------------------- +--NODE INFO +-------------------------------------------------------- +--EDITOR NODE +function Editor:GetVisual(node) + if node.type == "editor" then + if node.visual == "label" then + return { method = "text", font = "FPGALabel", default = "Label" } + elseif node.visual == "comment" then + return { method = "text", font = "auto", default = "Comment" } + end + end + return nil +end + +--GATES (further up) + +-------------------------------------------------------- +--UTILITY +-------------------------------------------------------- +function Editor:PosToScr(x, y) + return (self:GetWide() - 300) / 2 - (self.Position[1] - x) * self.Zoom, self:GetTall() / 2 - (self.Position[2] - y) * self.Zoom +end + +function Editor:ScrToPos(x, y) + return self.Position[1] - ((self:GetWide() - 300) / 2 - x) / self.Zoom, self.Position[2] - (self:GetTall() / 2 - y) / self.Zoom +end + +function Editor:AlignPosToGrid(x, y) + return math.Round(x / self.GateSize) * self.GateSize, math.Round(y / self.GateSize) * self.GateSize +end + +function Editor:NodeInputPos(node, input) + return node.x - self.GateSize / 2 - self.IOSize / 2, node.y + (input - 1) * self.GateSize +end + +function Editor:NodeOutputPos(node, output) + return node.x + self.GateSize / 2 + self.IOSize / 2, node.y + (output - 1) * self.GateSize +end + +function Editor:GetConnectionKey(nodeId, inputNum) + return tostring(nodeId) .. "_" .. tostring(inputNum) +end + +function Editor:GetWaypointSelectionKey(connectionKey, waypointIndex) + return connectionKey .. "_wp" .. tostring(waypointIndex) +end + +function Editor:DrawBezierCurve(x1, y1, x2, y2, color, segments, flipStart, flipEnd) + segments = segments or 20 + + -- Calculate control points for smooth curve + local distance = math.sqrt((x2 - x1)^2 + (y2 - y1)^2) + local offsetX = math.min(distance * 0.5, 100) + + local cx1 = x1 + (flipStart and -offsetX or offsetX) + local cy1 = y1 + local cx2 = x2 - (flipEnd and -offsetX or offsetX) + local cy2 = y2 + + surface.SetDrawColor(color) + + local prevX, prevY = x1, y1 + for i = 1, segments do + local t = i / segments + local it = 1 - t + + -- Cubic Bezier formula + local x = it^3 * x1 + 3 * it^2 * t * cx1 + 3 * it * t^2 * cx2 + t^3 * x2 + local y = it^3 * y1 + 3 * it^2 * t * cy1 + 3 * it * t^2 * cy2 + t^3 * y2 + + surface.DrawLine(prevX, prevY, x, y) + prevX, prevY = x, y + end +end + +function Editor:DrawCircle(x, y, radius, segments) + segments = segments or 16 + local circle = {} + + for i = 0, segments do + local angle = (i / segments) * math.pi * 2 + table.insert(circle, { + x = x + math.cos(angle) * radius, + y = y + math.sin(angle) * radius + }) + end + + draw.NoTexture() + surface.DrawPoly(circle) +end + +function getInputAmountForNode(node) + local gate = getGate(node) + local amountOfInputs = 0 + if gate.compact_inputs then + inputLimit = gate.compact_inputs + for inputIdx, _ in pairs(node.connections) do + inputLimit = math.max(inputLimit, inputIdx + 1) + end + amountOfInputs = math.min(#gate.inputs, inputLimit) + else + amountOfInputs = #gate.inputs + end + return amountOfInputs +end + +-------------------------------------------------------- +--UNDO/REDO SYSTEM +-------------------------------------------------------- +function Editor:SaveState(description) + if not self.UndoEnabled then return end + + -- Deep copy current state + local state = { + Nodes = table.Copy(self.Nodes), + Position = {self.Position[1], self.Position[2]}, + Zoom = self.Zoom, + InputNameCounter = self.InputNameCounter, + OutputNameCounter = self.OutputNameCounter, + Description = description or "Unknown Action" + } + + -- Deep copy connections with waypoints + for nodeId, node in pairs(state.Nodes) do + if node.connections then + local newConnections = {} + for inputNum, connection in pairs(node.connections) do + newConnections[inputNum] = { + connection[1], + connection[2], + waypoints = connection.waypoints and table.Copy(connection.waypoints) or nil + } + end + state.Nodes[nodeId].connections = newConnections + end + end + + table.insert(self.UndoStack, state) + + -- Limit undo stack size + while #self.UndoStack > self.MaxUndoSteps do + table.remove(self.UndoStack, 1) + end + + -- Clear redo stack when new action is performed + self.RedoStack = {} +end + +function Editor:Undo() + if #self.UndoStack == 0 then return end + + -- Save current state to redo stack + local currentState = { + Nodes = table.Copy(self.Nodes), + Position = {self.Position[1], self.Position[2]}, + Zoom = self.Zoom, + InputNameCounter = self.InputNameCounter, + OutputNameCounter = self.OutputNameCounter + } + + -- Deep copy connections with waypoints for redo + for nodeId, node in pairs(currentState.Nodes) do + if node.connections then + local newConnections = {} + for inputNum, connection in pairs(node.connections) do + newConnections[inputNum] = { + connection[1], + connection[2], + waypoints = connection.waypoints and table.Copy(connection.waypoints) or nil + } + end + currentState.Nodes[nodeId].connections = newConnections + end + end + + table.insert(self.RedoStack, currentState) + + -- Restore previous state + local state = table.remove(self.UndoStack) + self:RestoreState(state) +end + +function Editor:Redo() + if #self.RedoStack == 0 then return end + + -- Save current state to undo stack + local currentState = { + Nodes = table.Copy(self.Nodes), + Position = {self.Position[1], self.Position[2]}, + Zoom = self.Zoom, + InputNameCounter = self.InputNameCounter, + OutputNameCounter = self.OutputNameCounter + } + + -- Deep copy connections with waypoints for undo + for nodeId, node in pairs(currentState.Nodes) do + if node.connections then + local newConnections = {} + for inputNum, connection in pairs(node.connections) do + newConnections[inputNum] = { + connection[1], + connection[2], + waypoints = connection.waypoints and table.Copy(connection.waypoints) or nil + } + end + currentState.Nodes[nodeId].connections = newConnections + end + end + + table.insert(self.UndoStack, currentState) + + -- Restore next state + local state = table.remove(self.RedoStack) + self:RestoreState(state) +end + +function Editor:RestoreState(state) + self.Nodes = table.Copy(state.Nodes) + self.Position = {state.Position[1], state.Position[2]} + self.Zoom = state.Zoom + self.InputNameCounter = state.InputNameCounter or 0 + self.OutputNameCounter = state.OutputNameCounter or 0 + + -- Deep copy connections with waypoints + for nodeId, node in pairs(self.Nodes) do + if node.connections then + local newConnections = {} + for inputNum, connection in pairs(node.connections) do + newConnections[inputNum] = { + connection[1], + connection[2], + waypoints = connection.waypoints and table.Copy(connection.waypoints) or nil + } + end + self.Nodes[nodeId].connections = newConnections + end + end + + -- Clear selections + self.SelectedNodes = {} + self.SelectedNodeCount = 0 +end + +-------------------------------------------------------- +--DETECTION +-------------------------------------------------------- +function Editor:GetNodeAt(x, y) + local gx, gy = self:ScrToPos(x, y) + + for k, node in pairs(self.Nodes) do + local gate = getGate(node) + + if gate then + --gates + local amountOfInputs = 0 + if gate.inputs then + amountOfInputs = getInputAmountForNode(node) + end + local amountOfOutputs = 1 + if gate.outputs then + amountOfOutputs = #gate.outputs + end + + local height = math.max(amountOfInputs, amountOfOutputs) + + if gx < node.x - self.GateSize / 2 then continue end + if gx > node.x + self.GateSize / 2 then continue end + if gy < node.y - self.GateSize / 2 then continue end + if gy > node.y - self.GateSize / 2 + self.GateSize * height then continue end + end + + local visual = self:GetVisual(node) + if visual then + --editor nodes + + if visual.method == "text" then + if visual.font == "auto" then + if (self.Zoom > self.ZoomThreshold) then + surface.SetFont("FPGATextBig") + elseif (self.Zoom <= self.ZoomHideThreshold) then + continue + else + surface.SetFont("FPGAText") + end + else + surface.SetFont(visual.font) + end + local tx, ty = surface.GetTextSize(node.value) + + if gx < node.x - (tx / 2) / self.Zoom then continue end + if gx > node.x + (tx / 2) / self.Zoom then continue end + if gy < node.y - (ty / 2) / self.Zoom then continue end + if gy > node.y + (ty / 2) / self.Zoom then continue end + else + continue + end + end + + return k + end + + return nil +end + +function Editor:GetNodeInputAt(x, y) + local gx, gy = self:ScrToPos(x, y) + + for k, node in pairs(self.Nodes) do + local gate = getGate(node) + + if not gate then continue end + + local amountOfInputs = getInputAmountForNode(node) + + if gx < node.x - self.GateSize / 2 - self.IOSize then continue end + if gx > node.x + self.GateSize / 2 + self.IOSize then continue end + if gy < node.y - self.GateSize / 2 then continue end + if gy > node.y - self.GateSize / 2 + self.GateSize * amountOfInputs then continue end + + for inputNum = 1, amountOfInputs do + local ix, iy = self:NodeInputPos(node, inputNum) + + if gx < ix - self.IOSize / 2 then continue end + if gx > ix + self.IOSize / 2 then continue end + if gy < iy - self.IOSize / 2 then continue end + if gy > iy + self.IOSize / 2 then continue end + + return k, inputNum + end + end + + return nil +end + +function Editor:GetNodeOutputAt(x, y) + local gx, gy = self:ScrToPos(x, y) + + for k, node in pairs(self.Nodes) do + local gate = getGate(node) + + if not gate then continue end + + if gx < node.x - self.GateSize / 2 - self.IOSize then continue end + if gx > node.x + self.GateSize / 2 + self.IOSize then continue end + if gy < node.y - self.GateSize / 2 then continue end + if gate.outputs then + if gy > node.y - self.GateSize / 2 + self.GateSize * #gate.outputs then continue end + else + if gy > node.y + self.GateSize / 2 then continue end + end + + if gate.outputs then + for outputNum, _ in pairs(gate.outputs) do + local ix, iy = self:NodeOutputPos(node, outputNum) + + if gx < ix - self.IOSize / 2 then continue end + if gx > ix + self.IOSize / 2 then continue end + if gy < iy - self.IOSize / 2 then continue end + if gy > iy + self.IOSize / 2 then continue end + + return k, outputNum + end + else + local ix, iy = self:NodeOutputPos(node, 1) + + if gx < ix - self.IOSize / 2 then continue end + if gx > ix + self.IOSize / 2 then continue end + if gy < iy - self.IOSize / 2 then continue end + if gy > iy + self.IOSize / 2 then continue end + + return k, 1 + end + end + + return nil +end + +function Editor:GetWaypointAt(x, y) + local radius = self.Zoom * self.IOSize / 2 + + for nodeId, node in pairs(self.Nodes) do + for inputNum, connectedTo in pairs(node.connections) do + local waypoints = connectedTo.waypoints + if waypoints then + for i, wp in ipairs(waypoints) do + local sx, sy = self:PosToScr(wp[1], wp[2]) + local dx = x - sx + local dy = y - sy + local dist = math.sqrt(dx * dx + dy * dy) + + if dist <= radius then + local key = self:GetConnectionKey(nodeId, inputNum) + return key, i + end + end + end + end + end + + return nil, nil +end + +function Editor:ClosestPointOnBezier(gx, gy, x1, y1, x2, y2, flipStart, flipEnd, samples) + samples = samples or 100 + + local distance = math.sqrt((x2 - x1)^2 + (y2 - y1)^2) + local offsetX = math.min(distance * 0.5, 100 / self.Zoom) + + local cx1 = x1 + (flipStart and -offsetX or offsetX) + local cy1 = y1 + local cx2 = x2 - (flipEnd and -offsetX or offsetX) + local cy2 = y2 + + local bestDist = math.huge + local bestX, bestY = x1, y1 + + for i = 0, samples do + local t = i / samples + local it = 1 - t + + local bx = it^3 * x1 + 3 * it^2 * t * cx1 + 3 * it * t^2 * cx2 + t^3 * x2 + local by = it^3 * y1 + 3 * it^2 * t * cy1 + 3 * it * t^2 * cy2 + t^3 * y2 + + local dx = gx - bx + local dy = gy - by + local dist = dx * dx + dy * dy -- with no sqrt, just to compare + + if dist < bestDist then + bestDist = dist + bestX, bestY = bx, by + end + end + + return bestX, bestY, math.sqrt(bestDist) +end + +function Editor:GetConnectionSegmentAt(x, y) + local gx, gy = self:ScrToPos(x, y) + local threshold = 8 / self.Zoom + + for nodeId, node in pairs(self.Nodes) do + local gate = getGate(node) + if not gate then continue end + + for inputNum, connectedTo in pairs(node.connections) do + local outputNodeId = connectedTo[1] + local outputNum = connectedTo[2] + local outputNode = self.Nodes[outputNodeId] + if not outputNode then continue end + + local points = {} + points[1] = {self:NodeOutputPos(outputNode, outputNum)} + + if connectedTo.waypoints then + for _, wp in ipairs(connectedTo.waypoints) do + points[#points + 1] = {wp[1], wp[2]} + end + end + + points[#points + 1] = {self:NodeInputPos(node, inputNum)} + + local hasWaypoints = #points > 2 + local lastFlipped + + for i = 1, #points - 1 do + local flipStart, flipEnd + + if hasWaypoints then + local goingLeft = points[i+1][1] < points[i][1] + + if i == 1 then + flipStart = false + flipEnd = goingLeft + else + flipStart = lastFlipped + flipEnd = (i < #points - 1) and goingLeft + end + lastFlipped = goingLeft + end + + local px, py, dist = self:ClosestPointOnBezier( + gx, gy, + points[i][1], points[i][2], + points[i+1][1], points[i+1][2], + flipStart, flipEnd + ) + + if dist <= threshold then + local key = self:GetConnectionKey(nodeId, inputNum) + return key, i, px, py + end + end + end + end + + return nil, nil, nil, nil +end + +-------------------------------------------------------- +--DRAWING +-------------------------------------------------------- +function Editor:PaintConnection(nodeFrom, output, nodeTo, input, type) + local connectedTo = nodeTo.connections[input] + local x1, y1 = self:NodeOutputPos(nodeFrom, output) + local sx1, sy1 = self:PosToScr(x1, y1) + local points = {{sx1, sy1}} + + if connectedTo and connectedTo.waypoints then + for _, wp in ipairs(connectedTo.waypoints) do + points[#points + 1] = {self:PosToScr(wp[1], wp[2])} + end + end + + local x2, y2 = self:NodeInputPos(nodeTo, input) + points[#points + 1] = {self:PosToScr(x2, y2)} + + local color = FPGATypeColor[type] + local hasWaypoints = #points > 2 + local lastFlipped + + -- Draw smooth Bezier curves between points + for i = 1, #points - 1 do + local flipStart, flipEnd + + if hasWaypoints then + local goingLeft = points[i+1][1] < points[i][1] + + if i == 1 then + flipStart = false + flipEnd = goingLeft + else + flipStart = lastFlipped + flipEnd = (i < #points - 1) and goingLeft + end + lastFlipped = goingLeft + end + + self:DrawBezierCurve( + points[i][1], points[i][2], + points[i+1][1], points[i+1][2], + color, nil, flipStart, flipEnd + ) + end + + if connectedTo and connectedTo.waypoints then + local key = self:GetConnectionKey(nodeTo.id or input, input) + local r = self.Zoom * self.IOSize / 2 + + for i, wp in ipairs(connectedTo.waypoints) do + local sx, sy = self:PosToScr(wp[1], wp[2]) + local wpKey = self:GetWaypointSelectionKey(key, i) + + if self.SelectedWaypoints[wpKey] then + surface.SetDrawColor(self.WaypointSelectedColor) + self:DrawCircle(sx, sy, r + 2) + elseif (self.DraggingWaypoint and self.DraggingWaypoint[1] == key and self.DraggingWaypoint[2] == i) + or (self.HoveringWaypoint and self.HoveringWaypoint[1] == key and self.HoveringWaypoint[2] == i) then + surface.SetDrawColor(self.WaypointHoverColor) + self:DrawCircle(sx, sy, r + 1) + end + + surface.SetDrawColor(self.WaypointColor) + self:DrawCircle(sx, sy, r) + end + end +end + +function Editor:PaintConnections() + for nodeId, node in pairs(self.Nodes) do + node.id = nodeId -- Store ID for waypoint tracking + local gate = getGate(node) + if not gate then continue end + for inputNum, connectedTo in pairs(node.connections) do + self:PaintConnection(self.Nodes[connectedTo[1]], connectedTo[2], node, inputNum, getInputType(gate, inputNum)) + end + end +end + +function Editor:PaintInput(x, y, type, name, ioSize) + surface.SetDrawColor(FPGATypeColor[type]) + surface.DrawRect(x, y, ioSize * 2, ioSize) + + if (self.Zoom > self.ZoomHideThreshold) then + local tx, ty = surface.GetTextSize(name) + surface.SetTextPos(x - tx - ioSize * 0.3, y + ioSize / 2 - ty / 2) + surface.DrawText(name) + end +end + +function Editor:PaintOutput(x, y, type, name, ioSize) + surface.SetDrawColor(FPGATypeColor[type]) + surface.DrawRect(x, y, ioSize * 2, ioSize) + + if (self.Zoom > self.ZoomHideThreshold) then + local _, ty = surface.GetTextSize(name) + surface.SetTextPos(x + ioSize * 2.3, y + ioSize / 2 - ty / 2) + surface.DrawText(name) + end +end + +function Editor:PaintGate(nodeId, node, gate) + local amountOfInputs = 0 + if gate.inputs then + amountOfInputs = getInputAmountForNode(node) + end + local amountOfOutputs = 1 + if gate.outputs then + amountOfOutputs = #gate.outputs + end + + local x, y = self:PosToScr(node.x, node.y) + + local size = self.Zoom * self.GateSize + local ioSize = self.Zoom * self.IOSize + + -- Inputs + if (self.Zoom > self.ZoomThreshold) then + surface.SetFont("FPGAIOBig") + else + surface.SetFont("FPGAIO") + end + surface.SetTextColor(255, 255, 255) + + + if gate.inputs then + for inputNum, inputName in pairs(gate.inputs) do + if inputNum > amountOfInputs then break end + local nx = x - size / 2 - ioSize + local ny = y - ioSize / 2 + (inputNum-1) * size + + self:PaintInput(nx, ny, getInputType(gate, inputNum), inputName, ioSize) + end + end + + -- Output + if gate.outputs then + for outputNum, outputName in pairs(gate.outputs) do + local nx = x + size / 2 - ioSize + local ny = y - ioSize / 2 + (outputNum - 1) * size + + self:PaintOutput(nx, ny, getOutputType(gate, outputNum), outputName, ioSize) + end + else + local nx = x + size / 2 - ioSize + local ny = y - ioSize / 2 + + self:PaintOutput(nx, ny, getOutputType(gate, 1), "Out", ioSize) + end + + -- Body + local height = math.max(amountOfInputs, amountOfOutputs, 1) + local cornerRadius = math.min(size * 0.15, 8) -- Adaptive corner radius + + local bodyColor + if self.SelectedNodes[nodeId] then + bodyColor = self.SelectedNodeColor + else + if gate.isInput then + bodyColor = self.InputNodeColor + elseif gate.isOutput then + bodyColor = self.OutputNodeColor + elseif gate.timed then + bodyColor = self.TimedNodeColor + else + bodyColor = self.NodeColor + end + end + + -- Animated glow for selected nodes (draw FIRST, behind everything) + if self.SelectedNodes[nodeId] then + local glowAnim = self:Animate("node_glow_" .. nodeId, 1, 5) + local glowAlpha = 100 + math.sin(SysTime() * 3) * 50 * glowAnim + local glowSize = 2 + glowAnim * 2 + + local glowColor = Color(0, 122, 204, glowAlpha) + draw.RoundedBox(cornerRadius + glowSize, x - size / 2 - glowSize, y - size / 2 - glowSize, size + glowSize * 2, size * height + glowSize * 2, glowColor) + else + -- Fade out glow + self:Animate("node_glow_" .. nodeId, 0, 8) + end + + -- Draw shadow (offset) + draw.RoundedBox(cornerRadius, x - size / 2 + 3, y - size / 2 + 3, size, size * height, self.NodeShadowColor) + + -- Draw main node body + draw.RoundedBox(cornerRadius, x - size / 2, y - size / 2, size, size * height, bodyColor) + + -- Draw subtle outline + surface.SetDrawColor(self.NodeOutlineColor) + -- Top + surface.DrawLine(x - size / 2 + cornerRadius, y - size / 2, x + size / 2 - cornerRadius, y - size / 2) + -- Bottom + surface.DrawLine(x - size / 2 + cornerRadius, y - size / 2 + size * height, x + size / 2 - cornerRadius, y - size / 2 + size * height) + -- Left + surface.DrawLine(x - size / 2, y - size / 2 + cornerRadius, x - size / 2, y - size / 2 + size * height - cornerRadius) + -- Right + surface.DrawLine(x + size / 2, y - size / 2 + cornerRadius, x + size / 2, y - size / 2 + size * height - cornerRadius) + + -- Name + if (self.Zoom > self.ZoomThreshold) then + surface.SetFont("FPGATextBig") + else + surface.SetFont("FPGAText") + end + surface.SetTextColor(255, 255, 255) + if (self.Zoom > self.ZoomHideThreshold) then + local tx, ty = surface.GetTextSize(gate.name) + surface.SetTextPos(x - tx / 2, y - ty / 2 - size / 1.2) + surface.DrawText(gate.name) + + surface.SetTextColor(200, 200, 200) + -- Input + if node.ioName then + local tx, ty = surface.GetTextSize(node.ioName) + surface.SetTextPos(x - tx / 2, y - ty / 2 + size / 1.2) + surface.DrawText(node.ioName) + -- Constant + elseif node.value then + local s = util.TypeToString(node.value) + local tx, ty = surface.GetTextSize(s) + surface.SetTextPos(x - tx / 2, y - ty / 2 + size / 1.2) + surface.DrawText(s) + end + end +end + +function Editor:PaintEditorNode(nodeId, node, visual) + local x, y = self:PosToScr(node.x, node.y) + + if visual.method == "text" then + if visual.font == "auto" then + if (self.Zoom > self.ZoomThreshold) then + surface.SetFont("FPGATextBig") + elseif (self.Zoom <= self.ZoomHideThreshold) then + return + else + surface.SetFont("FPGAText") + end + else + surface.SetFont(visual.font) + end + + local tx, ty = surface.GetTextSize(node.value) + surface.SetTextPos(x - tx / 2, y - ty / 2) + + surface.DrawText(node.value) + end +end + +function Editor:PaintNodes() + for nodeId, node in pairs(self.Nodes) do + local gate = getGate(node) + if gate then + self:PaintGate(nodeId, node, gate) + continue + end + + local visual = self:GetVisual(node) + if visual then + self:PaintEditorNode(nodeId, node, visual) + end + end +end + +function Editor:PaintGrid() + if not self.GridEnabled then return end + + local gridSize = self.GridSize * self.Zoom + if gridSize < 5 then return end + + local screenW = self:GetWide() - 300 + local screenH = self:GetTall() - 36 + + local startX = math.floor((self.Position[1] - screenW / (2 * self.Zoom)) / self.GridSize) * self.GridSize + local endX = math.ceil((self.Position[1] + screenW / (2 * self.Zoom)) / self.GridSize) * self.GridSize + local startY = math.floor((self.Position[2] - screenH / (2 * self.Zoom)) / self.GridSize) * self.GridSize + local endY = math.ceil((self.Position[2] + screenH / (2 * self.Zoom)) / self.GridSize) * self.GridSize + + surface.SetDrawColor(self.GridColor) + + for x = startX, endX, self.GridSize do + local sx, sy1 = self:PosToScr(x, startY) + local _, sy2 = self:PosToScr(x, endY) + surface.DrawLine(sx, sy1, sx, sy2) + end + + for y = startY, endY, self.GridSize do + local sx1, sy = self:PosToScr(startX, y) + local sx2, _ = self:PosToScr(endX, y) + surface.DrawLine(sx1, sy, sx2, sy) + end +end + +function Editor:PaintHelp() + local x, y = self:PosToScr(0, 0) + + surface.SetFont("FPGAText") + surface.SetTextColor(255, 255, 255) + + local helpText = [[Drag gates and draw selections with the left mouse button, + and drag around the plane with the right mouse button. + Connect inputs and outputs by left clicking on either, and dragging to the other. + By double clicking on an input or output, you can draw multiple connections at once. + + 'C' creates a gate at the cursor position (select which gate on the right menu) + 'X' deletes the gate or the waypoint under the cursor (or with a selection, deletes all selected gates) + 'E' edits the gate under the cursor (input/output names, constant values) + 'G' toggles align to grid + 'W' adds waypoint to connection line at cursor + + 'Ctrl + C' copies the selected gates (relative to mouse position) + 'Ctrl + V' pastes the copied gates (relative to mouse position) + 'Ctrl + Z' undo last action + 'Ctrl + Y' redo last undone action + + + To create inputs and outputs for the FPGA chip, use the gates found in 'FPGA/Input & Output' + ]] + + for line in helpText:gmatch("([^\n]*)\n?") do + local tx, ty = surface.GetTextSize(line) + surface.SetTextPos(x - tx / 2, y - ty / 2) + surface.DrawText(line) + y = y + ty + end +end + +function Editor:PaintMinimap() + if not self.MinimapConVar:GetBool() or not self:HasNodes() then return end + + local size = self.MinimapSize + local padding = self.MinimapPadding + local x = self:GetWide() - 300 - size - padding + local y = self:GetTall() - size - padding + + -- Background + draw.RoundedBox(4, x, y, size, size, self.MinimapBackgroundColor) + + -- Border + surface.SetDrawColor(self.MinimapBorderColor) + surface.DrawOutlinedRect(x, y, size, size) + + -- Calculate bounds of all nodes + if #self.Nodes < 1 then return end + local minX, maxX = math.huge, -math.huge + local minY, maxY = math.huge, -math.huge + + for _, node in pairs(self.Nodes) do + if node.visual == "comment" then continue end + if node.x then + minX = math.min(minX, node.x) + maxX = math.max(maxX, node.x) + minY = math.min(minY, node.y) + maxY = math.max(maxY, node.y) + end + end + + local worldW = maxX - minX + 200 + local worldH = maxY - minY + 200 + local scale = math.min((size - 20) / worldW, (size - 20) / worldH) + + -- Draw nodes on minimap + for nodeId, node in pairs(self.Nodes) do + if not node.x or node.visual == "comment" then continue end + + local nx = x + 10 + (node.x - minX + 100) * scale + local ny = y + 10 + (node.y - minY + 100) * scale + local nodeSize = 3 + + for inputNum, connectedTo in pairs(node.connections) do + local outputNodeId = connectedTo[1] + local outputNode = self.Nodes[outputNodeId] + if not outputNode then continue end + + local color = ColorAlpha(FPGATypeColor[getInputType(getGate(node), inputNum)], self.MinimapConnectionAlpha) + + -- Draw connection with waypoints + local points = {} + points[1] = {x + 10 + (outputNode.x - minX + 100) * scale, y + 10 + (outputNode.y - minY + 100) * scale} + + if connectedTo.waypoints then + for _, wp in ipairs(connectedTo.waypoints) do + points[#points + 1] = {x + 10 + (wp[1] - minX + 100) * scale, y + 10 + (wp[2] - minY + 100) * scale} + end + end + + points[#points + 1] = {x + 10 + (node.x - minX + 100) * scale, y + 10 + (node.y - minY + 100) * scale} + + local hasWaypoints = #points > 2 + local lastFlipped + + for i = 1, #points - 1 do + local flipStart, flipEnd + + if hasWaypoints then + local goingLeft = points[i+1][1] < points[i][1] + + if i == 1 then + flipStart = false + flipEnd = goingLeft + else + flipStart = lastFlipped + flipEnd = (i < #points - 1) and goingLeft + end + lastFlipped = goingLeft + end + + self:DrawBezierCurve( + points[i][1], points[i][2], + points[i+1][1], points[i+1][2], + color, 8, flipStart, flipEnd + ) + end + end + + if self.SelectedNodes[nodeId] then + surface.SetDrawColor(self.SelectedNodeColor) + surface.SetTextColor(self.SelectedNodeColor) + else + surface.SetDrawColor(self.NodeColor) + surface.SetTextColor(255, 255, 255) + end + + if node.visual == "label" then + surface.SetFont("FPGAIO") + local szx, szy = surface.GetTextSize(node.value) + surface.SetTextPos(nx - szx/2, ny - szy) + surface.DrawText(node.value) + else + surface.DrawRect(nx - nodeSize/2, ny - nodeSize/2, nodeSize, nodeSize) + end + end + + -- Draw viewport rectangle with clipping + local viewW = (self:GetWide() - 300) / self.Zoom + local viewH = self:GetTall() / self.Zoom + local vx = x + 10 + (self.Position[1] - minX + 100 - viewW/2) * scale + local vy = y + 10 + (self.Position[2] - minY + 100 - viewH/2) * scale + local vw = viewW * scale + local vh = viewH * scale + + local minimapLeft = x + 10 + local minimapTop = y + 10 + local minimapRight = x + size - 10 + local minimapBottom = y + size - 10 + + local viewLeft = vx + local viewTop = vy + local viewRight = vx + vw + local viewBottom = vy + vh + + local hasIntersection = not (viewRight < minimapLeft or + viewLeft > minimapRight or + viewBottom < minimapTop or + viewTop > minimapBottom) + + if hasIntersection then + local clippedLeft = math.max(viewLeft, minimapLeft) + local clippedTop = math.max(viewTop, minimapTop) + local clippedRight = math.min(viewRight, minimapRight) + local clippedBottom = math.min(viewBottom, minimapBottom) + + local clippedVx = clippedLeft + local clippedVy = clippedTop + local clippedVw = clippedRight - clippedLeft + local clippedVh = clippedBottom - clippedTop + + -- Draw clipped viewport rectangle + if clippedVw > 0 and clippedVh > 0 then + surface.SetDrawColor(self.SelectedNodeColor) + surface.DrawOutlinedRect(clippedVx, clippedVy, clippedVw, clippedVh) + if clippedVw > 2 and clippedVh > 2 then + surface.DrawOutlinedRect(clippedVx + 1, clippedVy + 1, clippedVw - 2, clippedVh - 2) + end + end + else + -- Camera is outside minimap bounds - show direction indicator + local viewCenterX = vx + vw / 2 + local viewCenterY = vy + vh / 2 + local circleX = math.Clamp(viewCenterX, minimapLeft, minimapRight) + local circleY = math.Clamp(viewCenterY, minimapTop, minimapBottom) + + surface.SetDrawColor(self.SelectedNodeColor) + self:DrawCircle(circleX, circleY, 5, 16) + end +end + +-------------------------------------------------------- +--ANIMATION SYSTEM +-------------------------------------------------------- +function Editor:Animate(key, targetValue, speed) + speed = speed or 10 + + if not self.Animations[key] then + self.Animations[key] = {value = 0, target = targetValue} + end + + local anim = self.Animations[key] + anim.target = targetValue + + -- Smooth lerp + local delta = self.LastFrameTime and (SysTime() - self.LastFrameTime) or 0 + local step = delta * speed + + if math.abs(anim.target - anim.value) > 0.01 then + anim.value = anim.value + (anim.target - anim.value) * step + else + anim.value = anim.target + end + + return anim.value +end + +function Editor:GetAnimValue(key) + return self.Animations[key] and self.Animations[key].value or 0 +end + +function Editor:Paint() + -- Update animation frame time + self.LastFrameTime = SysTime() + + surface.SetDrawColor(self.BackgroundColor) + surface.DrawRect(0, 36, self:GetWide() - 300, self:GetTall() - 36) + + self:PaintGrid() + self:PaintNodes() + self:PaintConnections() + + if not self:HasNodes() then + self:PaintHelp() + end + + -- Update hovering waypoint + local x, y = self:CursorPos() + if not self.DraggingWaypoint and not self.DraggingNode and not self.DrawingConnection then + self.HoveringWaypoint = nil + local key, wpIndex = self:GetWaypointAt(x, y) + if key then + self.HoveringWaypoint = {key, wpIndex} + end + end + + -- detects if mouse is let go outside of the window + if not input.IsMouseDown(MOUSE_RIGHT) then + self.DraggingWorld = nil + end + if not input.IsMouseDown(MOUSE_LEFT) then + self.DraggingNode = nil + self.DrawingConnection = nil + self.DrawingSelection = nil + self.DraggingWaypoint = nil + end + + -- moving the plane + if self.DraggingWorld then + local x, y = self:CursorPos() + local dx, dy = self.LastMousePos[1] - x, self.LastMousePos[2] - y + self.Position = { self.Position[1] + dx * (1 / self.Zoom), self.Position[2] + dy * (1 / self.Zoom) } + end + -- moving a node + if self.DraggingNode then + local x, y = self:CursorPos() + local gx, gy = self:ScrToPos(x, y) + gx = gx + self.DraggingOffset[1] + gy = gy + self.DraggingOffset[2] + + if self.AlignToGrid then + gx, gy = self:AlignPosToGrid(gx, gy) + end + + local cx, cy = self.Nodes[self.DraggingNode].x, self.Nodes[self.DraggingNode].y + local dx, dy = gx - cx, gy - cy -- Calculate movement delta + + if self.SelectedNodes[self.DraggingNode] and self.SelectedNodeCount > 0 then + -- Track which nodes are being moved for waypoint updates + local movedNodes = {} + + for selectedNodeId, selectedNode in pairs(self.SelectedNodes) do + local sox, soy = self.Nodes[selectedNodeId].x - cx, self.Nodes[selectedNodeId].y - cy + self.Nodes[selectedNodeId].x = gx + sox + self.Nodes[selectedNodeId].y = gy + soy + movedNodes[selectedNodeId] = true + end + + -- Update waypoints for connections involving moved nodes + for nodeId, node in pairs(self.Nodes) do + for inputNum, connection in pairs(node.connections) do + local outputNodeId = connection[1] + + -- Check if either end of connection is being moved + local inputMoved = movedNodes[nodeId] + local outputMoved = movedNodes[outputNodeId] + + if connection.waypoints then + for i, wp in ipairs(connection.waypoints) do + local wpKey = self:GetConnectionKey(nodeId, inputNum) + local wpSelKey = self:GetWaypointSelectionKey(wpKey, i) + + -- Move waypoint if it's selected OR if both ends are moving + if self.SelectedWaypoints[wpSelKey] then + -- Selected waypoint: move it by delta + connection.waypoints[i] = {wp[1] + dx, wp[2] + dy} + elseif inputMoved and outputMoved then + -- Both ends moving but waypoint not selected: move it too + connection.waypoints[i] = {wp[1] + dx, wp[2] + dy} + end + end + end + end + end + else + self.SelectedNodes = {} + self.Nodes[self.DraggingNode].x = gx + self.Nodes[self.DraggingNode].y = gy + end + end + -- NEW: moving waypoint(s) + if self.DraggingWaypoint then + local x, y = self:CursorPos() + local gx, gy = self:ScrToPos(x, y) + + if self.AlignToGrid then + gx, gy = self:AlignPosToGrid(gx, gy) + end + + local key = self.DraggingWaypoint[1] + local wpIndex = self.DraggingWaypoint[2] + + -- Calculate delta from the dragged waypoint's original position + local originalPos = nil + for nodeId, node in pairs(self.Nodes) do + for inputNum, connectedTo in pairs(node.connections) do + local testKey = self:GetConnectionKey(nodeId, inputNum) + if testKey == key and connectedTo.waypoints and connectedTo.waypoints[wpIndex] then + originalPos = {connectedTo.waypoints[wpIndex][1], connectedTo.waypoints[wpIndex][2]} + break + end + end + if originalPos then break end + end + + if originalPos then + local dx = gx - originalPos[1] + local dy = gy - originalPos[2] + + -- Move all selected waypoints by the same delta + for wpSelKey, wpData in pairs(self.SelectedWaypoints) do + local wpKey = wpData.key + local wpIdx = wpData.index + + for nodeId, node in pairs(self.Nodes) do + for inputNum, connectedTo in pairs(node.connections) do + local testKey = self:GetConnectionKey(nodeId, inputNum) + if testKey == wpKey and connectedTo.waypoints and connectedTo.waypoints[wpIdx] then + connectedTo.waypoints[wpIdx] = { + connectedTo.waypoints[wpIdx][1] + dx, + connectedTo.waypoints[wpIdx][2] + dy + } + end + end + end + end + end + end + -- drawing a connection + if self.DrawingConnection then + local nodeId = self.DrawingConnectionFrom[1] + local node = self.Nodes[nodeId] + local gate = getGate(node) + + local drawingConnectionFrom = { self.DrawingConnectionFrom[2] } + local selectedPort = self.DrawingConnectionFrom[2] + if self.DrawingConnectionAll then + drawingConnectionFrom = {} + local ports + local amountOfInputs = getInputAmountForNode(node) + if self.DrawingFromInput then ports = gate.inputs + elseif self.DrawingFromOutput then ports = gate.outputs or { "Out" } end + for portNum, portName in pairs(ports) do + if self.DrawingFromInput and portNum > amountOfInputs then break end + drawingConnectionFrom[portNum] = portNum + end + end + + local x, y = 0, 0 + for _, inputNum in pairs(drawingConnectionFrom) do + local type = "NORMAL" + if self.DrawingFromInput then + x, y = self:NodeInputPos(node, inputNum) + type = getInputType(gate, inputNum) + elseif self.DrawingFromOutput then + x, y = self:NodeOutputPos(node, inputNum) + type = getOutputType(gate, inputNum) + end + local sx, sy = self:PosToScr(x, y) + local mx, my = self:CursorPos() + self:DrawBezierCurve(sx, sy, mx, my + (inputNum - selectedPort) * self.GateSize * self.Zoom, FPGATypeColor[type], nil, self.DrawingFromInput, not self.DrawingFromOutput) + end + end + -- selecting + if self.DrawingSelection then + local sx, sy = self:PosToScr(self.DrawingSelection[1], self.DrawingSelection[2]) + local mx, my = self:CursorPos() + + local x, y = math.min(sx, mx), math.min(sy, my) + local w, h = math.abs(sx - mx), math.abs(sy - my) + + surface.SetDrawColor(self.SelectionColor) + surface.DrawOutlinedRect(x, y, w, h) + end + + self:PaintMinimap() + self:PaintOverlay() + + local x, y = self:CursorPos() + self.LastMousePos = { x, y } +end + +function Editor:PaintDebug() + surface.SetFont("Default") + surface.SetTextColor(255, 255, 255) + surface.SetTextPos(10, 50) + surface.DrawText(self.Position[1] .. ", " .. self.Position[2]) + surface.SetTextPos(10, 70) + surface.DrawText(self.Zoom) +end + +function Editor:PaintOverlay() + surface.SetFont("FPGAText") + local y = 43 + local xOffset = self:GetWide() - 310 + + if self.AlignToGrid then + surface.SetTextColor(100, 180, 255) + local tx, _ = surface.GetTextSize("Align to grid") + surface.SetTextPos(xOffset - tx, y) + surface.DrawText("Align to grid") + y = y + 20 + end + + if self.SelectedNodeCount > 0 then + surface.SetTextColor(255, 255, 120) + local text = self.SelectedNodeCount + if self.SelectedNodeCount == 1 then + text = text .. " node selected" + else + text = text .. " nodes selected" + end + local tx, _ = surface.GetTextSize(text) + surface.SetTextPos(xOffset - tx, y) + surface.DrawText(text) + y = y + 20 + end + + -- Display selected waypoints count + if self.SelectedWaypointCount > 0 then + surface.SetTextColor(255, 200, 100) + local text = self.SelectedWaypointCount + if self.SelectedWaypointCount == 1 then + text = text .. " waypoint selected" + else + text = text .. " waypoints selected" + end + local tx, _ = surface.GetTextSize(text) + surface.SetTextPos(xOffset - tx, y) + surface.DrawText(text) + y = y + 20 + end + + local copyDataSize = self:GetParent():GetCopyDataSize() + if copyDataSize > 0 then + surface.SetTextColor(120, 255, 120) + local text = copyDataSize + if copyDataSize == 1 then + text = text .. " node in paste buffer" + else + text = text .. " nodes in paste buffer" + end + local tx, _ = surface.GetTextSize(text) + surface.SetTextPos(xOffset - tx, y) + surface.DrawText(text) + y = y + 20 + end + + -- Display Undo/Redo stack info + if #self.UndoStack > 0 or #self.RedoStack > 0 then + surface.SetTextColor(180, 180, 180) + local text = "Undo: " .. #self.UndoStack .. " | Redo: " .. #self.RedoStack + local tx, _ = surface.GetTextSize(text) + surface.SetTextPos(xOffset - tx, y) + surface.DrawText(text) + y = y + 20 + end +end + + +-------------------------------------------------------- +--ACTIONS +-------------------------------------------------------- +function Editor:GetInputName() + self.InputNameCounter = self.InputNameCounter + 1 + return "In" .. self.InputNameCounter +end + +function Editor:GetOutputName() + self.OutputNameCounter = self.OutputNameCounter + 1 + return "Out" .. self.OutputNameCounter +end + +function Editor:CreateNode(selectedInMenu, x, y) + -- Save state before creating node + self:SaveState("Create Node") + + node = { + type = selectedInMenu.type, + gate = selectedInMenu.gate, + visual = selectedInMenu.visual, + x = x, + y = y, + connections = {} + } + + if self.AlignToGrid then + node.x, node.y = self:AlignPosToGrid(node.x, node.y) + end + + if selectedInMenu.gate then + local gateInfo = getGate(node) + + if gateInfo.isInput then + node.ioName = self:GetInputName() + elseif gateInfo.isOutput then + node.ioName = self:GetOutputName() + elseif gateInfo.isConstant then + local type = getOutputType(gateInfo, 1) + node.value = FPGADefaultValueForType[type] + end + elseif selectedInMenu.visual then + node.value = self:GetVisual(node).default + end + + --print("Created " .. table.ToString(node, "node", false)) + + table.insert(self.Nodes, node) +end + +function Editor:DeleteNode(nodeId) + --print("Deleted " .. nodeId) + + --remove all connections to this node + for k1, node in pairs(self.Nodes) do + for inputNum, connection in pairs(node.connections) do + if connection[1] == nodeId then + node.connections[inputNum] = nil + end + end + end + + --finally remove node + self.Nodes[nodeId] = nil +end + +function Editor:CopyNodes(nodeIds) + local nodeIdLookup = {} + local i = 1 + for nodeId, _ in pairs(nodeIds) do + nodeIdLookup[nodeId] = i + i = i + 1 + end + + local nodeAmount = table.Count(nodeIds) + local copyBuffer = {} + local copyOffset = { 0, 0 } + for nodeId, _ in pairs(nodeIds) do + local node = self.Nodes[nodeId] + local gate = getGate(node) + + local nodeCopy = { + type = node.type, + gate = node.gate, + x = node.x, + y = node.y, + connections = {} + } + + if gate then + if gate.isInput then + nodeCopy.ioName = node.ioName + elseif gate.isOutput then + nodeCopy.ioName = node.ioName + elseif gate.isConstant then + nodeCopy.value = node.value + nodeCopy.valueAsString = node.valueAsString + end + elseif node.visual then + nodeCopy.visual = node.visual + if node.visual == "label" or node.visual == "comment" then + nodeCopy.value = node.value + end + end + + for inputNum, connection in pairs(node.connections) do + if nodeIds[connection[1]] then + nodeCopy.connections[inputNum] = { + nodeIdLookup[connection[1]], + connection[2], + waypoints = connection.waypoints and table.Copy(connection.waypoints) or nil + } + end + end + + table.insert(copyBuffer, nodeCopy) + + copyOffset = { copyOffset[1] + node.x / nodeAmount, copyOffset[2] + node.y / nodeAmount } + end + + self:GetParent():SetCopyData(copyBuffer, copyOffset) +end + +function Editor:PasteNodes(x, y) + local copyData = self:GetParent():GetCopyData() + local copyBuffer = copyData[1] + local copyOffset = copyData[2] + + if not copyBuffer then return end + + local nodeIdLookup = {} + self.SelectedNodes = {} + self.SelectedNodeCount = 0 + local i = #self.Nodes + 1 + for copyNodeId, _ in pairs(copyBuffer) do + while self.Nodes[i] do + i = i + 1 + end + + nodeIdLookup[copyNodeId] = i + self.SelectedNodes[i] = true + self.SelectedNodeCount = self.SelectedNodeCount + 1 + i = i + 1 + end + + for copyNodeId, copyNode in pairs(copyBuffer) do + local nodeCopy = { + type = copyNode.type, + gate = copyNode.gate, + connections = {} + } + + local gate = getGate(copyNode) + if gate then + if gate.isInput then + nodeCopy.ioName = copyNode.ioName + elseif gate.isOutput then + nodeCopy.ioName = copyNode.ioName + elseif gate.isConstant then + nodeCopy.value = copyNode.value + nodeCopy.valueAsString = copyNode.valueAsString + end + elseif copyNode.visual then + nodeCopy.visual = copyNode.visual + if copyNode.visual == "label" or copyNode.visual == "comment" then + nodeCopy.value = copyNode.value + end + end + + for inputNum, connection in pairs(copyNode.connections) do + local waypointsCopy = nil + if connection.waypoints then + waypointsCopy = {} + local offsetX = x - copyOffset[1] + local offsetY = y - copyOffset[2] + + for i, wp in ipairs(connection.waypoints) do + waypointsCopy[i] = {wp[1] + offsetX, wp[2] + offsetY} + end + end + + nodeCopy.connections[inputNum] = { + nodeIdLookup[connection[1]], + connection[2], + waypoints = waypointsCopy + } + end + + nodeCopy.x = (copyNode.x - copyOffset[1]) + x + nodeCopy.y = (copyNode.y - copyOffset[2]) + y + + self.Nodes[nodeIdLookup[copyNodeId]] = nodeCopy + end +end + +-------------------------------------------------------- +--Local functions for editor (I'm not an egyptian) +-------------------------------------------------------- +local function deleteWaypointsByKey(self, key, indices) + table.sort(indices, function(a, b) return a > b end) + for nodeId, node in pairs(self.Nodes) do + for inputNum, connectedTo in pairs(node.connections) do + local testKey = self:GetConnectionKey(nodeId, inputNum) + if testKey == key and connectedTo.waypoints then + for _, idx in ipairs(indices) do + table.remove(connectedTo.waypoints, idx) + end + if #connectedTo.waypoints == 0 then + connectedTo.waypoints = nil + end + break + end + end + end +end + +local function deleteSelectedWaypoints(self) + local waypointsToDelete = {} + for wpSelKey, wpData in pairs(self.SelectedWaypoints) do + local key = wpData.key + local index = wpData.index + if not waypointsToDelete[key] then + waypointsToDelete[key] = {} + end + table.insert(waypointsToDelete[key], index) + end + + for key, indices in pairs(waypointsToDelete) do + deleteWaypointsByKey(self, key, indices) + end + + self.SelectedWaypoints = {} + self.SelectedWaypointCount = 0 +end + +local function deleteSelectedNodes(self) + for selectedNodeId in pairs(self.SelectedNodes) do + self:DeleteNode(selectedNodeId) + end + self.SelectedNodes = {} + self.SelectedNodeCount = 0 +end + +local function deleteWaypointAt(self, x, y) + local wpKey, wpIndex = self:GetWaypointAt(x, y) + if not wpKey then return false end + + self:SaveState("Delete Waypoint") + for nodeId, node in pairs(self.Nodes) do + for inputNum, connectedTo in pairs(node.connections) do + local testKey = self:GetConnectionKey(nodeId, inputNum) + if testKey == wpKey and connectedTo.waypoints then + table.remove(connectedTo.waypoints, wpIndex) + if #connectedTo.waypoints == 0 then + connectedTo.waypoints = nil + end + break + end + end + end + return true +end + +-------------------------------------------------------- +--EVENTS +-------------------------------------------------------- +--KEYBOARD +function Editor:OnKeyCodePressed(code) + local x, y = self:CursorPos() + local control = input.IsKeyDown(KEY_LCONTROL) or input.IsKeyDown(KEY_RCONTROL) + local shift = input.IsKeyDown(KEY_LSHIFT) or input.IsKeyDown(KEY_RSHIFT) + + if control then + self:OnShortcut(code) + + if code == KEY_Z then + --Undo + self:Undo() + return + elseif code == KEY_Y then + --Redo + self:Redo() + return + elseif code == KEY_C then + --Copy + if self.SelectedNodeCount > 0 then + self:CopyNodes(self.SelectedNodes) + else + self:GetParent():ClearCopyData() + end + elseif code == KEY_V then + --Paste + self:SaveState("Paste Nodes") + local gx, gy = self:ScrToPos(x, y) + self:PasteNodes(gx, gy) + end + elseif code == KEY_X then + if self.SelectedWaypointCount > 0 or self.SelectedNodeCount > 0 then + self:SaveState("Delete Selection") + if self.SelectedWaypointCount > 0 then + deleteSelectedWaypoints(self) + end + if self.SelectedNodeCount > 0 then + deleteSelectedNodes(self) + end + else + local deleted = deleteWaypointAt(self, x, y) + if not deleted then + local nodeId = self:GetNodeAt(x, y) + if nodeId then + self:SaveState("Delete Node") + self:DeleteNode(nodeId) + end + end + end + elseif code == KEY_C then + --Create + if self.SelectedInMenu then + local gx, gy = self:ScrToPos(x, y) + self:CreateNode(self.SelectedInMenu, gx, gy) + end + elseif code == KEY_W then + if shift then + -- Remove waypoint + local key, wpIndex = self:GetWaypointAt(x, y) + if key then + self:SaveState("Remove Waypoint") + for nodeId, node in pairs(self.Nodes) do + for inputNum, connectedTo in pairs(node.connections) do + local testKey = self:GetConnectionKey(nodeId, inputNum) + if testKey == key and connectedTo.waypoints then + table.remove(connectedTo.waypoints, wpIndex) + if #connectedTo.waypoints == 0 then + connectedTo.waypoints = nil + end + break + end + end + end + end + else + -- Add waypoint + local key, segmentIndex, px, py = self:GetConnectionSegmentAt(x, y) + if key then + self:SaveState("Add Waypoint") + for nodeId, node in pairs(self.Nodes) do + for inputNum, connectedTo in pairs(node.connections) do + local testKey = self:GetConnectionKey(nodeId, inputNum) + if testKey == key then + if not connectedTo.waypoints then + connectedTo.waypoints = {} + end + table.insert(connectedTo.waypoints, segmentIndex, {px, py}) + break + end + end + end + end + end + elseif code == KEY_E and not self.EditingNode then + --Edit + local nodeId = self:GetNodeAt(x, y) + if nodeId then + local node = self.Nodes[nodeId] + local gate = getGate(node) + + if gate then + if gate.isInput or gate.isOutput then + self.EditingNode = true + self:OpenNamingWindow(node, x, y) + elseif gate.isConstant then + self.EditingNode = true + self:OpenConstantSetWindow(node, x, y, gate.outputtypes[1]) + end + return + end + + local visual = self:GetVisual(node) + if visual then + if visual.method == "text" then + self.EditingNode = true + self:OpenNamingWindow(node, x, y) + end + return + end + end + elseif code == KEY_G then + self.AlignToGrid = not self.AlignToGrid + end +end + +--MOUSE +function Editor:OnMouseWheeled(delta) + local sx, sy = self:CursorPos() + + if sx > 0 and sy > 36 and sx < self:GetWide() - 300 and sy < self:GetTall() - 36 then + self.Zoom = self.Zoom + delta * 0.1 * self.Zoom + if self.Zoom < 0.1 then self.Zoom = 0.1 end + if self.Zoom > 10 then self.Zoom = 10 end + end +end + +function Editor:OnMousePressed(code) + self:RequestFocus() --Fix for weird bug, remove once resolved + + if code == MOUSE_LEFT then + self.MouseDown = true + + --double click detection + local doubleClick + if self.LastClick then + doubleClick = SysTime() - self.LastClick < 0.3 + else doubleClick = false end + self.LastClick = SysTime() + + local x, y = self:CursorPos() + local control = input.IsKeyDown(KEY_LCONTROL) or input.IsKeyDown(KEY_RCONTROL) + + local wpKey, wpIndex = self:GetWaypointAt(x, y) + if wpKey then + local wpSelectionKey = self:GetWaypointSelectionKey(wpKey, wpIndex) + + -- Handle selection + if control then + -- Ctrl+Click: Toggle selection + if self.SelectedWaypoints[wpSelectionKey] then + self.SelectedWaypoints[wpSelectionKey] = nil + self.SelectedWaypointCount = self.SelectedWaypointCount - 1 + else + self.SelectedWaypoints[wpSelectionKey] = {key = wpKey, index = wpIndex} + self.SelectedWaypointCount = self.SelectedWaypointCount + 1 + end + return + elseif not self.SelectedWaypoints[wpSelectionKey] then + -- Click on unselected waypoint: clear selection and select this one + self.SelectedWaypoints = {} + self.SelectedWaypointCount = 0 + self.SelectedWaypoints[wpSelectionKey] = {key = wpKey, index = wpIndex} + self.SelectedWaypointCount = 1 + end + + -- Start dragging selected waypoint(s) + self:SaveState("Move Waypoint(s)") + self.DraggingWaypoint = {wpKey, wpIndex} + return + end + + -- Check for node BEFORE clearing waypoints + local nodeId = self:GetNodeAt(x, y) + + -- Clear waypoint selection only if NOT clicking on a node and NOT holding Ctrl + if not nodeId and not control then + self.SelectedWaypoints = {} + self.SelectedWaypointCount = 0 + end + + --NODE DRAGGING + if nodeId then + self:SaveState("Move Node") -- Save BEFORE dragging starts + self.DraggingNode = nodeId + local gx, gy = self:ScrToPos(x, y) + self.DraggingOffset = { self.Nodes[nodeId].x - gx, self.Nodes[nodeId].y - gy } + + else + --CONNECTION DRAWING + local nodeId, inputNum = self:GetNodeInputAt(x, y) + if nodeId then + self:BeginDrawingConnection(nodeId, inputNum, nil, doubleClick) + else + local nodeId, outputNum = self:GetNodeOutputAt(x, y) + if nodeId then + self:BeginDrawingConnection(nodeId, nil, outputNum, doubleClick) + else + --SELECTION DRAWING + local gx, gy = self:ScrToPos(x, y) + self.DrawingSelection = { gx, gy } + end + end + + end + elseif code == MOUSE_RIGHT then + -- PLANE DRAGGING + self.DraggingWorld = true + end +end + +function Editor:OnMouseReleased(code) + local x, y = self:CursorPos() + + if code == MOUSE_LEFT then + self.MouseDown = false + self.DraggingNode = nil + self.DraggingWaypoint = nil + + if self.DrawingConnection then + self:OnDrawConnectionFinished(x, y) + elseif self.DrawingSelection then + self:OnDrawSelectionFinished(x, y) + end + elseif code == MOUSE_RIGHT then + self.DraggingWorld = false + end + +end + +--EDITOR EVENTS +function Editor:BeginDrawingConnection(nodeId, inputNum, outputNum, doubleClick) + self.DrawingConnectionAll = doubleClick + + if inputNum then + --check if something is connected to this input + node = self.Nodes[nodeId] + Input = node.connections[inputNum] + + --Input already connected + if Input then + self:SaveState("Disconnect Connection") + local connectedNode, connectedOutput = Input[1], Input[2] + node.connections[inputNum] = nil + self.DrawingConnectionFrom = { connectedNode, connectedOutput } + self.DrawingFromOutput = true + self.DrawingConnectionAll = false + else + --input not connected + self.DrawingConnectionFrom = { nodeId, inputNum } + self.DrawingFromInput = true + end + + self.DrawingConnection = true + end + + if outputNum then + self.DrawingConnection = true + self.DrawingFromOutput = true + self.DrawingConnectionFrom = { nodeId, outputNum } + end +end + +function Editor:OnDrawSelectionFinished(x, y) + local gx, gy = self.DrawingSelection[1], self.DrawingSelection[2] + local mx, my = self:CursorPos() + local mgx, mgy = self:ScrToPos(mx, my) + + local lx, ly = math.min(gx, mgx), math.min(gy, mgy) + local ux, uy = math.max(gx, mgx), math.max(gy, mgy) + + local control = input.IsKeyDown(KEY_LCONTROL) or input.IsKeyDown(KEY_RCONTROL) + + -- Don't clear selections if holding Ctrl + if not control then + self.SelectedNodes = {} + self.SelectedNodeCount = 0 + self.SelectedWaypoints = {} + self.SelectedWaypointCount = 0 + end + + -- Select nodes in rectangle + for nodeId, node in pairs(self.Nodes) do + if node.x < lx then continue end + if node.x > ux then continue end + if node.y < ly then continue end + if node.y > uy then continue end + + self.SelectedNodes[nodeId] = true + self.SelectedNodeCount = self.SelectedNodeCount + 1 + end + + -- Select waypoints in rectangle + for nodeId, node in pairs(self.Nodes) do + for inputNum, connectedTo in pairs(node.connections) do + if connectedTo.waypoints then + local key = self:GetConnectionKey(nodeId, inputNum) + for i, wp in ipairs(connectedTo.waypoints) do + if wp[1] >= lx and wp[1] <= ux and wp[2] >= ly and wp[2] <= uy then + local wpSelKey = self:GetWaypointSelectionKey(key, i) + if not self.SelectedWaypoints[wpSelKey] then + self.SelectedWaypoints[wpSelKey] = {key = key, index = i} + self.SelectedWaypointCount = self.SelectedWaypointCount + 1 + end + end + end + end + end + end + + self.DrawingSelection = nil +end + +function Editor:OnDrawConnectionFinished(x, y) + local fromNodeId = self.DrawingConnectionFrom[1] + local fromNode = self.Nodes[fromNodeId] + local fromGate = getGate(fromNode) + + local drawingConnectionFrom = { self.DrawingConnectionFrom[2] } + local selectedPort = self.DrawingConnectionFrom[2] + if self.DrawingConnectionAll then + drawingConnectionFrom = {} + local ports + local amountOfInputs = getInputAmountForNode(fromNode) + if self.DrawingFromInput then ports = fromGate.inputs + elseif self.DrawingFromOutput then ports = fromGate.outputs or { "Out" } end + for portNum, _ in pairs(ports) do + if self.DrawingFromInput and portNum > amountOfInputs then break end + drawingConnectionFrom[portNum] = portNum + end + end + + local inputNode = fromNode + local outputNodeId = fromNodeId + local outputNode = fromNode + local connectionMade = false + + for _, portNum in pairs(drawingConnectionFrom) do + local nodeId, inputNum, outputNum + if self.DrawingFromOutput then + nodeId, inputNum = self:GetNodeInputAt(x, y + (portNum - selectedPort) * self.GateSize * self.Zoom) + outputNum = portNum + elseif self.DrawingFromInput then + nodeId, outputNum = self:GetNodeOutputAt(x, y + (portNum - selectedPort) * self.GateSize * self.Zoom) + inputNum = portNum + end + + if nodeId then + if self.DrawingFromOutput then + inputNode = self.Nodes[nodeId] + elseif self.DrawingFromInput then + outputNode = self.Nodes[nodeId] + outputNodeId = nodeId + end + + --check type + local inputType, outputType + if self.DrawingFromOutput then + inputType = getInputType(getGate(inputNode), inputNum) + outputType = getOutputType(fromGate, outputNum) + elseif self.DrawingFromInput then + inputType = getInputType(fromGate, inputNum) + outputType = getOutputType(getGate(outputNode), outputNum) + end + + if inputType == outputType and inputNode ~= outputNode then + if not connectionMade then + self:SaveState("Create Connection") + connectionMade = true + end + --connect up + inputNode.connections[inputNum] = { outputNodeId, outputNum } + end + end + end + + self.DrawingConnection = false + self.DrawingFromInput = false + self.DrawingFromOutput = false +end + +-------------------------------------------------------- +--EXTRA WINDOWS +-------------------------------------------------------- +function Editor:CreateNamingWindow() + self.NamingWindow = vgui.Create("DFrame", self) + local pnl = self.NamingWindow + pnl:SetSize(300, 55) + pnl:ShowCloseButton(true) + pnl:SetDeleteOnClose(false) + pnl:MakePopup() + pnl:SetVisible(false) + pnl:SetTitle("Edit") + pnl:SetScreenLock(true) + do + local old = pnl.Close + function pnl.Close() + self.ForceDrawCursor = false + self.EditingNode = false + old(pnl) + end + end + + self.NamingNameEntry = vgui.Create("DTextEntry", pnl) + self.NamingNameEntry:Dock(BOTTOM) + self.NamingNameEntry:SetSize(175, 20) + self.NamingNameEntry:RequestFocus() +end + +function Editor:OpenNamingWindow(node, x, y) + if not self.NamingWindow then self:CreateNamingWindow() end + + if node.gate then + self.NamingNameEntry:SetText(node.ioName) + self.NamingNameEntry.OnEnter = function(pnl) + node.ioName = pnl:GetValue() + pnl:RequestFocus() + pnl:GetParent():Close() + end + elseif node.visual then + self.NamingNameEntry:SetText(node.value) + self.NamingNameEntry.OnEnter = function(pnl) + node.value = pnl:GetValue() + pnl:RequestFocus() + pnl:GetParent():Close() + end + else + return + end + + self.NamingWindow:SetVisible(true) + self.NamingWindow:MakePopup() -- This will move it above the E2 editor frame if it is behind it. + self.ForceDrawCursor = true + + local px, py = self:GetParent():GetPos() + self.NamingWindow:SetPos(px + x + 80, py + y + 30) + + local inputField = self.NamingNameEntry + local this = self + inputField.OnLoseFocus = function (pnl) + timer.Simple(0, function () if not pnl:GetParent():HasFocus() and this.EditingNode then pnl:OnEnter() end end) + pnl:GetParent():MoveToFront() + end + + self.NamingWindow.OnFocusChanged = function (pnl, gained) + if not gained then + timer.Simple(0, function () if not inputField:HasFocus() and this.EditingNode then inputField:OnEnter() end end) + pnl:MoveToFront() + end + end +end + +function Editor:CreateConstantSetWindow() + self.ConstantSetWindow = vgui.Create("DFrame", self) + local pnl = self.ConstantSetWindow + pnl:SetSize(200, 55) + pnl:ShowCloseButton(true) + pnl:SetDeleteOnClose(false) + pnl:MakePopup() + pnl:SetVisible(false) + pnl:SetTitle("Set constant value") + pnl:SetScreenLock(true) + + self.ConstantSetNormal = vgui.Create("DNumberWang", pnl) + self.ConstantSetNormal:Dock(BOTTOM) + self.ConstantSetNormal:SetSize(175, 20) + self.ConstantSetNormal:SetMinMax(-10 ^ 100, 10 ^ 100) + self.ConstantSetNormal:SetDecimals(6) + self.ConstantSetNormal:SetVisible(false) + self.ConstantSetString = vgui.Create("DTextEntry", pnl) + self.ConstantSetString:Dock(BOTTOM) + self.ConstantSetString:SetSize(175, 20) + self.ConstantSetString:SetVisible(false) + + do + local old = pnl.Close + function pnl.Close() + self.ForceDrawCursor = false + self.EditingNode = false + old(pnl) + end + end +end + +local function validateVector(string) + local x,y,z = string.match(string, "^ *([^%s,]+) *, *([^%s,]+) *, *([^%s,]+) *$") + return tonumber(x) ~= nil and tonumber(y) ~= nil and tonumber(z) ~= nil, x, y, z +end + +function Editor:OpenConstantSetWindow(node, x, y, type) + if not self.ConstantSetWindow then self:CreateConstantSetWindow() end + self.ConstantSetNormal:SetVisible(false) + self.ConstantSetNormal.OnEnter = function () end + self.ConstantSetString:SetVisible(false) + self.ConstantSetString.OnEnter = function () end + self.ConstantSetString.OnChange = function () end + self.ConstantSetString:SetValue("") + self.ConstantSetWindow:SetVisible(true) + self.ConstantSetWindow:MakePopup() -- This will move it above the FPGA editor if it is behind it. + self.ForceDrawCursor = true + + local invalidColor = Color(255, 0, 50, 255) + self.ConstantSetString:SetTextColor(color_black) + + local px, py = self:GetParent():GetPos() + self.ConstantSetWindow:SetPos(px + x + 80, py + y + 30) + + if type == "NORMAL" then + self.ConstantSetNormal:SetVisible(true) + self.ConstantSetNormal:SetValue(node.value) + self.ConstantSetNormal:RequestFocus() + self.ConstantSetNormal.OnEnter = function(pnl) + node.value = pnl:GetValue() + pnl:SetVisible(false) + pnl:GetParent():Close() + end + elseif type == "STRING" then + self.ConstantSetString:SetVisible(true) + self.ConstantSetString:SetText(node.value) + self.ConstantSetString:RequestFocus() + self.ConstantSetString.OnEnter = function(pnl) + node.value = pnl:GetValue() + pnl:SetVisible(false) + pnl:GetParent():Close() + end + elseif type == "VECTOR" then + self.ConstantSetString:SetVisible(true) + self.ConstantSetString:SetText(node.valueAsString or (node.value.x .. ", " .. node.value.y .. ", " .. node.value.z)) + self.ConstantSetString:RequestFocus() + self.ConstantSetString.OnEnter = function(pnl) + valid, x, y, z = validateVector(pnl:GetValue()) + if valid then + node.value = Vector(x, y, z) + node.valueAsString = pnl:GetValue() + pnl:SetVisible(false) + pnl:GetParent():Close() + end + end + self.ConstantSetString.OnChange = function(pnl) + valid, _, _, _ = validateVector(pnl:GetValue()) + if valid then pnl:SetTextColor(color_black) + else pnl:SetTextColor(invalidColor) end + end + elseif type == "ANGLE" then + self.ConstantSetString:SetVisible(true) + self.ConstantSetString:SetText(node.valueAsString or (node.value.x .. ", " .. node.value.y .. ", " .. node.value.z)) + self.ConstantSetString:RequestFocus() + self.ConstantSetString.OnEnter = function(pnl) + valid, p, y, r = validateVector(pnl:GetValue()) + if valid then + node.value = Angle(p, y, r) + node.valueAsString = pnl:GetValue() + pnl:SetVisible(false) + pnl:GetParent():Close() + end + end + self.ConstantSetString.OnChange = function(pnl) + valid, _, _, _ = validateVector(pnl:GetValue()) + if valid then pnl:SetTextColor(color_black) + else pnl:SetTextColor(invalidColor) end + end + end + + local inputField = self.ConstantSetString + if type == "NORMAL" then + inputField = self.ConstantSetNormal + end + + local this = self + inputField.OnLoseFocus = function (pnl) + timer.Simple(0, function () if not pnl:GetParent():HasFocus() and this.EditingNode then pnl:OnEnter() end end) + pnl:GetParent():MoveToFront() + end + + self.ConstantSetWindow.OnFocusChanged = function (pnl, gained) + if not gained then + timer.Simple(0, function () if not inputField:HasFocus() and this.EditingNode then inputField:OnEnter() end end) + pnl:MoveToFront() + end + end +end + +vgui.Register("MSLCDEditor", Editor, "Panel"); \ No newline at end of file diff --git a/lua/wire/client/segment_editor/wire_mslcd_editor.lua b/lua/wire/client/segment_editor/wire_mslcd_editor.lua new file mode 100644 index 0000000000..d11983d82f --- /dev/null +++ b/lua/wire/client/segment_editor/wire_mslcd_editor.lua @@ -0,0 +1,1331 @@ +local Editor = {} + +Editor.NewTabOnOpen = CreateClientConVar("wire_mslcd_new_tab_on_open", "1", true, false) + +surface.CreateFont("DefaultBold", { + font = "Tahoma", + size = 12, + weight = 700, + antialias = true, + additive = false, +}) + +------------------------------------------------------------------------ + +local invalid_filename_chars = { + ["*"] = "", + ["?"] = "", + [">"] = "", + ["<"] = "", + ["|"] = "", + ["\\"] = "", + ['"'] = "", + [" "] = "_", +} + +-- overwritten commands +function Editor:Init() + -- don't use any of the default DFrame UI components + for _, v in pairs(self:GetChildren()) do v:Remove() end + self.Title = "" + self.subTitle = "" + self.LastClick = 0 + self.GuiClick = 0 + self.SimpleGUI = false + self.Location = "" + + self.C = {} + self.Components = {} + + -- Load border colors, position, & size + self:LoadEditorSettings() + + local fontTable = { + font = "default", + size = 11, + weight = 300, + antialias = false, + additive = false, + } + surface.CreateFont("E2SmallFont", fontTable) + self.logo = surface.GetTextureID("vgui/e2logo") + + self:InitComponents() + + -- This turns off the engine drawing + self:SetPaintBackgroundEnabled(false) + self:SetPaintBorderEnabled(false) + + self:SetV(false) + + self:InitShutdownHook() +end + +local size = CreateClientConVar("wire_mslcd_editor_size", "800_600", true, false) +local pos = CreateClientConVar("wire_mslcd_editor_pos", "-1_-1", true, false) + +function Editor:LoadEditorSettings() + + -- Position & Size + local w, h = size:GetString():match("(%d+)_(%d+)") + w = tonumber(w) + h = tonumber(h) + + self:SetSize(w, h) + + local x, y = pos:GetString():match("(%-?%d+)_(%-?%d+)") + x = tonumber(x) + y = tonumber(y) + + if x == -1 and y == -1 then + self:Center() + else + self:SetPos(x, y) + end + + if x < 0 or y < 0 or x + w > ScrW() or y + h > ScrH() then -- If the editor is outside the screen, reset it + local width, height = math.min(ScrW() - 200, 800), math.min(ScrH() - 200, 620) + self:SetPos((ScrW() - width) / 2, (ScrH() - height) / 2) + self:SetSize(width, height) + + self:SaveEditorSettings() + end +end + +function Editor:SaveEditorSettings() + + -- Position & Size + local w, h = self:GetSize() + RunConsoleCommand("wire_mslcd_editor_size", w .. "_" .. h) + + local x, y = self:GetPos() + RunConsoleCommand("wire_mslcd_editor_pos", x .. "_" .. y) +end + + +function Editor:PaintOver() + surface.SetFont("DefaultBold") + surface.SetTextColor(255, 255, 255, 255) + surface.SetTextPos(10, 6) + surface.DrawText(self.Title .. self.subTitle) + surface.SetDrawColor(255, 255, 255, 255) + surface.SetTextPos(0, 0) + surface.SetFont("Default") + return true +end + +function Editor:PerformLayout() + local w, h = self:GetSize() + + for i = 1, #self.Components do + local c = self.Components[i] + local c_x, c_y, c_w, c_h = c.Bounds.x, c.Bounds.y, c.Bounds.w, c.Bounds.h + if (c_x < 0) then c_x = w + c_x end + if (c_y < 0) then c_y = h + c_y end + if (c_w < 0) then c_w = w + c_w - c_x end + if (c_h < 0) then c_h = h + c_h - c_y end + c:SetPos(c_x, c_y) + c:SetSize(c_w, c_h) + end + + self.C.Tree:SetHeight(self.C.Holder:GetTall()/2) + self.C.Properties:SetHeight(self.C.Holder:GetTall()/2) +end + +function Editor:OnMousePressed(mousecode) + if mousecode ~= 107 then return end -- do nothing if mouseclick is other than left-click + if not self.pressed then + self.pressed = true + self.p_x, self.p_y = self:GetPos() + self.p_w, self.p_h = self:GetSize() + self.p_mx = gui.MouseX() + self.p_my = gui.MouseY() + self.p_mode = self:getMode() + if self.p_mode == "drag" then + if self.GuiClick > CurTime() - 0.2 then + self:fullscreen() + self.pressed = false + self.GuiClick = 0 + else + self.GuiClick = CurTime() + end + end + end +end + +function Editor:OnMouseReleased(mousecode) + if mousecode ~= 107 then return end -- do nothing if mouseclick is other than left-click + self.pressed = false +end + +function Editor:Think() + if self.fs then return end + if self.pressed then + if not input.IsMouseDown(MOUSE_LEFT) then -- needs this if you let go of the mouse outside the panel + self.pressed = false + end + local movedX = gui.MouseX() - self.p_mx + local movedY = gui.MouseY() - self.p_my + if self.p_mode == "drag" then + local x = self.p_x + movedX + local y = self.p_y + movedY + if (x < 10 and x > -10) then x = 0 end + if (y < 10 and y > -10) then y = 0 end + if (x + self.p_w < ScrW() + 10 and x + self.p_w > ScrW() - 10) then x = ScrW() - self.p_w end + if (y + self.p_h < ScrH() + 10 and y + self.p_h > ScrH() - 10) then y = ScrH() - self.p_h end + self:SetPos(x, y) + end + if self.p_mode == "sizeBR" then + local w = self.p_w + movedX + local h = self.p_h + movedY + if (self.p_x + w < ScrW() + 10 and self.p_x + w > ScrW() - 10) then w = ScrW() - self.p_x end + if (self.p_y + h < ScrH() + 10 and self.p_y + h > ScrH() - 10) then h = ScrH() - self.p_y end + if (w < 300) then w = 300 end + if (h < 200) then h = 200 end + self:SetSize(w, h) + end + if self.p_mode == "sizeR" then + local w = self.p_w + movedX + if (w < 300) then w = 300 end + self:SetWide(w) + end + if self.p_mode == "sizeB" then + local h = self.p_h + movedY + if (h < 200) then h = 200 end + self:SetTall(h) + end + end + if not self.pressed then + local cursor = "arrow" + local mode = self:getMode() + if (mode == "sizeBR") then cursor = "sizenwse" + elseif (mode == "sizeR") then cursor = "sizewe" + elseif (mode == "sizeB") then cursor = "sizens" + end + if cursor ~= self.cursor then + self.cursor = cursor + self:SetCursor(self.cursor) + end + end + + local x, y = self:GetPos() + local w, h = self:GetSize() + + if w < 518 then w = 518 end + if h < 200 then h = 200 end + if x < 0 then x = 0 end + if y < 0 then y = 0 end + if x + w > ScrW() then x = ScrW() - w end + if y + h > ScrH() then y = ScrH() - h end + if y < 0 then y = 0 end + if x < 0 then x = 0 end + if w > ScrW() then w = ScrW() end + if h > ScrH() then h = ScrH() end + + self:SetPos(x, y) + self:SetSize(w, h) +end + +-- special functions + +function Editor:fullscreen() + if self.fs then + self:SetPos(self.preX, self.preY) + self:SetSize(self.preW, self.preH) + self.fs = false + else + self.preX, self.preY = self:GetPos() + self.preW, self.preH = self:GetSize() + self:SetPos(0, 0) + self:SetSize(ScrW(), ScrH()) + self.fs = true + end +end + +function Editor:getMode() + local x, y = self:GetPos() + local w, h = self:GetSize() + local ix = gui.MouseX() - x + local iy = gui.MouseY() - y + + if (ix < 0 or ix > w or iy < 0 or iy > h) then return end -- if the mouse is outside the box + if (iy < 22) then + return "drag" + end + if (iy > h - 10) then + if (ix > w - 20) then return "sizeBR" end + return "sizeB" + end + if (ix > w - 10) then + if (iy > h - 20) then return "sizeBR" end + return "sizeR" + end +end + +function Editor:addComponent(panel, x, y, w, h) + assert(not panel.Bounds) + panel.Bounds = { x = x, y = y, w = w, h = h } + self.Components[#self.Components + 1] = panel + return panel +end + +function Editor:GetLastTab() return self.LastTab end + +function Editor:SetLastTab(Tab) self.LastTab = Tab end + +function Editor:GetActiveTab() return self.C.TabHolder:GetActiveTab() end + +function Editor:GetNumTabs() return #self.C.TabHolder.Items end + +function Editor:SetActiveTab(val) + if self:GetActiveTab() == val then + val:GetPanel():RequestFocus() + return + end + self:SetLastTab(self:GetActiveTab()) + if isnumber(val) then + self.C.TabHolder:SetActiveTab(self.C.TabHolder.Items[val].Tab) + self:GetCurrentEditor():RequestFocus() + elseif val and val:IsValid() then + self.C.TabHolder:SetActiveTab(val) + val:GetPanel():RequestFocus() + end + + self:UpdateActiveTabTitle() +end + +function Editor:ExtractNameFromEditor() + return self:GetCurrentEditor():GetName() +end + +function Editor:UpdateActiveTabTitle() + local title = self:GetChosenFile() + local tabtext = self:ExtractNameFromEditor() + + if title then self:SubTitle("Editing: " .. title) else self:SubTitle() end + if tabtext then + if self:GetActiveTab():GetText() ~= tabtext then + self:GetActiveTab():SetText(tabtext) + self.C.TabHolder.tabScroller:InvalidateLayout() + end + end +end + +function Editor:GetActiveTabIndex() + local tab = self:GetActiveTab() + for k, v in pairs(self.C.TabHolder.Items) do + if tab == v.Tab then + return k + end + end + return -1 +end + + +function Editor:SetActiveTabIndex(index) + local tab = self.C.TabHolder.Items[index].Tab + + if not tab then return end + + self:SetActiveTab(tab) +end + +local old +function Editor:FixTabFadeTime() + if old ~= nil then return end -- It's already being fixed + old = self.C.TabHolder:GetFadeTime() + self.C.TabHolder:SetFadeTime(0) + timer.Simple(old, function() self.C.TabHolder:SetFadeTime(old) old = nil end) +end + +function Editor:CreateTab(chosenfile) + local editor = vgui.Create("MSLCDEditor") + --editor:SetDropTarget(0,0,200,200) + editor.ParentPanel = self + local sheet = self.C.TabHolder:AddSheet(chosenfile, editor) + editor.chosenfile = chosenfile + sheet.Tab:SetText(chosenfile) + editor:SetName(chosenfile) + sheet.Tab.OnMousePressed = function(pnl, keycode, ...) + + if keycode == MOUSE_MIDDLE then + self:CloseTab(pnl) + return + elseif keycode == MOUSE_RIGHT then + local menu = DermaMenu() + menu:AddOption("Close", function() + self:CloseTab(pnl) + end) + menu:AddOption("Close all others", function() + self:FixTabFadeTime() + self:SetActiveTab(pnl) + for i = self:GetNumTabs(), 1, -1 do + if self.C.TabHolder.Items[i] ~= sheet then + self:CloseTab(i) + end + end + end) + menu:AddSpacer() + menu:AddOption("Save", function() + self:FixTabFadeTime() + local old = self:GetLastTab() + local currentTab = self:GetActiveTab() + self:SetActiveTab(pnl) + self:SaveFile(self:GetChosenFile(), false) + self:SetActiveTab(currentTab) + self:SetLastTab(old) + end) + menu:AddOption("Save As", function() + self:FixTabFadeTime() + self:SetActiveTab(pnl) + self:SaveFile(self:GetChosenFile(), false, true) + end) + menu:AddOption("Reload", function() + self:FixTabFadeTime() + local old = self:GetLastTab() + self:SetActiveTab(pnl) + self:LoadFile(editor.chosenfile, false) + self:SetActiveTab(self:GetLastTab()) + self:SetLastTab(old) + self:UpdateActiveTabTitle() + end) + menu:AddSpacer() + menu:AddOption("Copy file path to clipboard", function() + if editor.chosenfile and editor.chosenfile ~= "" then + SetClipboardText(editor.chosenfile) + end + end) + menu:AddOption("Copy all file paths to clipboard", function() + local str = "" + for i = 1, self:GetNumTabs() do + local chosenfile = self:GetEditor(i).chosenfile + if chosenfile and chosenfile ~= "" then + str = str .. chosenfile .. ";" + end + end + str = str:sub(1, -2) + SetClipboardText(str) + end) + menu:Open() + return + end + + self:SetActiveTab(pnl) + end + + editor.OnTextChanged = function(panel) + timer.Create("mslcdautosave", 5, 1, function() + self:AutoSave() + end) + end + editor.OnShortcut = function(_, code) + if code == KEY_S then + self:SaveFile(self:GetChosenFile()) + end + end + editor:RequestFocus() + + self:OnTabCreated(sheet) -- Call a function that you can override to do custom stuff to each tab. + + return sheet +end + +function Editor:OnTabCreated(sheet) end + +-- This function is made to be overwritten + +function Editor:GetNextAvailableTab() + local activetab = self:GetActiveTab() + for _, v in pairs(self.C.TabHolder.Items) do + if v.Tab and v.Tab:IsValid() and v.Tab ~= activetab then + return v.Tab + end + end +end + +function Editor:NewTab() + local sheet = self:CreateTab("screen") + self:SetActiveTab(sheet.Tab) + + self:NewChip(true) +end + +function Editor:CloseTab(_tab) + local activetab, sheetindex + if _tab then + if isnumber(_tab) then + local temp = self.C.TabHolder.Items[_tab] + if temp then + activetab = temp.Tab + sheetindex = _tab + else + return + end + else + activetab = _tab + -- Find the sheet index + for k, v in pairs(self.C.TabHolder.Items) do + if activetab == v.Tab then + sheetindex = k + break + end + end + end + else + activetab = self:GetActiveTab() + -- Find the sheet index + for k, v in pairs(self.C.TabHolder.Items) do + if activetab == v.Tab then + sheetindex = k + break + end + end + end + + self:AutoSave() + + -- There's only one tab open, no need to actually close any tabs + if self:GetNumTabs() == 1 then + activetab:SetText("screen") + self.C.TabHolder:InvalidateLayout() + self:NewChip(true) + return + end + + -- Find the panel (for the scroller) + local tabscroller_sheetindex + for k, v in pairs(self.C.TabHolder.tabScroller.Panels) do + if v == activetab then + tabscroller_sheetindex = k + break + end + end + + self:FixTabFadeTime() + + if activetab == self:GetActiveTab() then -- We're about to close the current tab + if self:GetLastTab() and self:GetLastTab():IsValid() then -- If the previous tab was saved + if activetab == self:GetLastTab() then -- If the previous tab is equal to the current tab + local othertab = self:GetNextAvailableTab() -- Find another tab + if othertab and othertab:IsValid() then -- If that other tab is valid, use it + self:SetActiveTab(othertab) + self:SetLastTab() + else -- Reset the current tab (backup) + self:GetActiveTab():SetText("screen") + self.C.TabHolder:InvalidateLayout() + self:NewChip(true) + return + end + else -- Change to the previous tab + self:SetActiveTab(self:GetLastTab()) + self:SetLastTab() + end + else -- If the previous tab wasn't saved + local othertab = self:GetNextAvailableTab() -- Find another tab + if othertab and othertab:IsValid() then -- If that other tab is valid, use it + self:SetActiveTab(othertab) + else -- Reset the current tab (backup) + self:GetActiveTab():SetText("screen") + self.C.TabHolder:InvalidateLayout() + self:NewChip(true) + return + end + end + end + + self:OnTabClosed(activetab) -- Call a function that you can override to do custom stuff to each tab. + + activetab:GetPanel():Remove() + activetab:Remove() + table.remove(self.C.TabHolder.Items, sheetindex) + table.remove(self.C.TabHolder.tabScroller.Panels, tabscroller_sheetindex) + + self.C.TabHolder.tabScroller:InvalidateLayout() + local w, h = self.C.TabHolder:GetSize() + self.C.TabHolder:SetSize(w + 1, h) -- +1 so it updates +end + +function Editor:BuildNode(v,node,group) + local new = nil + if v.Type == GROUP then + new = node:AddNode( v.Text or "Group", "icon16/text_list_numbers.png" ) + self:BuildNodes(new,v) + elseif v.Type == UNION then + new = node:AddNode( v.Text or "Union", "icon16/text_list_bullets.png" ) + self:BuildNodes(new,v) + elseif v.Type == POLY then + new = node:AddNode( v.Text or "Poly", "icon16/bullet_green.png" ) + elseif v.Type == MATRIX then + new = node:AddNode( v.Text or "Matrix", "icon16/bullet_red.png" ) + elseif v.Type == ALIGN then + new = node:AddNode( v.Text or "Text", "icon16/bullet_pink.png" ) + elseif v.Type == OFFSET then + new = node:AddNode( v.Text or "Text", "icon16/bullet_white.png" ) + else + new = node:AddNode( v.Text or "Segment", "icon16/bullet_green.png" ) + end + new.group = v + new.parentgroup = group +end + +function Editor:BuildNodes(node,group) + for i,v in ipairs(group.Children) do + self:BuildNode(v,node,group) + end +end + +function Editor:OnTabClosed(sheet) end + +-- This function is made to be overwritten + +-- initialization commands +function Editor:InitComponents() + self.Components = {} + self.C = {} + + local function PaintFlatButton(panel, w, h) + if not (panel:IsHovered() or panel:IsDown()) then return end + derma.SkinHook("Paint", "Button", panel, w, h) + end + + local DMenuButton = vgui.RegisterTable({ + Init = function(panel) + panel:SetText("") + panel:SetSize(24, 20) + panel:Dock(LEFT) + end, + Paint = PaintFlatButton, + DoClick = function(panel) + local name = panel:GetName() + local f = name and name ~= "" and self[name] or nil + if f then f(self) end + end + }, "DButton") + + -- addComponent( panel, x, y, w, h ) + -- if x, y, w, h is minus, it will stay relative to right or buttom border + self.C.Close = self:addComponent(vgui.Create("DButton", self), -45-4, 0, 45, 22) -- Close button + self.C.Inf = self:addComponent(vgui.CreateFromTable(DMenuButton, self), -45-4-26, 0, 24, 22) -- Info button + self.C.ConBut = self:addComponent(vgui.CreateFromTable(DMenuButton, self), -45-4-24-26, 0, 24, 22) -- Control panel open/close + + self.C.Divider = vgui.Create("DHorizontalDivider", self) + + self.C.Browser = vgui.Create("wire_expression2_browser", self.C.Divider) -- Expression 2 file browser + do + local pnl = self.C.Browser.SearchBox + local old = pnl.OnLoseFocus + + function pnl.OnLoseFocus() + old(pnl) + self:GetCurrentEditor():RequestFocus() + end + end + self.C.Browser.Folders:SetClickOnDragHover(true) + + + self.C.MainPane = vgui.Create("DPanel", self.C.Divider) + self.C.Menu = vgui.Create("DPanel", self.C.MainPane) + self.C.TabHolder = vgui.Create("DPropertySheet", self.C.MainPane) + + self.C.Btoggle = vgui.CreateFromTable(DMenuButton, self.C.Menu) -- Toggle Browser being shown + self.C.Sav = vgui.CreateFromTable(DMenuButton, self.C.Menu) -- Save button + self.C.NewTab = vgui.CreateFromTable(DMenuButton, self.C.Menu, "NewTab") -- New tab button + self.C.CloseTab = vgui.CreateFromTable(DMenuButton, self.C.Menu, "CloseTab") -- Close tab button + self.C.Reload = vgui.CreateFromTable(DMenuButton, self.C.Menu) -- Reload tab button + --self.C.Segment = vgui.Create("DButton", self.C.Menu) + self.C.Poly = vgui.Create("DButton", self.C.Menu) + --self.C.Matrix = vgui.Create("DButton", self.C.Menu) + --self.C.Group = vgui.Create("DButton", self.C.Menu) + --self.C.Align = vgui.Create("DButton", self.C.Menu) + self.C.SaE = vgui.Create("DButton", self.C.Menu) -- Save & Exit button + self.C.SavAs = vgui.Create("DButton", self.C.Menu) -- Save As button + + self.C.Control = self:addComponent(vgui.Create("Panel", self), -350, 52, 342, -32) -- Control Panel + self.C.Credit = self:addComponent(vgui.Create("DTextEntry", self), -160, 52, 150, 190) -- Credit box + self.C.Credit:SetEditable(false) + + self.C.Holder = vgui.Create("DPanel", self) + self.C.Holder:SetWidth(300) + self.C.Holder:Dock(RIGHT) + self.C.Holder:SetBackgroundColor(Color(255, 255, 255, 255)) + + self.C.Tree = vgui.Create("DTree", self.C.Holder) + self.C.Tree:Dock(TOP) + self.C.Tree:DockMargin(2, 0, 2, 2) + + self.C.PropList = vgui.Create("DCategoryList", self.C.Holder) + self.C.PropList:Dock(FILL) + self.C.PropList:DockMargin(2, 0, 2, 2) + + self.C.EditorProps = vgui.Create("DForm", self.C.PropList) + self.C.EditorProps:Dock(FILL) + self.C.EditorProps:DockMargin(2, 0, 2, 2) + self.C.EditorProps:NumSlider("Snap increment", "wire_multisegmentlcd_snapinc", 0, 128) + self.C.EditorProps:SetLabel("Editor") + + self.C.Properties = vgui.Create("DForm", self.C.PropList) + self.C.Properties:Dock(FILL) + self.C.Properties:DockMargin(2, 0, 2, 2) + self.C.Prop_X = self.C.Properties:NumberWang("X",nil,-65536,65536) + function self.C.Prop_X.OnValueChanged(wang, val) + local editor = self:GetCurrentEditor() + if editor.SelectedSegment == nil then return end + editor.SelectedSegment.X = val + end + self.C.Prop_Y = self.C.Properties:NumberWang("Y",nil,-65536,65536) + function self.C.Prop_Y.OnValueChanged(wang, val) + local editor = self:GetCurrentEditor() + if editor.SelectedSegment == nil then return end + editor.SelectedSegment.Y = val + end + self.C.Properties:SetLabel("Properties") + + + self.C.VertProps = vgui.Create("DForm", self.C.PropList) + self.C.VertProps:Dock(FILL) + self.C.VertProps:DockMargin(2, 0, 2, 2) + self.C.VertProps:SetLabel("Vertex Properties") + + self.C.Vert_X = self.C.VertProps:NumberWang("X",nil,-65536,65536) + function self.C.Vert_X.OnValueChanged(wang, val) + local editor = self:GetCurrentEditor() + if editor.SelectedSegment == nil then return end + if editor.SelectedVert == 0 then return end + editor.SelectedSegment.Poly[editor.SelectedVert].x = val + end + self.C.Vert_Y = self.C.VertProps:NumberWang("Y",nil,-65536,65536) + function self.C.Vert_Y.OnValueChanged(wang, val) + local editor = self:GetCurrentEditor() + if editor.SelectedSegment == nil then return end + if editor.SelectedVert == 0 then return end + editor.SelectedSegment.Poly[editor.SelectedVert].y = val + end + + self.C.PropList:AddItem(self.C.EditorProps) + self.C.PropList:AddItem(self.C.Properties) + self.C.PropList:AddItem(self.C.VertProps) + -- extra component options + + self.C.Divider:SetLeft(self.C.Browser) + self.C.Divider:SetRight(self.C.MainPane) + self.C.Divider:Dock(FILL) + self.C.Divider:SetDividerWidth(4) + self.C.Divider:SetCookieName("wire_mslcd_editor_divider") + self.C.Divider:SetLeftMin(0) + + local DoNothing = function() end + self.C.MainPane.Paint = DoNothing + + self.C.Menu:Dock(TOP) + self.C.TabHolder:Dock(FILL) + + self.C.TabHolder:SetPadding(1) + + self.C.Menu:SetHeight(24) + self.C.Menu:DockPadding(2,2,2,2) + + self.C.SaE:SetSize(80, 20) + self.C.SaE:Dock(RIGHT) + self.C.SavAs:SetSize(51, 20) + self.C.SavAs:Dock(RIGHT) + + self.C.Inf:Dock(NODOCK) + self.C.ConBut:Dock(NODOCK) + + self.C.Close:SetText("r") + self.C.Close:SetFont("Marlett") + self.C.Close.DoClick = function(btn) self:Close() end + + self.C.ConBut:SetImage("icon16/wrench.png") + self.C.ConBut:SetText("") + self.C.ConBut.Paint = PaintFlatButton + self.C.ConBut.DoClick = function() self.C.Control:SetVisible(not self.C.Control:IsVisible()) end + + self.C.Inf:SetImage("icon16/information.png") + self.C.Inf.Paint = PaintFlatButton + self.C.Inf.DoClick = function(btn) + self.C.Credit:SetVisible(not self.C.Credit:IsVisible()) + end + + + self.C.Sav:SetImage("icon16/disk.png") + self.C.Sav.DoClick = function(button) self:SaveFile(self:GetChosenFile()) end + self.C.Sav:SetTooltip( "Save" ) + + self.C.NewTab:SetImage("icon16/page_white_add.png") + self.C.NewTab.DoClick = function(button) self:NewTab() end + self.C.NewTab:SetTooltip( "New tab" ) + + self.C.CloseTab:SetImage("icon16/page_white_delete.png") + self.C.CloseTab.DoClick = function(button) self:CloseTab() end + self.C.CloseTab:SetTooltip( "Close tab" ) + + self.C.Reload:SetImage("icon16/page_refresh.png") + self.C.Reload:SetTooltip( "Refresh file" ) + self.C.Reload.DoClick = function(button) + self:LoadFile(self:GetChosenFile(), false) + self:UpdateActiveTabTitle() + end + + self.C.Poly:SetText("Poly") + self.C.Poly:SetTooltip( "Poly Tool" ) + self.C.Poly:Dock(LEFT) + self.C.Poly.DoClick = function(button) + self:GetCurrentEditor():SetMode(POLY) + end + + + + self.C.SaE:SetText("Save and Exit") + self.C.SaE.DoClick = function(button) self:SaveFile(self:GetChosenFile(), true) end + + self.C.SavAs:SetText("Save As") + self.C.SavAs.DoClick = function(button) self:SaveFile(self:GetChosenFile(), false, true) end + + self.C.Browser:AddRightClick(self.C.Browser.filemenu, 4, "Save to", function() + Derma_Query("Overwrite this file?", "Save To", + "Overwrite", function() + self:SaveFile(self.C.Browser.File.FileDir) + end, + "Cancel") + end) + self.C.Browser.OnFileOpen = function(_, filepath, newtab) + self:Open(filepath, nil, newtab) + end + + self.C.Btoggle:SetImage("icon16/application_side_contract.png") + function self.C.Btoggle.DoClick(button) + if button.hide then + self.C.Divider:LoadCookies() + else + self.C.Divider:SetLeftWidth(0) + end + self.C.Divider:InvalidateLayout() + button:InvalidateLayout() + end + + local oldBtoggleLayout = self.C.Btoggle.PerformLayout + function self.C.Btoggle.PerformLayout(button) + oldBtoggleLayout(button) + if self.C.Divider:GetLeftWidth() > 0 then + button.hide = false + button:SetImage("icon16/application_side_contract.png") + else + button.hide = true + button:SetImage("icon16/application_side_expand.png") + end + end + + self.C.Credit:SetTextColor(Color(0, 0, 0, 255)) + self.C.Credit:SetText("\t\tCREDITS\n\n\tEditor by: \tSyranide and Shandolum\n\n\tTabs (and more) added by Divran.\n\n\tFixed for GMod13 By Ninja101 \n\n\tRewritten into a node editor by Lysdal\n\n\tRewritten again into a segment editor by wav3") -- woohoo! + self.C.Credit:SetMultiline(true) + self.C.Credit:SetVisible(false) + + self:InitControlPanel(self.C.Control) -- making it seperate for better overview + self.C.Control:SetVisible(false) + + self:CreateTab("screen") +end + +function Editor:AutoSave() + local buffer = self:GetData() + if self.savebuffer == buffer or buffer == "" then return end + self.savebuffer = buffer + file.CreateDir(self.Location) + file.Write(self.Location .. "/_autosave_.txt", buffer) +end + +function Editor:AddControlPanelTab(label, icon, tooltip) + local frame = self.C.Control + local panel = vgui.Create("DPanel") + local ret = frame.TabHolder:AddSheet(label, panel, icon, false, false, tooltip) + local old = ret.Tab.OnMousePressed + function ret.Tab.OnMousePressed(...) + timer.Simple(0.1,function() frame:ResizeAll() end) -- timers solve everything + old(...) + end + + ret.Panel:SetBackgroundColor(Color(96, 96, 96, 255)) + + return ret +end + +function Editor:InitControlPanel(frame) + -- Add a property sheet to hold the tabs + local tabholder = vgui.Create("DPropertySheet", frame) + tabholder:SetPos(2, 4) + frame.TabHolder = tabholder + + -- They need to be resized one at a time... dirty fix incoming (If you know of a nicer way to do this, don't hesitate to fix it.) + local function callNext(t, n) + local obj = t[n] + local pnl = obj[1] + if pnl and pnl:IsValid() then + local x, y = obj[2], obj[3] + pnl:SetPos(x, y) + local w, h = pnl:GetParent():GetSize() + local wofs, hofs = w - x * 2, h - y * 2 + pnl:SetSize(wofs, hofs) + end + n = n + 1 + if n <= #t then + timer.Simple(0, function() callNext(t, n) end) + end + end + + function frame:ResizeAll() + timer.Simple(0, function() + callNext(self.ResizeObjects, 1) + end) + end + + -- Resize them at the right times + local oldFrameSetSize = frame.SetSize + function frame:SetSize(...) + self:ResizeAll() + oldFrameSetSize(self, ...) + end + + local oldFrameSetVisible = frame.SetVisible + function frame:SetVisible(...) + self:ResizeAll() + oldFrameSetVisible(self, ...) + end + + -- Function to add more objects to resize automatically + frame.ResizeObjects = {} + function frame:AddResizeObject(...) + self.ResizeObjects[#self.ResizeObjects + 1] = { ... } + end + + -- Our first object to auto resize is the tabholder. This sets it to position 2,4 and with a width and height offset of w-4, h-8. + frame:AddResizeObject(tabholder, 2, 4) + + -- ------------------------------------------- EDITOR TAB + local sheet = self:AddControlPanelTab("Editor", "icon16/wrench.png", "Options for the editor itself.") + + -- WINDOW BORDER COLORS + + local dlist = vgui.Create("DPanelList", sheet.Panel) + dlist.Paint = function() end + frame:AddResizeObject(dlist, 4, 4) + dlist:EnableVerticalScrollbar(true) + + local NewTabOnOpen = vgui.Create("DCheckBoxLabel") + dlist:AddItem(NewTabOnOpen) + NewTabOnOpen:SetConVar("wire_mslcd_new_tab_on_open") + NewTabOnOpen:SetText("New tab on open") + NewTabOnOpen:SizeToContents() + NewTabOnOpen:SetTooltip("Enable/disable loaded files opening in a new tab.\nIf disabled, loaded files will be opened in the current tab.") + + local SaveTabsOnClose = vgui.Create("DCheckBoxLabel") + dlist:AddItem(SaveTabsOnClose) + SaveTabsOnClose:SetConVar("wire_mslcd_editor_savetabs") + SaveTabsOnClose:SetText("Save tabs on close") + SaveTabsOnClose:SizeToContents() + SaveTabsOnClose:SetTooltip("Save the currently opened tab file paths on shutdown.\nOnly saves tabs whose files are saved.") + + local OpenOldTabs = vgui.Create("DCheckBoxLabel") + dlist:AddItem(OpenOldTabs) + OpenOldTabs:SetConVar("wire_mslcd_editor_openoldtabs") + OpenOldTabs:SetText("Open old tabs on load") + OpenOldTabs:SizeToContents() + OpenOldTabs:SetTooltip("Open the tabs from the last session on load.\nOnly tabs whose files were saved before disconnecting from the server are stored.") + + local WorldClicker = vgui.Create("DCheckBoxLabel") + dlist:AddItem(WorldClicker) + WorldClicker:SetConVar("wire_mslcd_editor_worldclicker") + WorldClicker:SetText("Enable Clicking Outside Editor") + WorldClicker:SizeToContents() + function WorldClicker.OnChange(pnl, bVal) + self:GetParent():SetWorldClicker(bVal) + end + + local Minimap = vgui.Create("DCheckBoxLabel") + dlist:AddItem(Minimap) + Minimap:SetConVar("wire_mslcd_editor_minimap") + Minimap:SetText("Show minimap") + Minimap:SizeToContents() + Minimap:SetTooltip("Enable or disable the minimap in the editor.") + --------------------------------------------- MSLCD TAB + sheet = self:AddControlPanelTab("MSLCD", "icon16/computer.png", "Options for MSLCD.") + + dlist = vgui.Create("DPanelList", sheet.Panel) + dlist.Paint = function() end + frame:AddResizeObject(dlist, 4, 4) + dlist:EnableVerticalScrollbar(true) + + dlist:InvalidateLayout() +end + +------------------------------------- + + +function Editor:NewChip(incurrent) + if not incurrent and self.NewTabOnOpen:GetBool() then + self:NewTab() + else + self:AutoSave() + self:ChosenFile() + + -- Set title + self:GetActiveTab():SetText("screen") + + self.C.TabHolder:InvalidateLayout() + self:ClearData() + end +end + +local wire_mslcd_editor_savetabs = CreateClientConVar("wire_mslcd_editor_savetabs", "1", true, false) + +local id = 0 +function Editor:InitShutdownHook() + id = id + 1 + + -- save code when shutting down + hook.Add("ShutDown", "wire_mslcd_ShutDown" .. id, function() + local buffer = self:GetData() + if not self:GetCurrentEditor():HasNodes() then return end + + file.CreateDir(self.Location) + file.Write(self.Location .. "/_shutdown_.txt", buffer) + + if wire_mslcd_editor_savetabs:GetBool() then + self:SaveTabs() + end + end) +end + +function Editor:SaveTabs() + local strtabs = "" + local tabs = {} + for i=1, self:GetNumTabs() do + local chosenfile = self:GetEditor(i).chosenfile + if chosenfile and chosenfile ~= "" and not tabs[chosenfile] then + strtabs = strtabs .. chosenfile .. ";" + tabs[chosenfile] = true -- Prevent duplicates + end + end + + strtabs = strtabs:sub(1, -2) + + file.CreateDir(self.Location) + file.Write(self.Location .. "/_tabs_.txt", strtabs) +end + +local wire_mslcd_editor_openoldtabs = CreateClientConVar("wire_mslcd_editor_openoldtabs", "1", true, false) + +function Editor:OpenOldTabs() + if not file.Exists(self.Location .. "/_tabs_.txt", "DATA") then return end + + -- Read file + local tabs = file.Read(self.Location .. "/_tabs_.txt") + if not tabs or tabs == "" then return end + + -- Explode around ; + tabs = string.Explode(";", tabs) + if not tabs or #tabs == 0 then return end + + -- Temporarily remove fade time + self:FixTabFadeTime() + + local is_first = true + for _, v in pairs(tabs) do + if v and v ~= "" then + if (file.Exists(v, "DATA")) then + -- Open it in a new tab + self:LoadFile(v, true) + + -- If this is the first loop, close the initial tab. + if (is_first) then + timer.Simple(0, function() + self:CloseTab(1) + end) + is_first = false + end + end + end + end +end + +function Editor:SubTitle(sub) + if not sub then self.subTitle = "" + else self.subTitle = " - " .. sub + end +end + +local wire_mslcd_editor_worldclicker = CreateClientConVar("wire_mslcd_editor_worldclicker", "0", true, false) +function Editor:SetV(bool) + if bool then + self:MakePopup() + self:InvalidateLayout(true) + end + self:SetVisible(bool) + self:SetKeyboardInputEnabled(bool) + self:GetParent():SetWorldClicker(wire_mslcd_editor_worldclicker:GetBool() and bool) -- Enable this on the background so we can update MSLCD's without closing the editor +end + +function Editor:GetChosenFile() + return self:GetCurrentEditor().chosenfile +end + +function Editor:ChosenFile(Line) + self:GetCurrentEditor().chosenfile = Line + if Line then + self:SubTitle("Editing: " .. Line) + else + self:SubTitle() + end +end + +function Editor:FindOpenFile(FilePath) + for i = 1, self:GetNumTabs() do + local ed = self:GetEditor(i) + if ed.chosenfile == FilePath then + return ed + end + end +end + +function Editor:ExtractName() + self.savefilefn = self:ExtractNameFromEditor() + return +end + +function Editor:ClearCopyData() + self.copyBuffer = nil + self.copyBufferSize = 0 + self.copyOffset = nil +end + +function Editor:SetCopyData(buffer, offset) + self.copyBuffer = buffer + self.copyBufferSize = table.Count(buffer) + self.copyOffset = offset +end + +function Editor:GetCopyData() + if self.copyBuffer then + return {self.copyBuffer, self.copyOffset} + else + return {nil, nil} + end +end + +function Editor:GetCopyDataSize() + if self.copyBufferSize then + return self.copyBufferSize + end + return 0 +end + + +function Editor:SetData(data) + self:GetCurrentEditor():SetData(data) + local childs = self.C.Tree.RootNode:GetChildren() + if childs[4] ~= nil then + childs[4]:Remove() + end + self:BuildNodes(self.C.Tree,self:GetCurrentEditor().SegmentTree) + self.savebuffer = self:GetData() + self:ExtractName() +end + +function Editor:SetDataFromEnt(data) + self:GetCurrentEditor().SegmentTree = table.Copy(data) + local childs = self.C.Tree.RootNode:GetChildren() + if childs[4] ~= nil then + childs[4]:Remove() + end + self:BuildNodes(self.C.Tree,self:GetCurrentEditor().SegmentTree) + self.savebuffer = self:GetData() + self:ExtractName() +end + +function Editor:ClearData() + self:GetCurrentEditor():ClearData() + self.savebuffer = self:GetData() +end + +function Editor:GetEditor(n) + if self.C.TabHolder.Items[n] then + return self.C.TabHolder.Items[n].Panel + end +end + +function Editor:GetData() + local data = self:GetCurrentEditor():GetData() + + local last_data = "" + if #data < 64 then + last_data = data + else + last_data = data:sub(-64 + #data % 8) + end + + --MSLCDSetToolInfo(self:ExtractNameFromEditor(), #data, last_data) + return data +end + +function Editor:GetCurrentEditor() + return self:GetActiveTab():GetPanel() +end + +function Editor:Open(Line, data, forcenewtab) + if self:IsVisible() and not Line and not data then self:Close() end + hook.Run("WireMSLCDEditorOpen", self, Line, data, forcenewtab) + self:SetV(true) + self.C.SaE:SetText("Save and Exit") + if data then + if not forcenewtab then + for i = 1, self:GetNumTabs() do + if self:GetEditor(i).chosenfile == Line then + self:SetActiveTab(i) + self:SetData(data) + return + elseif self:GetEditor(i):GetValue() == data then + self:SetActiveTab(i) + return + end + end + end + + local tab + if self.NewTabOnOpen:GetBool() or forcenewtab then + tab = self:CreateTab("Download").Tab + else + tab = self:GetActiveTab() + end + self:SetActiveTab(tab) + + self:ChosenFile() + self:SetData(data) + + self:UpdateActiveTabTitle() + + if Line then self:SubTitle("Editing: " .. Line) end + return + end + if Line then self:LoadFile(Line, forcenewtab) return end +end + +function Editor:SaveFile(Line, close, SaveAs) + self:ExtractName() + + if close and self.chip then + self:Close() + return + end + if not Line or SaveAs or Line == self.Location .. "/" .. ".txt" then + local str + if self.C.Browser.File then + str = self.C.Browser.File.FileDir -- Get FileDir + if str and str ~= "" then -- Check if not nil + + -- Remove "expression2/" or "cpuchip/" etc + local n, _ = str:find("/", 1, true) + str = str:sub(n + 1, -1) + + if str and str ~= "" then -- Check if not nil + if str:Right(4) == ".txt" then -- If it's a file + str = string.GetPathFromFilename(str):Left(-2) -- Get the file path instead + if not str or str == "" then + str = nil + end + end + else + str = nil + end + else + str = nil + end + end + Derma_StringRequestNoBlur("Save to New File", "", (str ~= nil and str .. "/" or "") .. self.savefilefn, + function(strTextOut) + strTextOut = string.gsub(strTextOut, ".", invalid_filename_chars):lower() + self:SaveFile(self.Location .. "/" .. strTextOut .. ".txt", close) + end) + return + end + + file.CreateDir(string.GetPathFromFilename(Line)) + file.Write(Line, self:GetData()) + + surface.PlaySound("ambient/water/drip3.wav") + + if not self.chip then self:ChosenFile(Line) end + + self:UpdateActiveTabTitle() + + if close then + GAMEMODE:AddNotify("MSLCD saved as " .. Line .. ".", NOTIFY_GENERIC, 7) + self:Close() + end +end + +function Editor:LoadFile(Line, forcenewtab) + if not Line or file.IsDir(Line, "DATA") then return end + + local f = file.Open(Line, "r", "DATA") + if not f then + ErrorNoHalt("Erroring opening file: " .. Line) + else + local str = f:Read(f:Size()) or "" + f:Close() + self:AutoSave() + if not forcenewtab then + for i = 1, self:GetNumTabs() do + if self:GetEditor(i).chosenfile == Line then + self:SetActiveTab(i) + if forcenewtab ~= nil then self:SetData(str) end + return + elseif self:GetEditor(i):GetData() == str then + self:SetActiveTab(i) + return + end + end + end + + local tab + if self.NewTabOnOpen:GetBool() or forcenewtab then + tab = self:CreateTab(Line).Tab + else + tab = self:GetActiveTab() + end + self:SetActiveTab(tab) + self:ChosenFile(Line) + self:SetData(str) + self:UpdateActiveTabTitle() + end +end + +function Editor:Close() + timer.Stop("mslcdautosave") + self:AutoSave() + + self:ExtractName() + self:SetV(false) + self.chip = false + + self:SaveEditorSettings() + + hook.Run("WireMSLCDEditorClose", self) +end + + +function Editor:Setup(nTitle, nLocation) + self.Title = nTitle + self.Location = nLocation + self.C.Browser:Setup(nLocation) + + self:NewChip(true) -- Opens initial tab, in case OpenOldTabs is disabled or fails. + + if wire_mslcd_editor_openoldtabs:GetBool() then + self:OpenOldTabs() + end + + self:InvalidateLayout() +end + +vgui.Register("MSLCDEditorFrame", Editor, "DFrame") +if SERVER then MsgC(Color(0, 100, 255), "MSLCD Editor loaded!\n") end \ No newline at end of file diff --git a/lua/wire/client/wire_expression2_browser.lua b/lua/wire/client/wire_expression2_browser.lua index 3b355fc31d..e7d9391d72 100644 --- a/lua/wire/client/wire_expression2_browser.lua +++ b/lua/wire/client/wire_expression2_browser.lua @@ -36,6 +36,7 @@ function PANEL:Search( str, foldername, fullpath, parentfullpath, first_recursio if string_find( string_lower( files[i] ), str, 1, true ) ~= nil then local filenode = node:AddNode( files[i], "icon16/page_white.png" ) filenode:SetFileName( fullpath .. "/" .. files[i] ) + filenode:SetDraggableName( fullpath .. "/" .. files[i] ) myresults = myresults + 1 end diff --git a/lua/wire/gpulib.lua b/lua/wire/gpulib.lua index 62d625cf86..1b44e1d2e5 100644 --- a/lua/wire/gpulib.lua +++ b/lua/wire/gpulib.lua @@ -154,17 +154,23 @@ if CLIENT then WireGPU_matScreen = CreateMaterial("sprites/GPURT","UnlitGeneric",{ ["$vertexcolor"] = 1, ["$vertexalpha"] = 1, - ["$translucent"] = 1, + ["$translucent"] = 1, ["$ignorez"] = 1, ["$nolod"] = 1, }) WireGPU_matBuffer = CreateMaterial("sprites/GPUBUF","UnlitGeneric",{ ["$vertexcolor"] = 1, ["$vertexalpha"] = 1, - ["$translucent"] = 1, + ["$translucent"] = 1, ["$ignorez"] = 1, ["$nolod"] = 1, }) + WireGPU_matSegment = CreateMaterial("sprites/SEGRT","UnlitGeneric",{ + ["$vertexcolor"] = 1, + ["$vertexalpha"] = 1, + ["$translucent"] = 1, + ["$nolod"] = 1, + }) function GPU:Initialize(no_rendertarget) diff --git a/lua/wire/stools/multisegmentlcd.lua b/lua/wire/stools/multisegmentlcd.lua new file mode 100644 index 0000000000..9146dd6cf5 --- /dev/null +++ b/lua/wire/stools/multisegmentlcd.lua @@ -0,0 +1,1217 @@ +WireToolSetup.setCategory( "Visuals/Screens" ) +WireToolSetup.open( "multisegmentlcd", "Multi-segment LCD", "gmod_wire_multisegmentlcd", nil, "Multi-segment LCDs" ) + +OFFSET = -3 +ALIGN = -2 +GROUP = -1 +UNION = 0 +SEGMENT = 1 +TEXT = 2 +MATRIX = 3 +SegmentTypeNames = { +[GROUP] = "Group", +[UNION] = "Union", +[SEGMENT] = "Segment", +[TEXT] = "Text", +[MATRIX] = "Matrix", +[ALIGN] = "Align", +[OFFSET] = "Offset", +} + +WireLib.SegmentLCD_Tree = { + Type=GROUP, + X=0, + Y=0, + Children= + { + + } + } + + +if CLIENT then + language.Add( "tool.wire_multisegmentlcd.name", "Multi-segment LCD Tool (Wire)" ) + language.Add( "tool.wire_multisegmentlcd.desc", "Spawns a Multi-segment LCD, which can be used to display numbers and miscellaneous graphics" ) + language.Add( "tool.wire_multisegmentlcd.interactive", "Interactive (if available):" ) + language.Add( "tool.wire_multisegmentlcd.resw", "Canvas width:" ) + language.Add( "tool.wire_multisegmentlcd.resh", "Canvas height:" ) + language.Add( "tool.wire_multisegmentlcd.xormask", "Xor segment order mask:" ) + language.Add( "tool.wire_multisegmentlcd.fgcolor", "Segment color:" ) + language.Add( "tool.wire_multisegmentlcd.bgcolor", "Background color:" ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } + + WireToolSetup.setToolMenuIcon("icon16/application_xp_terminal.png") + + + + net.Receive("wire_multisegmentlcd_tool_upload_request", function(len, ply) + local ent = net.ReadUInt(16) + if MSLCD_Editor == nil then + MSLCD_Editor = vgui.Create("MSLCDEditorFrame") + MSLCD_Editor:Setup("MSLCD Editor", "multisegmentlcd") + end + local serialized = util.Compress(MSLCD_Editor:GetData()) + if #serialized > 65535 then + return + end + net.Start("wire_multisegmentlcd_tool_upload") + net.WriteUInt(ent,16) + net.WriteUInt(#serialized,16) + net.WriteData(serialized) + net.SendToServer() + --ent.Tree = table.Copy(WireLib.SegmentLCD_Tree) + end) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if SERVER then + + function TOOL:GetConVars() + return self:GetClientNumber("interactive") == 1, math.Clamp(self:GetClientNumber("resw"),0,1024), math.Clamp(self:GetClientNumber("resh"),0,1024), + math.Clamp(self:GetClientNumber("bgred"), 0, 255), + math.Clamp(self:GetClientNumber("bggreen"), 0, 255), + math.Clamp(self:GetClientNumber("bgblue"), 0, 255), + math.Clamp(self:GetClientNumber("bgalpha"), 0, 255), + math.Clamp(self:GetClientNumber("fgred"), 0, 255), + math.Clamp(self:GetClientNumber("fggreen"), 0, 255), + math.Clamp(self:GetClientNumber("fgblue"), 0, 255), + math.Clamp(self:GetClientNumber("fgalpha"), 0, 255), + math.Clamp(self:GetClientNumber("xormask"), 0, 255) + end + + util.AddNetworkString("wire_multisegmentlcd_tool_upload_request") + util.AddNetworkString("wire_multisegmentlcd_tool_upload") + + + function TOOL:LeftClick_Update( trace ) + net.Start("wire_multisegmentlcd_tool_upload_request") + net.WriteUInt(trace.Entity:EntIndex(),16) + net.Send(self:GetOwner()) + trace.Entity:Setup(self:GetConVars()) + end + + net.Receive("wire_multisegmentlcd_tool_upload", function(len, ply) + local ent = ents.GetByIndex(net.ReadUInt(16)) + local sz = net.ReadUInt(16) + local data = net.ReadData(sz) + local ok, data = pcall(util.Decompress, data) + if not ok then return end + + ok, data = pcall(WireLib.von.deserialize, data) + ent.Tree = data.SegmentTree + ent:Retransmit() + end) + + function TOOL:MakeEnt( ply, model, Ang, trace ) + local ent = WireLib.MakeWireEnt( ply, {Class = self.WireClass, Pos=trace.HitPos, Angle=Ang, Model=model}, self:GetConVars() ) + if ent and ent.RestoreNetworkVars then ent:RestoreNetworkVars(self:GetDataTables()) end + net.Start("wire_multisegmentlcd_tool_upload_request") + net.WriteUInt(ent:EntIndex(),16) + net.Send(ply) + return ent + end +end + +TOOL.ClientConVar = { + model = "models/props_lab/monitor01b.mdl", + createflat = 0, + interactive = 1, + resw = 1024, + resh = 1024, + bgred = 148, + bggreen = 178, + bgblue = 15, + bgalpha = 255, + fgred = 45, + fggreen = 91, + fgblue = 45, + fgalpha = 255, + xormask = 0, + snapinc = 0 +} + + +function BuildNode(v,node,group) + local new = nil + if v.Type == GROUP then + new = node:AddNode( v.Text or "Group", "icon16/text_list_numbers.png" ) + BuildNodes(new,v) + elseif v.Type == UNION then + new = node:AddNode( v.Text or "Union", "icon16/text_list_bullets.png" ) + BuildNodes(new,v) + elseif v.Type == TEXT then + new = node:AddNode( v.Text or "Text", "icon16/bullet_yellow.png" ) + elseif v.Type == MATRIX then + new = node:AddNode( v.Text or "Matrix", "icon16/bullet_red.png" ) + elseif v.Type == ALIGN then + new = node:AddNode( v.Text or "Text", "icon16/bullet_pink.png" ) + elseif v.Type == OFFSET then + new = node:AddNode( v.Text or "Text", "icon16/bullet_white.png" ) + else + new = node:AddNode( v.Text or "Segment", "icon16/bullet_green.png" ) + end + new.group = v + new.parentgroup = group +end + +function BuildNodes(node,group) + for i,v in ipairs(group.Children) do + BuildNode(v,node,group) + end +end + +local invalid_filename_chars = { + ["*"] = "", + ["?"] = "", + [">"] = "", + ["<"] = "", + ["|"] = "", + ["\\"] = "", + ['"'] = "", + [" "] = "_", +} + +if CLIENT then + function TOOL:RightClick( trace ) + if not IsValid( trace.Entity ) then return end + local ent = trace.Entity + if ent:GetClass() ~= "gmod_wire_multisegmentlcd" then return end + MSLCD_Editor:SetDataFromEnt(ent.Tree) + end + ------------------------------------------------------------------------------ + -- Open Multi-segment editor + ------------------------------------------------------------------------------ + function MSLCD_OpenEditor() + if MSLCD_Editor == nil then + MSLCD_Editor = vgui.Create("MSLCDEditorFrame") + MSLCD_Editor:Setup("MSLCD Editor", "multisegmentlcd") + end + MSLCD_Editor:Open() + end + + function MSLCD_NewEditor() + MSLCD_Editor = vgui.Create("MSLCDEditorFrame") + MSLCD_Editor:Setup("MSLCD Editor", "multisegmentlcd") + MSLCD_Editor:Open() + end + + ------------------------------------------------------------------------------ + -- Build tool control panel + ------------------------------------------------------------------------------ + function TOOL.BuildCPanel(panel) + local FileBrowser = vgui.Create("wire_expression2_browser" , panel) + panel:AddPanel(FileBrowser) + FileBrowser:Setup("multisegmentlcd") + FileBrowser:SetSize(235,400) + function FileBrowser:OnFileOpen(filepath, newtab) + if MSLCD_Editor == nil then + MSLCD_Editor = vgui.Create("MSLCDEditorFrame") + MSLCD_Editor:Setup("MSLCD Editor", "multisegmentlcd") + end + MSLCD_Editor:Open(filepath, nil, newtab) + end + + + ---------------------------------------------------------------------------- + local New = vgui.Create("DButton" , panel) + panel:AddPanel(New) + New:SetText("New file") + New.DoClick = function(button) + MSLCD_OpenEditor() + MSLCD_Editor:AutoSave() + MSLCD_Editor:NewChip(false) + end + panel:AddControl("Label", {Text = ""}) + + ---------------------------------------------------------------------------- + local OpenEditor = vgui.Create("DButton", panel) + panel:AddPanel(OpenEditor) + OpenEditor:SetText("Open Editor") + OpenEditor.DoClick = MSLCD_OpenEditor + + + local NewEditor = vgui.Create("DButton", panel) + panel:AddPanel(NewEditor) + NewEditor:SetText("New Editor") + NewEditor.DoClick = MSLCD_NewEditor + + ---------------------------------------------------------------------------- + panel:AddControl("Label", {Text = ""}) + panel:AddControl("Label", {Text = "MS-LCD settings:"}) + + + ---------------------------------------------------------------------------- + WireDermaExts.ModelSelect(panel, "wire_multisegmentlcd_model", list.Get("WireScreenModels"), 5) + panel:AddControl("Label", {Text = ""}) + end +end +--[[ +function TOOL.BuildCPanel(panel) + WireDermaExts.ModelSelect(panel, "wire_multisegmentlcd_model", list.Get( "WireScreenModels" ), 5) + panel:CheckBox("#tool.wire_multisegmentlcd.interactive", "wire_multisegmentlcd_interactive") + panel:CheckBox("#Create Flat to Surface", "wire_multisegmentlcd_createflat") + panel:TextEntry("#tool.wire_multisegmentlcd.resw", "wire_multisegmentlcd_resw") + panel:TextEntry("#tool.wire_multisegmentlcd.resh", "wire_multisegmentlcd_resh") + panel:TextEntry("#tool.wire_multisegmentlcd.xormask", "wire_multisegmentlcd_xormask") + + + net.Receive("MSLCD_OpenEditor", MSLCD_OpenEditor) + + PreviewPanel = vgui.Create("DPanel", panel) + panel:AddPanel(PreviewPanel) + PreviewPanel:SetHeight(256) + PreviewPanel:Dock(TOP) + PreviewPanel.Paint = DrawSegmentLCDPreview + + TreeDataHolder = vgui.Create("DPanel", panel) + panel:AddPanel(TreeDataHolder) + TreeDataHolder:DockMargin(0, 0, 0, 0) + TreeDataHolder:Dock(TOP) + TreeDataHolder:SetHeight(480) + DisplayData = vgui.Create("DTree", TreeDataHolder) + DisplayData:Dock(FILL) + DisplayData:SetClickOnDragHover( true ) + DisplayData:DockMargin(0, 0, 0, 0) + --DisplayData.RootNode:AddNode( "Root", "icon16/monitor.png" ) + DisplayData.RootNode.group = WireLib.SegmentLCD_Tree + BuildNodes(DisplayData.RootNode,WireLib.SegmentLCD_Tree) + ButtonsHolder = TreeDataHolder:Add( "DPanel" ) + ButtonsHolder:Dock(TOP) + ButtonsHolder:DockMargin(0, 0, 0, 0) + ButtonsHolder.buttons = {} + ButtonsHolder:SetHeight(72) + + AddSegment = ButtonsHolder:Add( "DButton" ) + AddSegment:SetText( "Add Segment" ) + ButtonsHolder.buttons[1] = AddSegment + function AddSegmentI(node) + if node == nil then + node = DisplayData.RootNode + end + local group = node.group + local children = nil + if group ~= nil then + children = group.Children + end + + if children == nil then + node = DisplayData.RootNode + children = WireLib.SegmentLCD_Tree.Children + group = WireLib.SegmentLCD_Tree + end + local newgroup = {Type=SEGMENT, X=30,Y=0,W=10,H=20} + children[#children+1] = newgroup + local new = node:AddNode( "Segment", "icon16/bullet_green.png" ) + new.group = newgroup + new.parentgroup = group + end + function AddSegment:DoClick() + local node = DisplayData:GetSelectedItem() + AddSegmentI(node) + end + + AddText = ButtonsHolder:Add( "DButton" ) + AddText:SetText( "Add Text" ) + ButtonsHolder.buttons[2] = AddText + function AddTextI(node) + if node == nil then + node = DisplayData.RootNode + end + local group = node.group + local children = nil + if group ~= nil then + children = group.Children + end + + if children == nil then + node = DisplayData.RootNode + children = WireLib.SegmentLCD_Tree.Children + group = WireLib.SegmentLCD_Tree + end + local newgroup = {Type=TEXT, X=30, Y=0, Text="Text"} + children[#children+1] = newgroup + local new = node:AddNode( "Text", "icon16/bullet_yellow.png" ) + new.group = newgroup + new.parentgroup = group + end + function AddText:DoClick() + local node = DisplayData:GetSelectedItem() + AddTextI(node) + end + + AddMatrix = ButtonsHolder:Add( "DButton" ) + AddMatrix:SetText( "Add Matrix" ) + ButtonsHolder.buttons[3] = AddMatrix + function AddMatrixI(node) + if node == nil then + node = DisplayData.RootNode + end + local group = node.group + local children = nil + if group ~= nil then + children = group.Children + end + + if children == nil then + node = DisplayData.RootNode + children = WireLib.SegmentLCD_Tree.Children + group = WireLib.SegmentLCD_Tree + end + local newgroup = {Type=MATRIX, X=0, Y=0, W=6, H=8, ScaleW=5, ScaleH=5, OffsetX=6, OffsetY=6} + children[#children+1] = newgroup + local new = node:AddNode( "Matrix", "icon16/bullet_red.png" ) + new.group = newgroup + new.parentgroup = group + end + function AddMatrix:DoClick() + local node = DisplayData:GetSelectedItem() + AddMatrixI(node) + end + + AddGroup = ButtonsHolder:Add( "DButton" ) + AddGroup:SetText( "Add Group" ) + ButtonsHolder.buttons[4] = AddGroup + function AddGroupI(node) + + if node == nil then + node = DisplayData.RootNode + end + local group = node.group + local children = nil + if group ~= nil then + children = group.Children + end + if children == nil then + node = DisplayData.RootNode + children = WireLib.SegmentLCD_Tree.Children + group = WireLib.SegmentLCD_Tree + end + local newgroup = {Type=GROUP,Children={},X=0,Y=0,HasColor=false,R=255,G=255,B=255} + children[#children+1] = newgroup + local new = node:AddNode( "Group", "icon16/text_list_numbers.png" ) + new:SetExpanded(true); + new.group = newgroup + new.parentgroup = group + end + function AddGroup:DoClick() + local node = DisplayData:GetSelectedItem() + AddGroupI(node) + end + + AddUnion = ButtonsHolder:Add( "DButton" ) + AddUnion:SetText( "Add Union" ) + ButtonsHolder.buttons[5] = AddUnion + function AddUnionI(node) + if node == nil then + node = DisplayData.RootNode + end + local group = node.group + local children = nil + if group ~= nil then + children = group.Children + end + if children == nil then + node = DisplayData.RootNode + children = WireLib.SegmentLCD_Tree.Children + group = WireLib.SegmentLCD_Tree + end + local newgroup = {Type=UNION,Children={},X=0,Y=0,HasColor=false,R=255,G=255,B=255} + children[#children+1] = newgroup + local new = node:AddNode( "Union", "icon16/text_list_bullets.png" ) + new:SetExpanded(true); + new.group = newgroup + new.parentgroup = group + end + function AddUnion:DoClick() + local node = DisplayData:GetSelectedItem() + AddUnionI(node) + end + + Remove = ButtonsHolder:Add( "DButton" ) + Remove:SetText( "Remove" ) + ButtonsHolder.buttons[6] = Remove + function RemoveI(node) + if node == nil then + return + end + local parentgroup = node.parentgroup + if parentgroup == nil then + parentgroup = WireLib.SegmentLCD_Tree + end + for i,v in pairs(parentgroup.Children) do + if v == node.group then + table.remove(parentgroup.Children,i) + node:Remove() + return + end + end + end + function Remove:DoClick() + local node = DisplayData:GetSelectedItem() + RemoveI(node) + end + ButtonsHolder.textboxes = {} + WangX = ButtonsHolder:Add( "DNumberWang" ) + WangX:SetMin(-1024) + WangX:SetMax(1024) + function WangX:OnValueChanged(value) + local node = DisplayData:GetSelectedItem() + if node == nil or node.group == nil then + return + end + node.group.X = value + end + ButtonsHolder.textboxes[1] = WangX + WangY = ButtonsHolder:Add( "DNumberWang" ) + WangY:SetMin(-1024) + WangY:SetMax(1024) + function WangY:OnValueChanged(value) + local node = DisplayData:GetSelectedItem() + if node == nil or node.group == nil then + return + end + node.group.Y = value + end + ButtonsHolder.textboxes[2] = WangY + WangW = ButtonsHolder:Add( "DNumberWang" ) + WangW:SetMax(1024) + function WangW:OnValueChanged(value) + local node = DisplayData:GetSelectedItem() + if node == nil or node.group == nil then + return + end + node.group.W = value + end + ButtonsHolder.textboxes[3] = WangW + WangH = ButtonsHolder:Add( "DNumberWang" ) + WangH:SetMax(1024) + function WangH:OnValueChanged(value) + local node = DisplayData:GetSelectedItem() + if node == nil or node.group == nil then + return + end + node.group.H = value + end + ButtonsHolder.textboxes[4] = WangH + + TextSetter = ButtonsHolder:Add( "DTextEntry" ) + function TextSetter:OnChange() + local value = TextSetter:GetText() + local node = DisplayData:GetSelectedItem() + if node == nil or node.group == nil then + return + end + if value == "" then + node:SetText(SegmentTypeNames[node.group.Type]) + node.group.Text = nil + else + node:SetText(value) + node.group.Text = value + end + + end + + WangScaleW = ButtonsHolder:Add( "DNumberWang" ) + WangScaleW:SetMax(1024) + function WangScaleW:OnValueChanged(value) + local node = DisplayData:GetSelectedItem() + if node == nil or node.group == nil then + return + end + node.group.ScaleW = value + end + + WangScaleH = ButtonsHolder:Add( "DNumberWang" ) + WangScaleH:SetMax(1024) + function WangScaleH:OnValueChanged(value) + local node = DisplayData:GetSelectedItem() + if node == nil or node.group == nil then + return + end + node.group.ScaleH = value + end + + WangOffsetX = ButtonsHolder:Add( "DNumberWang" ) + WangOffsetX:SetMax(1024) + function WangOffsetX:OnValueChanged(value) + local node = DisplayData:GetSelectedItem() + if node == nil or node.group == nil then + return + end + node.group.OffsetX = value + end + + WangOffsetY = ButtonsHolder:Add( "DNumberWang" ) + WangOffsetY:SetMax(1024) + function WangOffsetY:OnValueChanged(value) + local node = DisplayData:GetSelectedItem() + if node == nil or node.group == nil then + return + end + node.group.OffsetY = value + end + + CheckHasColor = ButtonsHolder:Add( "DCheckBox" ) + function CheckHasColor:OnChange(value) + local node = DisplayData:GetSelectedItem() + if node == nil or node.group == nil then + return + end + node.group.HasColor = value + end + CheckLabel = ButtonsHolder:Add( "DLabel" ) + CheckLabel:SetText("Has color") + CheckLabel:SetTextColor(Color(0,0,0,255)) + + WangColorR = ButtonsHolder:Add( "DNumberWang" ) + WangColorR:SetMax(255) + function WangColorR:OnValueChanged(value) + local node = DisplayData:GetSelectedItem() + if node == nil or node.group == nil then + return + end + node.group.R = value + end + + WangColorG = ButtonsHolder:Add( "DNumberWang" ) + WangColorG:SetMax(255) + function WangColorG:OnValueChanged(value) + local node = DisplayData:GetSelectedItem() + if node == nil or node.group == nil then + return + end + node.group.G = value + end + + WangColorB = ButtonsHolder:Add( "DNumberWang" ) + WangColorB:SetMax(255) + function WangColorB:OnValueChanged(value) + local node = DisplayData:GetSelectedItem() + if node == nil or node.group == nil then + return + end + node.group.B = value + end + + WangColorA = ButtonsHolder:Add( "DNumberWang" ) + WangColorA:SetMax(255) + function WangColorA:OnValueChanged(value) + local node = DisplayData:GetSelectedItem() + if node == nil or node.group == nil then + return + end + node.group.A = value + end + + WangRotation = ButtonsHolder:Add( "DNumberWang" ) + WangRotation:SetMax(360) + WangRotation:SetMin(-360) + function WangRotation:OnValueChanged(value) + local node = DisplayData:GetSelectedItem() + if node == nil or node.group == nil then + return + end + node.group.Rotation = value + end + + WangSkewX = ButtonsHolder:Add( "DNumberWang" ) + WangSkewX:SetMin(-4096) + WangSkewX:SetMax(4096) + function WangSkewX:OnValueChanged(value) + local node = DisplayData:GetSelectedItem() + if node == nil or node.group == nil then + return + end + node.group.SkewX = value + end + + WangSkewY = ButtonsHolder:Add( "DNumberWang" ) + WangSkewY:SetMin(-4096) + WangSkewY:SetMax(4096) + function WangSkewY:OnValueChanged(value) + local node = DisplayData:GetSelectedItem() + if node == nil or node.group == nil then + return + end + node.group.SkewY = value + end + + WangBevel = ButtonsHolder:Add( "DNumberWang" ) + WangBevel:SetMin(-1024) + WangBevel:SetMax(1024) + function WangBevel:OnValueChanged(value) + local node = DisplayData:GetSelectedItem() + if node == nil or node.group == nil then + return + end + node.group.Bevel = value + end + + WangBevelSkew = ButtonsHolder:Add( "DNumberWang" ) + WangBevelSkew:SetMax(1024) + WangBevelSkew:SetMin(-1024) + function WangBevelSkew:OnValueChanged(value) + local node = DisplayData:GetSelectedItem() + if node == nil or node.group == nil then + return + end + node.group.BevelSkew = value + end + + WangSize = ButtonsHolder:Add( "DNumberWang" ) + WangSize:SetMax(1024) + WangSize:SetMin(1) + function WangSize:OnValueChanged(value) + local node = DisplayData:GetSelectedItem() + if node == nil or node.group == nil then + return + end + node.group.Size = value + end + + function AddOffsetI(node) + if node == nil then + node = DisplayData.RootNode + end + local group = node.group + local children = nil + if group ~= nil then + children = group.Children + end + + if children == nil then + node = DisplayData.RootNode + children = WireLib.SegmentLCD_Tree.Children + group = WireLib.SegmentLCD_Tree + end + local newgroup = {Type=OFFSET, Size=1} + children[#children+1] = newgroup + local new = node:AddNode( "Offset", "icon16/bullet_white.png" ) + new.group = newgroup + new.parentgroup = group + end + + function AddAlignI(node) + if node == nil then + node = DisplayData.RootNode + end + local group = node.group + local children = nil + if group ~= nil then + children = group.Children + end + + if children == nil then + node = DisplayData.RootNode + children = WireLib.SegmentLCD_Tree.Children + group = WireLib.SegmentLCD_Tree + end + local newgroup = {Type=ALIGN, Size=8} + children[#children+1] = newgroup + local new = node:AddNode( "Align", "icon16/bullet_pink.png" ) + new.group = newgroup + new.parentgroup = group + end + + function ButtonsHolder:PerformLayout(w, h) + local roww = w/8 + local rowh = h/4 + for i,v in ipairs(self.buttons) do + v:SetPos((i-1)*w/#self.buttons,0) + v:SetSize(w/#self.buttons,rowh) + end + for i,v in ipairs(self.textboxes) do + v:SetPos((i-1)*w/#self.textboxes,rowh) + v:SetSize(w/#self.textboxes,rowh) + end + TextSetter:SetPos(0,rowh*3) + TextSetter:SetSize(w,rowh) + WangScaleW:SetPos(0,rowh*2) + WangScaleW:SetSize(roww,rowh) + WangScaleH:SetPos(roww,rowh*2) + WangScaleH:SetSize(roww,rowh) + WangOffsetX:SetPos(roww*2,rowh*2) + WangOffsetX:SetSize(roww,rowh) + WangOffsetY:SetPos(roww*3,rowh*2) + WangOffsetY:SetSize(roww,rowh) + + WangSize:SetPos(0,rowh) + WangSize:SetSize(w,rowh) + + CheckHasColor:SetPos(1,rowh*2+3) + CheckLabel:SetPos(18,rowh*2) + CheckLabel:SetSize(roww-18,rowh) + WangColorR:SetPos(roww,rowh*2) + WangColorR:SetSize(roww,rowh) + WangColorG:SetPos(roww*2,rowh*2) + WangColorG:SetSize(roww,rowh) + WangColorB:SetPos(roww*3,rowh*2) + WangColorB:SetSize(roww,rowh) + WangColorA:SetPos(roww*4,rowh*2) + WangColorA:SetSize(roww,rowh) + + WangRotation:SetPos(roww*5,rowh*2) + WangRotation:SetSize(roww,rowh) + WangSkewX:SetPos(roww*6,rowh*2) + WangSkewX:SetSize(roww,rowh) + WangSkewY:SetPos(roww*7,rowh*2) + WangSkewY:SetSize(roww,rowh) + + WangBevel:SetPos(0,rowh*2) + WangBevel:SetSize(roww,rowh) + WangBevelSkew:SetPos(roww,rowh*2) + WangBevelSkew:SetSize(roww,rowh) + end + WangW:SetVisible(false) + WangH:SetVisible(false) + WangScaleW:SetVisible(false) + WangScaleH:SetVisible(false) + WangOffsetX:SetVisible(false) + WangOffsetY:SetVisible(false) + WangColorR:SetVisible(false) + WangColorG:SetVisible(false) + WangColorB:SetVisible(false) + WangColorA:SetVisible(false) + CheckHasColor:SetVisible(false) + CheckLabel:SetVisible(false) + WangRotation:SetVisible(false) + WangSkewX:SetVisible(false) + WangSkewY:SetVisible(false) + WangBevel:SetVisible(false) + WangBevelSkew:SetVisible(false) + WangSize:SetVisible(false) + WangX:SetVisible(false) + WangY:SetVisible(false) + + function DisplayData:DoClick(node) + group = node.group + WangScaleW:SetVisible(false) + WangScaleH:SetVisible(false) + WangOffsetX:SetVisible(false) + WangOffsetY:SetVisible(false) + WangW:SetVisible(false) + WangH:SetVisible(false) + WangColorR:SetVisible(false) + WangColorG:SetVisible(false) + WangColorB:SetVisible(false) + WangColorA:SetVisible(false) + CheckHasColor:SetVisible(false) + CheckLabel:SetVisible(false) + WangRotation:SetVisible(false) + WangSkewX:SetVisible(false) + WangSkewY:SetVisible(false) + WangBevel:SetVisible(false) + WangBevelSkew:SetVisible(false) + WangSize:SetVisible(false) + WangX:SetVisible(false) + WangY:SetVisible(false) + if group.Type == SEGMENT then + WangW:SetValue(group.W) + WangH:SetValue(group.H) + WangW:SetVisible(true) + WangH:SetVisible(true) + WangRotation:SetVisible(true) + WangSkewX:SetVisible(true) + WangSkewY:SetVisible(true) + WangBevel:SetVisible(true) + WangBevelSkew:SetVisible(true) + WangX:SetVisible(true) + WangY:SetVisible(true) + + WangRotation:SetValue(group.Rotation or 0) + WangSkewX:SetValue(group.SkewX or 0) + WangSkewY:SetValue(group.SkewY or 0) + WangBevel:SetValue(group.Bevel or 0) + WangBevelSkew:SetValue(group.BevelSkew or 0) + WangX:SetValue(group.X) + WangY:SetValue(group.Y) + elseif group.Type == MATRIX then + WangW:SetValue(group.W) + WangH:SetValue(group.H) + WangScaleW:SetVisible(true) + WangScaleH:SetVisible(true) + WangOffsetX:SetVisible(true) + WangOffsetY:SetVisible(true) + WangW:SetVisible(true) + WangH:SetVisible(true) + WangX:SetVisible(true) + WangY:SetVisible(true) + + WangScaleW:SetValue(group.ScaleW) + WangScaleH:SetValue(group.ScaleH) + WangOffsetX:SetValue(group.OffsetX) + WangOffsetY:SetValue(group.OffsetY) + WangX:SetValue(group.X) + WangY:SetValue(group.Y) + elseif group.Type == GROUP or group.Type == UNION then + WangColorR:SetVisible(true) + WangColorG:SetVisible(true) + WangColorB:SetVisible(true) + WangColorA:SetVisible(true) + CheckHasColor:SetVisible(true) + CheckLabel:SetVisible(true) + WangX:SetVisible(true) + WangY:SetVisible(true) + + WangColorR:SetValue(group.R) + WangColorG:SetValue(group.G) + WangColorB:SetValue(group.B) + WangColorA:SetValue(group.A) + CheckHasColor:SetValue(group.HasColor) + WangRotation:SetVisible(true) + WangSkewX:SetVisible(true) + WangSkewY:SetVisible(true) + WangRotation:SetValue(group.Rotation or 0) + WangSkewX:SetValue(group.SkewX or 0) + WangSkewY:SetValue(group.SkewY or 0) + WangBevel:SetValue(group.Bevel or 0) + WangBevelSkew:SetValue(group.BevelSkew or 0) + WangX:SetValue(group.X) + WangY:SetValue(group.Y) + elseif group.Type == ALIGN or group.Type == OFFSET then + WangSize:SetVisible(true) + WangSize:SetValue(group.Size) + end + + TextSetter:SetValue(group.Text or "") + return true + end + + function DisplayData:DoRightClick(node) + local Menu = DermaMenu() + Menu:AddOption( "Ungroup" ) + Menu:AddOption( "Copy" ) + Menu:AddOption( "Paste" ) + local InsertM, MMOption = Menu:AddSubMenu( "Insert" ) + InsertM:AddOption( "Union", function() AddUnionI(node) end ) + InsertM:AddOption( "Group", function() AddGroupI(node) end ) + InsertM:AddOption( "Segment", function() AddSegmentI(node) end ) + InsertM:AddOption( "Matrix", function() AddMatrixI(node) end ) + InsertM:AddOption( "Text", function() AddTextI(node) end ) + InsertM:AddOption( "Align", function() AddAlignI(node) end ) + InsertM:AddOption( "Offset", function() AddOffsetI(node) end ) + Menu:AddSpacer() + Menu:AddOption( "Remove" ) + Menu:Open() + function Menu:OptionSelected(option, optionText) + if optionText == "Ungroup" then + if node.group.Children then + local parentgroup = node.parentgroup + if parentgroup == nil then + parentgroup = WireLib.SegmentLCD_Tree + end + local indexofnode = 0 + for i,v in pairs(parentgroup.Children) do + if v == node.group then + table.remove(parentgroup.Children,i) + indexofnode = i + break + end + end + + + local children = node:GetChildNodes() + local parent = node:GetParent() + local parentnode = node:GetParentNode() + + local root = node:GetRoot() + for i=1,#children do + children[i]:SetParent(parent) + children[i]:SetParentNode(parentnode) + children[i]:SetRoot(root) + --parentnode:Insert(children[i],insertbefore,true) + table.insert(parentgroup.Children,indexofnode+i-1,node.group.Children[i]) + end + + local parentchildren = parentnode:GetChildNodes() + for j=1,#parentgroup.Children do + for i=1,#parentchildren do + if parentgroup.Children[j] == parentchildren[i].group then + parentchildren[i]:SetParent(node) -- required for it to reparent + parentchildren[i]:SetParentNode(parentnode) + parentchildren[i]:SetParent(parent) + parentchildren[i]:SetRoot(root) + break + end + end + end + + + node:Remove() + end + elseif optionText == "Copy" then + SegmentLCD_Clipboard = table.Copy(node.group) + elseif optionText == "Paste" then + if node.group.Children then + local newgroup = table.Copy(SegmentLCD_Clipboard) + node.group.Children[#node.group.Children+1] = newgroup + BuildNode(newgroup,node,node.group) + else + local newgroup = table.Copy(SegmentLCD_Clipboard) + WireLib.SegmentLCD_Tree.Children[#WireLib.SegmentLCD_Tree.Children+1] = newgroup + BuildNode(newgroup,DisplayData.RootNode,WireLib.SegmentLCD_Tree) + end + elseif optionText == "Remove" then + RemoveI(node) + end + end + return true + end + + local FileBrowser = vgui.Create("wire_expression2_browser", panel) + panel:AddPanel(FileBrowser) + FileBrowser:Setup("multisegmentlcd") + FileBrowser:SetSize(w, 320) + FileBrowser:DockMargin(0, 0, 0, 0) + FileBrowser:DockPadding(0, 0, 0, 0) + FileBrowser:Dock(TOP) + FileBrowser:RemoveRightClick("New File") + + for k, v in pairs(FileBrowser.foldermenu) do + if (v[1] == "New File..") then + FileBrowser.foldermenu[k] = nil + break + end + end + + function SaveFile(curloc,path) + file.CreateDir(curloc) + file.Write(path, util.TableToJSON(WireLib.SegmentLCD_Tree)) + FileBrowser:UpdateFolders() + end + + function FileBrowser:OnFileOpen(filepath, newtab) + local newgroup = util.JSONToTable(file.Read(filepath)) + local node = DisplayData:GetSelectedItem() + if node == nil then + node = DisplayData.RootNode + end + local group = node.group + local children = nil + if group ~= nil then + children = group.Children + end + if children == nil then + node = DisplayData.RootNode + children = WireLib.SegmentLCD_Tree.Children + group = WireLib.SegmentLCD_Tree + end + node.group.Children[#node.group.Children+1] = newgroup + BuildNode(newgroup,node,node.group) + end + + local Save = panel:Button("Save") + function Save:DoClick() + Derma_StringRequestNoBlur("Save to file", "", "", + function(strTextOut) + strTextOut = string.gsub(strTextOut, ".", invalid_filename_chars) + local curlocation = "multisegmentlcd" + if FileBrowser.File then + local newcurloc = FileBrowser.File:GetFolder() + curlocation = newcurloc or curlocation + end + local save_location = curlocation .. "/" .. strTextOut .. ".txt" + if file.Exists(save_location, "DATA") then + Derma_QueryNoBlur("The file '" .. strTextOut .. "' already exists. Do you want to overwrite it?", "File already exists", + "Yes", function() SaveFile(curlocation,save_location) end, + "No", function() end) + else + SaveFile(curlocation,save_location) + end + end) + end + + panel:AddControl("Color", { + Label = "#tool.wire_multisegmentlcd.bgcolor", + Red = "wire_multisegmentlcd_bgred", + Green = "wire_multisegmentlcd_bggreen", + Blue = "wire_multisegmentlcd_bgblue", + Alpha = "wire_multisegmentlcd_bgalpha", + ShowAlpha = "1", + ShowHSV = "1", + ShowRGB = "1", + Multiplier = "255" + }) + panel:AddControl("Color", { + Label = "#tool.wire_multisegmentlcd.fgcolor", + Red = "wire_multisegmentlcd_fgred", + Green = "wire_multisegmentlcd_fggreen", + Blue = "wire_multisegmentlcd_fgblue", + Alpha = "wire_multisegmentlcd_fgalpha", + ShowAlpha = "1", + ShowHSV = "1", + ShowRGB = "1", + Multiplier = "255" + }) +end + +function Transform(self,x,y) + return { + x=x*self.LocalXX+y*self.LocalXY+self.LocalX, + y=x*self.LocalYX+y*self.LocalYY+self.LocalY + } +end + +function TransformOffset(self,x,y) + return { + x*self.LocalXX+y*self.LocalXY, + x*self.LocalYX+y*self.LocalYY + } +end + + +function PushTransform(self,XX,XY,YX,YY) + self.TransformStack[#self.TransformStack + 1] = {self.LocalXX,self.LocalXY,self.LocalYX,self.LocalYY} + local oXX = self.LocalXX + local oXY = self.LocalXY + local oYX = self.LocalYX + local oYY = self.LocalYY + + local nXX = oXX*XX + oXY*YX + local nXY = oXY*YY + oXX*XY + local nYX = oYX*XX + oYY*YX + local nYY = oYY*YY + oYX*XY + + self.LocalXX = nXX + self.LocalXY = nXY + self.LocalYX = nYX + self.LocalYY = nYY +end + +function PopTransform(self) + self.LocalXX,self.LocalXY,self.LocalYX,self.LocalYY = unpack(self.TransformStack[#self.TransformStack]) + self.TransformStack[#self.TransformStack] = nil +end + +function DrawSegment(self,segment) + local transformedLocal = TransformOffset(self,segment.X or 0,segment.Y or 0) + self.LocalX = self.LocalX + transformedLocal[1] + self.LocalY = self.LocalY + transformedLocal[2] + local angle = math.rad(segment.Rotation or 0) + PushTransform(self,math.cos(angle), + math.sin(angle)-(segment.SkewX or 0), + -math.sin(angle)+(segment.SkewY or 0), + math.cos(angle)) + --self:Transform(,segment.H/2+(segment.H*(segment.BevelSkew or 0))), + local bevel = math.min(segment.H,segment.W)/2*(segment.Bevel or 0) + local rect = { + Transform(self,bevel,segment.H), + Transform(self,0,segment.H-bevel), + Transform(self,0,bevel), + Transform(self,bevel,0), + Transform(self,segment.W-bevel,0), + Transform(self,segment.W,bevel), + Transform(self,segment.W,segment.H-bevel), + Transform(self,segment.W-bevel,segment.H) + } + surface.DrawPoly(rect) + PopTransform(self) + --surface.DrawRect(self.LocalX,self.LocalY,segment.W,segment.H) + self.LocalX = self.LocalX - transformedLocal[1] + self.LocalY = self.LocalY - transformedLocal[2] +end + +function DrawText(self,text) + +end + +function DrawMatrix(self,matrix) + +end + +function DrawUnion(self,union) + for k,v in ipairs(union.Children) do + if v.Type == GROUP then + DrawGroup(self,v) + elseif v.Type == UNION then + DrawUnion(self,v) + elseif v.Type == SEGMENT then + DrawSegment(self,v) + elseif v.Type == TEXT then + DrawText(self,v) + elseif v.Type == MATRIX then + DrawMatrix(self,v) + end + end +end + +function DrawGroup(self,group) + local oCr = self.Cr + local oCg = self.Cg + local oCb = self.Cb + local oCa = self.Ca + if group.HasColor then + self.Cr = group.R or 255 + self.Cg = group.G or 255 + self.Cb = group.B or 255 + self.Ca = group.A or 255 + surface.SetDrawColor(self.Cr,self.Cg,self.Cb,self.Ca) + end + + local angle = math.rad(group.Rotation or 0) + local transformedLocal = TransformOffset(self,group.X or 0,group.Y or 0) + self.LocalX = self.LocalX + transformedLocal[1] + self.LocalY = self.LocalY + transformedLocal[2] + PushTransform(self,math.cos(angle), + math.sin(angle), + -math.sin(angle), + math.cos(angle)) + PushTransform(self,1, + -(group.SkewX or 0), + (group.SkewY or 0), + 1) + for k,v in ipairs(group.Children) do + if v.Type == GROUP then + DrawGroup(self,v) + elseif v.Type == UNION then + DrawUnion(self,v) + elseif v.Type == SEGMENT then + DrawSegment(self,v) + elseif v.Type == TEXT then + DrawText(self,v) + elseif v.Type == MATRIX then + DrawMatrix(self,v) + end + end + PopTransform(self) + self.LocalX = self.LocalX - transformedLocal[1] + self.LocalY = self.LocalY - transformedLocal[2] + self.Cr = oCr + self.Cg = oCg + self.Cb = oCb + self.Ca = oCa +end + +function DrawSegmentLCDPreview(self,width, height) + surface.SetDrawColor(0, 0, 0, 255) + draw.NoTexture() + surface.DrawRect(0, 0, width, height) + + resw = GetConVar( "wire_multisegmentlcd_resw" ):GetInt() + resh = GetConVar( "wire_multisegmentlcd_resh" ):GetInt() + + + local maxres = math.min(width/resw,height/resh) + + self.Cr = 255 + self.Cg = 255 + self.Cb = 255 + self.LocalXX = maxres + self.LocalXY = 0 + self.LocalYX = 0 + self.LocalYY = maxres + self.LocalX = 0 + self.LocalY = 0 + self.BitIndex = 0 + + surface.SetDrawColor(255, 255, 255, 255) + self.TransformStack = {} + + DrawGroup(self,WireLib.SegmentLCD_Tree) +end + +function TOOL:DrawToolScreen(width, height) + DrawSegmentLCDPreview(self,width,height) +end +]] \ No newline at end of file