Codea written in Codea
  • SimeonSimeon February 16

    Some of you might get a kick out of this. After watching Bret Victor's talk I spent a few hours writing an "instant" version of Codea inside Codea. Basically it gives you a keyboard and you can write Codea code, any code you write is instantly run and drawn behind your code.

    It's pretty interesting. Here's the code and a screenshot.

    Codea Instant

    Main file is Here: http://pastebin.com/8enSYdgZ — does not paste into forums due to Emoji.

    Buffer

    Buffer = class()
    
    function Buffer:init()
        self.buffer = {}
        
        self.font = "Inconsolata"
        self.fontSize = 20
        
        -- x = line, y = pos
        self.cursor = vec2(1,1)
        
        self.t = 0
        self.cursorBlink = 0
    end
    
    function Buffer:setStyle()
        textMode(CORNER)
        font( self.font )
        fill(255)
        fontSize( self.fontSize )
        textWrapWidth(10000)
    end
    
    function Buffer:cursorToScreen(c)
        -- Get a subset of the buffer
        local lines = table_slice( self.buffer, 1, c.x )
        local l = self.buffer[c.x]
        
        local upToStr = self:bufferToString(lines)
        local lstr = nil
        
        if l then
            lstr = table.concat(l)
        end
        
        pushStyle()
        
        self:setStyle()
        
        local lw = 0
        local _,lh = textSize("A")
        local emptyLine = true
        
        if lstr and lstr ~= "" then
            lw,lh = textSize(string.sub(lstr,1,c.y - 1))
            emptyLine = false
        end
        
        local pw,ph = textSize(upToStr)
        
        if emptyLine then
            ph = ph + lh
        end
        
        popStyle()
        
        return lw,(ph - lh/2)
    end
    
    function Buffer:bufferToString(b)
        local bstrings = {}
        for k,v in pairs(b) do
            table.insert(bstrings, table.concat( b[k] ) )
        end
        
        return table.concat( bstrings, "\n" )
    end
    
    function Buffer:moveCursor(o)
        self.cursor = self.cursor + o
        
        self.cursor.x = math.max(1, math.min(self.cursor.x, #self.buffer))
        
        local l = self.buffer[self.cursor.x]
        local y = self.cursor.y
        
        y = math.max(1, math.min(y, #l + 1))
        self.cursor.y = y
    end
    
    function Buffer:insertCharacter(c)
        local l = self.buffer[self.cursor.x]
        
        if l == nil then
            l = {}
            self.buffer[self.cursor.x] = l
        end
    
        if c == "\n" then
            local start = table_slice(l,1,self.cursor.y)
            local tail = table_slice(l,self.cursor.y+1,#l)
            
            table[self.cursor.x] = start
            table.insert(self.buffer, self.cursor.x+1, tail)
            
            self.cursor = vec2( self.cursor.x + 1, 1 )
        elseif c == BACKSPACE then
            if self.cursor.y == 1 then
                -- delete line
                local prevLine = self.buffer[self.cursor.x - 1]
                
                table.remove(self.buffer, self.cursor.x)
                
                if prevLine then
                    self.cursor = vec2(self.cursor.x - 1, #prevLine + 1)
                    table_append( prevLine, l )
                end
            else
                -- delete character
                table.remove(l, self.cursor.y - 1 )
                self.cursor = vec2(self.cursor.x, self.cursor.y - 1 )
            end
        else
            table.insert(l, self.cursor.y, c)
        
            self.cursor = vec2( self.cursor.x, self.cursor.y + 1 )
        end
    end
    
    function Buffer:draw()
        self.t = self.t + 8 * DeltaTime
        self.cursorBlink = (math.sin(self.t) + 1) * 128
        
        pushStyle()
        
        self:setStyle()
        
        local str = self:toString()
        local w,h = textSize(str)
        
        pushMatrix()
        translate( 40, -40 )
        
        text(str, 0, HEIGHT - h)
        
        -- Draw cursor
        -- Cursor pos x,y
        local cpx,cpy = self:cursorToScreen(self.cursor)
        fill(0, 87, 255, self.cursorBlink)
        rectMode(CENTER)
        rect(cpx,HEIGHT - cpy,5,22)
        
        popMatrix()
        popStyle()
    end
    
    function Buffer:clear()
        self.cursor = vec2(1,1)
        self.buffer = {}
    end
    
    function Buffer:toString()
        return self:bufferToString(self.buffer)
    end
    
    function Buffer:toStringWithoutActiveLine()
        local bstrings = {}
        for k,v in pairs(self.buffer) do
            if k ~= self.cursor.x then
                table.insert(bstrings, table.concat( self.buffer[k] ) )
            end
        end
        
        return table.concat( bstrings, "\n" )
    end
    

    EmButton

    -- Emoji Button
    EmButton = class()
    
    function EmButton:init(pos,txt)
        -- you can accept and set parameters here
        self.pos = pos
        self.text = txt
        self.action = nil
        self.highlight = false
        self.color = color(255,255,255,255)
    end
    
    function EmButton:size()
        pushStyle()
        self:setStyle()
    
        local w,h = textSize(self.text)
    
        popStyle()
    
        return w,h
    end
    
    function EmButton:hitTest(lp)
        local w,h = self:size()
    
        local left,right = self.pos.x - w/2, self.pos.x + w/2
        local top,bottom = self.pos.y + h/2, self.pos.y - h/2
    
        if lp.x > left and lp.x < right and
           lp.y > bottom and lp.y < top then
            return true
        end
    
        return false
    end
    
    function EmButton:setStyle()
        fill(self.color)
        noStroke()
        textMode(CENTER)
        fontSize(50)
        font("AppleColorEmoji")
    end
    
    function EmButton:draw()
        pushMatrix()
    
        translate(self.pos.x,self.pos.y)
    
        pushStyle()
        self:setStyle()
    
        text(self.text,0,0)
    
        popStyle()
    
        popMatrix()
    end
    
    function EmButton:touched(touch)
        if touch.state == ENDED then
            -- Tapped
            if self:hitTest( vec2(touch.x,touch.y) ) then
                if self.action then self.action() end
            end
        end
    end
    
  • SimeonSimeon February 16

    Util

    function table_append (t1, t2)
        local t1s = #t1
        for k,v in pairs(t2) do t1[k + t1s] = v end
    end
    
    function table_slice (values,i1,i2)
        local res = {}
        local n = #values
    
        -- default values for range
        i1 = i1 or 1
        i2 = i2 or n
        if i2 < 0 then
            i2 = n + i2 + 1
        elseif i2 > n then
            i2 = n
        end
    
        if i1 < 1 or i1 > n then
            return {}
        end
    
        local k = 1
    
        for i = i1,i2 do
            res[k] = values[i]
            k = k + 1
        end
    
        return res
    end
    
  • SimeonSimeon February 16

    Main file is Here: http://pastebin.com/8enSYdgZ — does not paste into forums due to Emoji.

  • ruilovruilov February 16

    It's awesome!

    Will inspect this code later - are you using loadstring?

    How much more would we need to convert this to a primitive codea debugger?

  • SimeonSimeon February 16

    It does use loadstring(). I'm not too sure how it could be converted into a debugger — if you think of anything it would be interesting to hear.

    Perhaps we need to add some more meta-methods to Codea, like reading and writing to your project's buffers.

  • XavierXavier February 16

    oh nice job, that's fun !

    Going to check that code out, thanks for sharing

  • ruilovruilov February 16

    I was thinking of experimenting on my own projects by hacking outside the sandbox, to see if in concept works first. I remember you had posted a lua interpreter in lua before, and might be able to use that...I think this is my next codea project :)

  • beebee February 16

    I knew someone will write something like this. I thought it's gonna be done first by @bortels as he's kinda an adventurer coder (is that term existed?). :D

    This is very basic but very fun nevertheless. I think it would interest kids as it's more interactive. I'm gonna give it to my 5 y.o son and see how he respons this. Thank you, @simeon. :)

  • ZoytZoyt February 16

    Just before I saw this post (while bored in science), I had thought about using my file io hack to do this. Mabe I'll alter the code a little to allow you o select the project to edit... Feel free to take my idea.

  • Ipad41001Ipad41001 February 17
    local pKey = EmButton(vec2(0,0), "A")
        pKey.action = function() 
            if displayMode() == STANDARD then
                displayMode(FULLSCREEN_NO_BUTTONS) 
            else displayMode(STANDARD) 
                print(buffer:toString()) end 
        end
        
        keys = {closeKey,startKey,leftKey,
                rightKey,endKey,upKey,
                downKey,keyKey,pKey}
    

    minor addition to main to allow cut/paste

  • SimeonSimeon February 17

    Nice change @Ipda41001.

    @John also made a really good change by using pcall() to execute the code block, and only executing the last-working-block. This allows it to ignore runtime errors and always draw something on the screen.

  • dylanfdylanf March 6

    Love this!

    Here's code using the pcall idea to catch errors.

        local chunk, loadErr = loadstring(str)
        
        if chunk then
            local status, err = pcall(chunk)
            if status then
                lastGood = chunk
                clearOutput()
                print("allgood")
            else
                clearOutput()
                print(err)
            end
        else
            if lastGood then
                pcall(lastGood)
            end
            
            clearOutput()
            print(loadErr)
        end
    

    Run it non-fullscreen and move the output window up to see error feedback as you type.

  • ZoytZoyt March 6

    Cool. And I also found a big that crashes Codea when you try and delete a character that's not there.

  • Simeon you used a function "chunk()". Is that a lua function? What it actually does? I figured out it executes the command we typed but Where can I learn about it more?

  • SimeonSimeon March 7

    @rashedlatif "chunk" was my choice of variable name. Basically the loadstring() function accepts an arbitrary "chunk" of Lua code and returns it as an executable "chunk". I store that in a variable called "chunk" which I later execute by putting function call parentheses after the variable name (i.e. chunk()).

    However @dylanf's method is much, much better. Using the built in Lua function pcall() to execute the chunk isolates run-time errors so that they don't crash or interrupt the app.

  • Makes Sence :) thanks Simeon. I m still very beginner in lua. But no doubt these kinds of features are very powerful ones.

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Sign In with OpenID Sign In with Google

Sign In Apply for Membership

In this Discussion