Howdy, Stranger!

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

Codea written in Codea

SimeonSimeon Admin Mod
edited February 2012 in Code Sharing Posts: 4,986

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

Comments

  • SimeonSimeon Admin Mod
    Posts: 4,986

    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 Admin Mod
    edited February 2012 Posts: 4,986

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

  • edited February 2012 Posts: 447

    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 Admin Mod
    Posts: 4,986

    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.

  • Posts: 196

    oh nice job, that's fun !

    Going to check that code out, thanks for sharing

  • edited February 2012 Posts: 447

    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
    Posts: 381

    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. :)

  • Posts: 2,820

    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.

  • 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 Admin Mod
    Posts: 4,986

    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.

  • Posts: 2

    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.

  • Posts: 2,820

    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 Admin Mod
    edited March 2012 Posts: 4,986

    @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.

  • Posts: 86

    This is very cool @Simeon!

  • Posts: 580

    Heh, MetaCodea.

  • edited July 2012 Posts: 226

    Hi, sorry for asking this (I'm a newbie): what does the 'current' version of this 'MetaCodea' look like? I mean, with the suggested additions from @Ipad41001, @John & @dylanf. Thanks in advance,
    Victor ~O)

    Edit: as usual, I've already found it by myself (the best way to get the basics), except the one from @John mentioned by @Simeon. :-D

    Tiny bugs: cursor takes a lower position, when at the left of the code lines, and blinks incorrectly when a program executes. I've checked with this tiny piece of code:

    function draw()
        ellipse(300,500,50)
    end
    

    Tiny suggestion: to cache written code and to add a "stop execution" button.

  • Posts: 2

    Wow, codeseption, we need to go deeper. This could be useful.

  • The user and all related content has been deleted.
  • Posts: 167

    This is really cool! =D>

  • Posts: 1,976

    @NatTheCoder @code_maker This is from early 2012... Google searches tend to bring up old discussions, please check the date before posting.

This discussion has been closed.