Basic MathStudio type app

Tried to write a code similar to app called “MathStudio” but with a very basic functionality. I have used Simeon’s “Buffer” class here. Thanks Simeon.

Demo:

Main:

-- Use this function to perform your initial setup
function setup()
    displayMode(STANDARD)
    displayMode(FULLSCREEN)
    screenWidth = 500
    screenHeight = 400
    drawFlag = false
    initFlag = false
    buffer = Buffer()
    xcounter = 0
    result = ""
    errorMsg = ""
    
    graphTable = {}
    
    -- Initializing Textfields
    textField = TextField("Formula", 500, 80)
    resultField = TextField("Result", 500,40)
    -- Initializing Buttons
    button = {}
    titles = {"sin()", "cos()", "tan()", "asin()", "acos()", "atan()", "log10()", "log()", "exp()","pow()", "sqrt()", "pi", "+", "-", "*", "/", "%", "(", ")", ",", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0",".", "x", "Solve", "Plot", "Clear","Del", "<==", "==>",}

    counter = 1
    for _, v in pairs (titles) do
        
        if counter < 13 then
            fill(61, 71, 94, 156)
            r,g,b,a = fill()
        
        elseif counter >= 13 and counter <= 20 then
            fill(61, 113, 31, 155)
            r,g,b,a = fill()
        
        else 
            fill(0, 0, 0, 160)
            r,g,b,a = fill()
        
        end
        table.insert(button,Buttons(v,r,g,b,a,255,255,255,255,15,24))
        counter = counter + 1
    end
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(173, 173, 173, 93)
    
    initializeScreen()
    
    drawButtons()
    textField.pos = vec2(centerPnt.x,HEIGHT - HEIGHT*0.1)
    textField:draw() 
    
    resultField.pos = vec2(centerPnt.x,HEIGHT - HEIGHT*0.18)
    resultField:draw() 
    
    equationStr = buffer:toString()
    
    if initFlag then  
        generateEquation()
        if equationStr ~= "y=" then
                initializeTable()
                executeEquation()
                result = y
        else
            errorMsg = "Not a valid equation"
        end
    end
    fontSize(16)
    l,t = textSize(result)
    text(result, resultField.pos.x-resultField.size.x/2+l/2+5,resultField.pos.y )
    text(errorMsg,WIDTH*0.2,HEIGHT-HEIGHT*0.1)
    
    if drawFlag==true and equationStr ~= "y=" then
       drawCurves()
       spriteMode(CENTER)
       sprite(graph,centerPnt.x,centerPnt.y)
    else
        drawFlag=false
    end 
    buffer:draw()
end

function drawButtons()
    left = WIDTH - WIDTH*0.92
    top = HEIGHT - HEIGHT * 0.265
    counter = 1
    
    for _, b in pairs(button) do
        b.pos = vec2(left,top)
        b:draw()
        
        left=left+b.size.x+5
        if counter == 4 then
            counter = 1
            left = WIDTH - WIDTH * 0.92
            top = top - b.size.y-5
        else
            counter = counter + 1
        end
    end
end

function touched (touch)
    
    for _, b in pairs (button) do
        b:touched (touch)
    end    
end

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(0, 0, 0, 255)
    fontSize( self.fontSize )
    textWrapWidth(500)
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, "\
" )
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)
    if c == "x" then
        xcounter = xcounter + 1
    end
    local l = self.buffer[self.cursor.x]
    
    if l == nil then
        l = {}
        self.buffer[self.cursor.x] = l
    end

    if c == "\
" 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 == "Del" 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
            
            if (l[self.cursor.y-1] == "x") then
                xcounter = xcounter-1
            end
            
            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(textField.pos.x-textField.size.x/2+5,-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,30)
    
    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, "\
" )
end

Other classes and function file is given in the next post as its not allowing to enter more characters in this post.

I try themcode an i have find this: 999999999999
And the code not works But i delet this and put 0

Ups i find this


<a href="tel:999999999999" x-apple-data-detectors="true" x-apple-data-detectors-result="1">999999999999</a> 

Very nice. I had a minor suggestion from watching the video. When pressing sin() then the cursor should be placed in the braces, á la Codea.

What ideas do you have for extending it?

@Andrew: I had that idea in mind and probably I will try to do that but to be honest I already had some problems dealing with the cursor. For example when you type a long equation and I untried to warp the text within the text field so it breaks down to secon line but the cursor remain at the right. I spent few times to deal with it but unfortunately failed to fix it. Probably Simeon can give me an idea how to fix it and that will also help me to extend the idea you gave me. I m still novice :slight_smile:

Codes Continuing

TextField

TextField = class()

function TextField:init(title, w, h)
    -- you can accept and set parameters here
    self.title = title
    self.pos = vec2(0,0)
    self.size = vec2(w,h)
    self.color = color(255, 255, 255, 255)
    
end


function TextField:draw()
    -- Codea does not automatically call this method
    
    fill(self.color)
    strokeWidth(3)
    stroke(190, 190, 190, 255)
    rectMode(CENTER)
    rect(self.pos.x,self.pos.y,self.size.x,self.size.y)
    strokeWidth(2)
    stroke(161, 162, 163, 255)
    rect(self.pos.x,self.pos.y,self.size.x-4,self.size.y-4)
    fill(0, 0, 0, 255)
    textMode(CENTER)
    --text(formulaStr, self.pos.x,self.pos.y)
    
end

function TextField:hit(p)
    local l = self.pos.x - self.size.x/2
    local r = self.pos.x + self.size.x/2
    local t = self.pos.y + self.size.y/2
    local b = self.pos.y - self.size.y/2
    if p.x > l and p.x < r and
       p.y > b and p.y < t then
        return true
    end
    
    return false
end

function TextField:touched(touch)
    -- Codea does not automatically call this method
    if touch.state == ENDED and self:hit(vec2(touch.x,touch.y)) then
        
    end
end

Buttons:

Buttons = class()

function Buttons:init(title,r,g,b,a,fr,fg,fb,fa,fnt,h)
    -- you can accept and set parameters here
    self.title = title
    self.pos = vec2(0,0)
    self.size = vec2(0,0)
    self.fntSize = fnt
    self.height = h
    self.action = function() self:buttonAction(self.title) end

    self.fr = fr
    self.fg = fg
    self.fb = fb
    self.fa = fa
    
    self.color = color(r,g,b,a)

end

function Buttons:hit(p)
    local l = self.pos.x - self.size.x/2
    local r = self.pos.x + self.size.x/2
    local t = self.pos.y + self.size.y/2
    local b = self.pos.y - self.size.y/2
    if p.x > l and p.x < r and p.y > b and p.y < t then
        return true
    else
        return false
    end
end

function Buttons:draw()
    -- Codea does not automatically call this method
    pushStyle()
    fill(self.color)
    font("ArialRoundedMTBold")
    fontSize(self.fntSize)
    -- use longest sound name for size
    
    local w,h = textSize("log10") 
    w = w + 15
    h = h + self.height
    noStroke()
    rectMode(CENTER)
    rect(self.pos.x,self.pos.y,w,h)

    self.size = vec2(w,h)        
    textMode(CENTER)
    fill(self.fr,self.fg,self.fb,self.fa)
    text(self.title,self.pos.x,self.pos.y)

    popStyle()
end

function Buttons:buttonAction(title)
    
    if title == "Clear" then
        resetTodefault()
    elseif title == "Plot" then
        initFlag = true
        drawFlag = true  
    elseif title == "Del" then
        buffer:insertCharacter(title)
    elseif title == "Solve" then
        if xcounter == 0 then
            initFlag = true
            drawFlag = false 
        end
    elseif title == "<==" then
        buffer:moveCursor(vec2(0,-1))
    elseif title == "==>" then
        buffer:moveCursor(vec2(0,1))
    elseif string.len(title) == 1 then
        buffer:insertCharacter(title)
    elseif string.len(title) > 1 then
        
        for p=1,string.len(title) do
            buffer:insertCharacter(string.sub(title,p,p))
            p = p + 1
        end
        
    end 
end
 

function Buttons:touched(touch)

    -- Codea does not automatically call this method
        if touch.state == ENDED and self:hit(vec2(touch.x,touch.y)) then
            if self.action then
                self.action()
            end
        end
end

Codes Continuing:

Functions:

Functions
function generateEquation()
    for n = 1,12 do 
        if titles[n]~="pi" then
            func= string.sub(titles[n],1,4)
        else
            func = titles[n]
        end
            length = string.len(equationStr)
            i =1 
            while i<length do
                chk=nil
                local s =  string.find(equationStr,func,i,true)
                if s~= nil and s>1 then
                   chk = string.sub(equationStr,s-1,s-1)
                end
                
                if s ~= nil and chk ~= "a" then
                    sub1 = string.sub(equationStr,1,s-1)
                    sub2 = string.sub(equationStr,s,string.len(equationStr))
                    equationStr = sub1.."math."..sub2
                    length = string.len(equationStr)
                    i = s + 5 + string.len(func)
                    s=nil
                else 
                    i = i+1
                end                
            end 
        n = n + 1
    end
    
    equationStr = "y="..equationStr
    initFlag = false
end
    
function drawCurves()
    for i = 1,500 do
            ycor = screenHeight/2+graphTable[i]*ratio*0.8         
            lineCapMode(SQUARE)
            strokeWidth(3)
            fill(0, 0, 0, 255)
            fontSize(16)
            stroke(0, 0, 0, 255) 
            
            if ymax == graphTable[i] then
                txt = "ymax = "..ymax
                l,t = textSize(txt)
                text(txt, centerPnt.x-screenWidth/2-l/2-5,centerPnt.y+ycor-screenHeight/2)
                line(centerPnt.x-screenWidth/2-5,centerPnt.y+ycor-screenHeight/2,centerPnt.x-screenWidth/2+5,centerPnt.y+ycor-screenHeight/2)
            end
        
            
            if ymin == graphTable[i] and ymin~=ymax then
                txt = "ymin = "..ymin
                l,t = textSize(txt)
                text(txt, centerPnt.x-screenWidth/2-l/2-5,centerPnt.y+ycor-screenHeight/2)
                line(centerPnt.x-screenWidth/2-5,centerPnt.y+ycor-screenHeight/2,centerPnt.x-screenWidth/2+5,centerPnt.y+ycor-screenHeight/2)       
            end
            
            grcolor = color(27, 30, 145, 255)
            if graphTable[i] >= ymin and graphTable[i]<=ymax then
                graph:set(i,ycor,grcolor)       
            end 
    end
    --fontSize(16)
    l,t = textSize("xmax = -5")
    text("xmax = 5", centerPnt.x+screenWidth/2,centerPnt.y-screenHeight/2-30)
    text("xmin = -5", centerPnt.x-screenWidth/2,centerPnt.y-screenHeight/2-30)
    text("2.5", centerPnt.x+screenWidth/4,centerPnt.y-screenHeight/2-30)
    text("-2.5", centerPnt.x-screenWidth/4,centerPnt.y-screenHeight/2-30)
    text("0", centerPnt.x,centerPnt.y-screenHeight/2-30)
    line(centerPnt.x+screenWidth/4,centerPnt.y-screenHeight/2-5,centerPnt.x+screenWidth/4,centerPnt.y-screenHeight/2+5)
    line(centerPnt.x-screenWidth/4,centerPnt.y-screenHeight/2-5,centerPnt.x-screenWidth/4,centerPnt.y-screenHeight/2+5)
    
    
    txt = "0"
    l,t = textSize(txt)
    text(txt, centerPnt.x-screenWidth/2-l/2-5,centerPnt.y)
    
    --[[txt = ymax/2
    l,t = textSize(txt)
    text(txt, centerPnt.x-screenWidth/2-l/2-5,centerPnt.y)
    
    txt = ymin/2
    l,t = textSize(txt)
    text(txt, centerPnt.x-screenWidth/2-l/2-5,centerPnt.y)--]]
    
    
end


function initializeTable()
    
    graph = nil
    graph = image(screenWidth,screenHeight)
    x=-5
    ymax = 0
    ymin = 999999999999  
     
    for i=1,500 do
        xpos=i
        
       -- executeEquation()
       
        local exe = loadstring(equationStr)  
        if exe then 
            exe()
        else
            str = buffer:toStringWithoutActiveLine()
            exe = loadstring(equationStr)
            if exe then 
                exe() 
            end
        end 
        
        
        if y > ymax then
            ymax = y
        end
        if y < ymin then
            ymin = y
        end
        graphTable[i] = y
        x=x+0.02
       -- print(graphTable[i])
    end
   
    if ymax > math.abs(ymin) then
        max = ymax
    else
        max = math.abs(ymin)
    end
    
    if max >= 1 then
        ratio = (screenHeight/2)/max
    else
        ratio = screenHeight/2
    end
    
end



function initializeScreen()
    noStroke()
    fill(255, 255, 255, 155)  
    rectMode(CENTER)
    centerPnt = vec2(WIDTH*0.72,HEIGHT/2)
    rect(centerPnt.x, centerPnt.y, 500,400)
    strokeWidth(3)
    stroke(132, 131, 131, 255)
    line(centerPnt.x-250,centerPnt.y,centerPnt.x + 250,centerPnt.y)
    line(centerPnt.x,centerPnt.y-200,centerPnt.x,centerPnt.y+200)
    strokeWidth(2)
    for i=1,500 do
        if i%5==0 then
        line(centerPnt.x-250+i,centerPnt.y-200,centerPnt.x-250+i,centerPnt.y+200)
        end
    end
    
    for i=1,400 do
        if i%5==0 then
        line(centerPnt.x-250,centerPnt.y-200+i,centerPnt.x +250,centerPnt.y-200+i)
        end
    end
end

function executeEquation()
        exe = loadstring(equationStr)  
        if exe then 
            exe()
        else
            str = buffer:toStringWithoutActiveLine()
            exe = loadstring(equationStr)
            if exe then 
                exe() 
            end
        end 
end
function resetTodefault()
    buffer:clear()
    drawFlag = false 
    graph=nil
    xcounter = 0
    result = ""
    errorMsg = ""
end

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