Cider -- interface builder / code generator

Sure, in fact it is probably more natural to take a table. I think it was old habits for me :slight_smile: it started as I was considering writing a quizz type app in which one of the questions was based on a grid. Initially I just made a grid but as I was gluing together a bunch of checkboxes for the multiple choice I thought I could as well hit two birds with one stone.

Maked some changes to fit better in CiderControls. I’ll add to the GIST later, but here’s what I put together so far:


-- the toucharray class takes a string in the latex format rows separated by & and ---
-- columns by \\\\ and produces a touchable array whose topleft corner is is at x,y and whose 
-- celsizes are either automatic or given by the user. 
-- the typical call is  
--  a=touchArray("aaaaaaaa aaaa & b \\\\ c& d & t\\\\ a& 5&", 300,300, "single") or
--  a=touchArray("aaaaaaaa aaaa & b \\\\ c& d & t\\\\ a& 5&", 300,300, "single",50,50) or
--  a=touchArray("aaaaaaaa aaaa & b \\\\ c& d & t\\\\ a& 5&", 300,300)
-- the single variation allows one to choose a unique cell
--the table self.selected is a the set of selected cells, that is the 
-- value of self.selected[1..","..2] is true if the cell 1,2 is touched and false otherwise
--  you can check if the cell i,j is selected via self:check(i,j) and
-- you can get the text in the cel i,j by self.array[i][j].text
 
touchArray = class(Control)
 Grid = touchArray        --make an alias

function touchArray:init(info, left,top ,seltype, cellsizex, cellsizey, callback)
    Control.init(self)
 
    if type(info) == "string" then 
          self:arrange(info)        --creates a table
      if cellsizex~=nil then self.celsizex=cellsizex textWrapWidth(cellsizex)  end 
        if cellsizey~=nil then 
                self.celsizey=math.max(cellsizey,20*self.size/self.celsizex) 
        else   
                self.celsizey=math.max(self.celsizey,20*self.size/self.celsizex) 
        end
    else
        assert(type(info)== "table" , "Must have a table or LaTeX string to use this code.")
        self.tab = info        --AC: Note we assuming a table 
        --AC:assuming the first cell is a model for all other cells. BAAD assumption!
        w, h = textSize(info[1][1])
        print(w,h)
          self.celsizex=math.max(cellsizex, w)
          self.celsizey=math.max(cellsizey,h)
        
        --self.celsizex = cellsizex
        textWrapWidth(cellsizex)
        self.size = self.celsizex
        --self.celsizey = self.cellsizey
    end
    
     self.selected={}

    self.seltype=seltype
    self.left = left
    self.top = top
    
    maxHeight = 0
    maxWidth = 0
     self.array={}
     for i,v in pairs(self.tab) do
        self.array[i]={}
        if maxHeight < i then maxHeight = i end
        for j,k in pairs(v) do
            if maxWidth < j then maxWidth = j end
            table.insert(self.array[i],cell(k,self.left+self.celsizex*(j-1),self.top-
            self.celsizey*i,self.left+self.celsizex*j,self.top-self.celsizey*(i-1), 
            callback))
            self.selected[i..","..j]=false
        end
     end

    self.bottom = self.top - self.celsizey * maxHeight
    self.right = self.left +  self.celsizex * maxWidth 
    self.border = 2        --px border
end
 
function touchArray:arrange(str)
    self.tab={}
    self.celsizex=0
    self.celsizey=0
    i=1
     
    for a in string.gmatch(str,"([^\\\\]+)") do
      self.tab[i]={}
        for b in string.gmatch(a,"([^&]+)") do
      
          d=b:match "^%s*(.-)%s*$"   
          w, h = textSize(d)
          self.celsizex=math.max(self.celsizex, w)
          self.celsizey=math.max(self.celsizey,h)
          table.insert(self.tab[i], d)
        end
       i = i + 1
       end
      self.size=self.celsizex  --cell width?
end
 
function touchArray:draw()
    pushStyle()
    fill(self.background)
            
    textWrapWidth(self.celsizex)
    rect(self.left-self.border, self.bottom-self.border, 
        self:width()+self.border*2, self:height()+self.border*2)
    popStyle()

    for i , v in pairs(self.array) do 
        for j, k in pairs(v) do            
          k:draw()              
        end
    end
end
function touchArray:check(i,j)
        return self.selected[i..","..j]
    end
 
function touchArray:touched(touch)
    for i , v in pairs(self.array) do 
        for j, k in pairs(v) do
            self.selected[i..","..j]=k.selected
            k:touched(touch)
            if self.seltype=="single" then
            if k.selected then
                for a,b in pairs(self.array) do 
        for c,d in pairs(b) do
            if d ~= k then d.selected=false 
            end end end end end 
        end
    end
end

--A cell in the TouchArray
cell=class(Control)

function cell:init(string,x,y,z,w,callback)

    Control.init(self,string,x,y,z,w, callback)
    self.selected=false
end

function cell:draw()
    if self.selected then
        self.background =color(175, 22, 47, 255)
    else 
        self.background=color(255, 255, 255, 255)       
    end         
    Control.draw(self)        
end

function cell:touched(touch)
    if self:ptIn(touch.x, touch.y) then
        if touch.state == BEGAN  then
            self.selected = not self.selected
            if self.callback ~= nil then self.callback(self) end 
        end
        return true
    end
    return false
end

--make a click sound when the control is touched
function click(val)
    sound(SOUND_HIT, 1960)
    if val.selected then 
    print(val.text)
    end
end

Sample code:

 assert(Frame ~= nil, "CiderControls not found. Please add the dependency.")

   
function setup()
     aTable= {}
    masterTable={}
    for x=1, 7 do
      for y=1,5 do
            --test for holes.
            if math.random(10) < 8 then 
            table.insert(aTable, "testing wrapping text:" ..tostring(x) .. ":" .. tostring(y))
            else
                table.insert(aTable, nil)        --not quite what i want, but good test
            end
        end
        table.insert(masterTable, aTable)
        aTable={}
    end
    a=touchArray("aaaaaaaa aaaa & b \\\\ c& d & t\\\\ a& 5&", 100,300, "Muliple", 75,25 ,click)
    b=Grid(masterTable, 300,625, "single", 80,25 ,click)
    b.border=5
    b.background=color(192,192,192,128)        --transparency for fun
    
end


function draw()
     background(194, 194, 194, 255)
  a:draw()
b:draw()
        
end

function touched(touch)
      a:touched(touch)
    b:touched(touch)
end

Looks very nice, I have no objection to renaming it to Grid

I made an alias to keep compatibility.

@aciolino When we get a little closer to 1.5, I’m going to take your current Git, and replace the portions of Cider that write output to the screen so that it writes directly to a new project. Should be much slicker.

Ok, sounds good. Let me know when so I can make sure we get the right version of several newer controls out there.

@acilino It’s that time. With the impending release of 1.5, I need to modify Cider to use the ability to write code straight to a project tab (especially as text in the output area no longer seems to be selectable).

Can you point me at the most up to date gist? Thanks.

I have the current stuff available here for a short while becuase GIT and GITHUB and I do not seem to see eye to eye. Grab’em from here…

http://dl.dropbox.com/u/24774303/PublicCider/Cider.lua
http://dl.dropbox.com/u/24774303/PublicCider/CiderControls.lua
http://dl.dropbox.com/u/24774303/PublicCider/Menu.lua
http://dl.dropbox.com/u/24774303/PublicCider/TextBox.lua (has issues, not sure why)

I am doing a complete rewrite and merge of two seperate projects to create anintegrated Window manager with controls and menuing. I think that’ll be demoable in a day or two. I bring it up because I am planning on using a large amount of these controls, but fixing some of the design issues with they way they work - they are all absolute coordinates, which makes them somewhat difficult to use in other projects. Not impossible, just difficult.

Cider looks nice. This weekend I’ve been testing to implement a UI similar to QML used in the Qt Library. With my Codea version of this you can write the logic for a button like this:

Button = UI.Item({id="button",
                  w=100, h=50,
                  text="Button",
                  focusColor=color(125,125,250), 
                  color=color(125,125,125)},
    UI.TouchArea({id="touchArea", anchors={fill="buttonRect"}, 
                  onClicked="parent.onClicked"}),
    UI.Rect({id="buttonRect", w="parent.w", h="parent.h",
             color=function ()
                if touchArea.pressed then 
                    return button.focusColor
                else
                    return button.color
                end
             end}),
    UI.Text({text=function() return parent.text end, 
             anchors={centerIn="parent"}})
)

The code is quite declarative. You can then use the button component in a project like this:

function setup()
    displayMode(FULLSCREEN)
    scene2()
end
 
function scene2()
    scene = UI.Item({},
        UI.Rect({w="200", x="b2.x",y="b1.y/2",
                 color=color(100, 183, 91, 255)},
            UI.Image({image="Planet Cute:Character Boy",
                   x=5,y=5,w="parent.w-10",h="parent.h-10"})
        ),
        Button({id="b1", text="Up", y=100, x=10}),
        Button({id="b2", text="Right", x="prev.right", y="prev.y"}),
        Button({id="b3", text="Reset", x="b1.x", y="b1.top"})
    )
    
    local h = scene:id("b1")
    h.onClicked = function ()
        tween(.2, h, {y=200+h.y})
    end
    scene:id("b2").onClicked = function ()
        tween(.2, h, {x=200+h.x})
    end
    scene:id("b3").onClicked = function ()
        tween(.2, h, {x=10,y=100})
    end
end
 
function draw()
    background()
    scene:draw()
end
 
function touched(touch)
    scene:touched(touch)
end

You can reference to other objects in lua code, thanks to some use of setmetatable. A bit chaotoc right now, but the gist for the current code is here: https://gist.github.com/4644451

Thanks, @aciolino. Sounds like I need to hang on for a bit and see the next spin you give the project before taking any other steps. I just wanted to use the new saveProjectTab function of 1.5 to write the generated code directly to a temporary project tab, making it easier and neater than the copy/paste out of the output window (and essential under 1.5, because you can no longer copy output).

@tnology That looks very interesting. Something that would probably pay off in the project I’m currently piddling around with (except that, since I’m hustling to get this little 2D game to market, it’ll probably go out with the ugly, inheritance-through-duplicate-code mess that I’ve already written).

When we get to the point where Cider is essentially stable in its core design, and working correctly for 1.5, it’s my intention to add ā€œone touch formsā€ – an option that would allow you to either create a stand along program that would act as a data collection form, or to create the form as a file that can be used by a general purpose forms program.

Interesting, at the end of the day I am doing something very similar to above, or functions are declared is callied in instantiating class.

I have to update the textbox that has problems and doesn’t work correctly.

@aciolino in your latest version, there’s a call to the function saveCode().

Where is this function found? Thanks.

SaveCode can be safely removed. It’s purpose is to back up code to an external service. I archive to Dropbox.

If interested, here’s the class…

----
--place this at the top of each tab that needs achiving.
--Must have 4 dashes and no spaces after captureTab for this to operate
--captureTab("filename.lua", captureCode(1))
----
--if you want to achive everything in one file, call in setup()
--saveCode("filename.lua")
-- since all contents in code are concatenated...

captureCode = debug.getinfo -- Give this function a friendly name
local code = ""    --concat all code into this string.
codeTabs={}        -- store code in a table to write file by file



function captureTab(tabName, tabCode)
    if #code > 0 then code = code.."\
\
" end
    code = code.."--\
-- "..tabName.."\
--\
"
    code = code..string.match(tabCode.source, ".-%-%-%-%-\
(.*)") 

    table.insert(codeTabs, {tabName, code})
    print (tabName.." captured.")
    code=""
end

--by this time, code has all of the tabs pasted together.
function saveCode(key)
    
    appKey = "your key"  --app key
    appSecret = "your secret"  --app secret
    db = DropBox(appKey, appSecret)   

    db:CreateFolder(key, function(data)
        for k,v in pairs(codeTabs) do
            name=v[1] 
            code=v[2]
            print(key.."/"..name)
            --printing name below seems to fail!
            db:Write(key.."/"..name, code, 
               function(data)  --print( "File written.") 
                end)
        end
    end)
end

--example: in each tab add at the top:
--captureTab("main.lua", captureCode(1))
----

--and add to setup() of your target class()
--function setup()
--saveCode("ProjectName")
--end




Sorry if this is a dumb question but I cant figure out how to pull the code out of the generator. It seems that I cant copy it. I believe I have 1.5.1.

Thanks

Codea 1.5 seems to have changed the output window. In 1.4.6, you coulod copy and paste the whole output window. I think @Mark is going to update the original Cider project to alow for writing directly to tas in Codea 1.5.

I’ll be making the same mode to Cider2 later on.

I am new to Codea and Lua and have only done minimal programming in the past. I like what I see with Cider, but cannot get it to run without errors. I created a new project with 4 tabs; Main, Menu, CiderControls, and TextBox and copied/pasted the code from aciolino’s Dropbox links. Should I be doing something else? When I run I get some controls on screen, but touching anything immediately causes an error: attempt to index global ā€˜menu2’ (a nil value).

Please help. I am trying to make a simple Ideal Otto Cycle Model to show my Thermodynamics another example of how they could mathematically model this problem. I would like switches, buttons, and text box inputs to set variables.

Brian

Here’s a version updated for Codea 1.5

Cider
https://gist.github.com/devilstower/5130706

CiderControls
https://gist.github.com/devilstower/5130686

I snuffed a lot of bugs on these and changed output to be routed to a code tab. See what you think.

@Briandbrady If you mix too many people’s code together all at the same time, you will get problems like this.

As a general programming rule, it is a good idea to start very small and very slowly add complexity. In your case, I would probably start by hard coding all the variables in setup, and printing results in the output window, just to get the core calculations right. Then I would gradually build the user interface one baby step at a time.

The less you change at any time, the easier it is to find your mistakes.

I have been programming as a hobby for a long time, and I use this approach, and I still spend hours trying to find tiny errors.

Thanks Mark and Ignatz. I have a barebones program that is generating basic output and even uses parameters at this time. I am hopeful of switching from using the parameters to vary input values to using interface elements. I am adding one thing at a time though. That is good advice.