THE Button library you want for codea

My thoughts on composition. I don’t think you need a composition “engine” to use composition, you can just use the class system that Codea comes with. In fact, having written various engines/ managers, and then not ended up using them much, I’m generally not keen on engines/ managers.

What I do is, say if in a game you have a Health class (a component that handles losing lives, shielding an entity from further damage for a few seconds after it’s been hit, killing an object when its lives run out etc), is simply have entities in the program own instances of Health, rather than inheriting from it, eg: self.health = Health{parent = self}, and just have them pass the parent through to the component. The entity tests to see if it has a component before calling its methods: if self.health then self.health:update() end. This meets most of the specifications for component-based design, without the need for hundreds of lines of engine/ manager. eg mutability: in a game I’m working on, parts of the scenery come alive. At that point, they acquire a Health component.

Getting back to the button/UX library, a good use for components might be the look of the buttons. So if users wanted to skin the buttons, to over-ride the default iOS7-impersonating look, they could just pass in a different component, eg Button{title="A skinned button", skin =MyCustomDrawComponent()}

thank you for your thoughts. That will help, for sure.

Thinking again about #3, auto-orientation changing, how about this:

  • if x or y > 0 then they are treated as a regular coordinate, ie relative to the origin

  • But, if x or y are negative they are treated as being relative to the right and top edge. eg x = -60 will become x = WIDTH -60 (and will automatically change when the orientation changes).

  • Finally, if x or y are a fraction between 0.0 and 1.0 they will be treated as a proportion of WIDTH and HEIGHT. So x = 0.5 is parsed as x = WIDTH * 0.5. This could also apply to button width and button height, eg w = 0.5 for a button covering half the screen. This should probably activate a CENTER mode (ie the x and y describe the centre of the element, not the bottom-left corner)

  • You could also have a CORNERS mode where instead of specifying an element’s width and height, you specify an x2 and y2 for the top right corner, using the same rules as above, and width and height are then derived from this, as x2-x and y2-y.

  • You could combine all of the above. So say if you wanted a button of width 60 to stick to the middle of the right hand edge, it would be x = -30, y = 0.5

i’ve used this syntax, it works!

Hello everyone! This thread is very interesting!

I thinks about some little things:

  • There can be two libs:

    • A very light one, really focus on button and simplicity
    • A more powerful one, more focus on an abstract gui system with layouts, editors, file format, …
  • Maybe it can be interesting to do a little competition around that? I thinks this is a good way for founding some pearls in terms of features, architectures or code tricks. And after, we can build a more standard library with ideas found in best entries, well documented, etc…

Personally, I prefer something light, simple and elegant. It’s easier and faster to integrate in various project.

PS: Your previous version (xfc) was more powerful than light, but really impressive! It can be useful to keep it up to date with the last version of Lua (in your function Table.map, use {...} and #tbl instead of tbl.n)

Working to prepare Sked (shift line up tool) for landscape and portrait, iPad and iPhone, has been a graphic demonstration of how difficult it is to be truly “universal.” The controls from Cider, which store their positions in simple screen coordinates, turn into a mess with different screen sizes and orientations. But even using points and relative locations isn’t enough. It takes active management (I’m using a kind of “layout page” that supports tweaking rules) to make things flexible in the sense of spacing controls appropriately, resizing fonts as needed, adjusting relative icon positions at different scales, abbreviating text, moving items from button rows on iPad to menus on iPhone, etc.

I have it running, but with way too much overhead at the moment. If there is ever a Cider 8, it’s going to break old code.

@Jvm38, this is an absolutely great pursuit and thanks for sounding the call.

Personally, from a design standpoint, I find it easiest to think in terms of user stories. So, for instance, “beginner has scaled the learning curve enough to have the basics of Lua down, now just wants to make a button that does something, anything, as quickly as possible”, is a user story that I myself have lived, and that I imagine is very, very common.

Another user story is “experienced Codea user is starting a new project and shudders at the thought of having to write yet more button code, would love to be able to write something like button = Button("press me", CENTER, thisFunction) and get on with it”. I’ve also been this guy.

A third story would be “experienced Codea user, well into a project, with several basic buttons already set up, now wants to tweak layout and appearance, and would love it if the button class had simple, easy methods exposed for doing so.” I haven’t been this guy yet. I have a ways to go.

So, to me, it comes down to this: can all these be the same spec? I’m not sure. The classic Apple trade-off is to remove configurability for the sake of ease of use. It depends what you mean when you talk about THE button library, I suppose. That Apple trade-off is usually resented the most keenly by people who like to get their fingers in the guts of their computers, so in the context of Codea we’re at a strange intersection. Asking people to join us in an adventure of learning, but also trying to make that learning easy. Programmers skillfully negotiating that tension is what results in a good experience for the user. Choices must be made.

tl,dr: my sympathy is for the newcomer.

I think newcomers would love:

  • a default button can be created with one line of code and no configuration (i.e. typing button() into the draw() function makes a default button appear in the middle of the screen)
  • an easily-activated drag-and-drop mode–after you put button() into draw() there is a way that you can run the project and manually drag it into the position you want it
  • either an iOS 7/8/9 look and feel–or a Codea look and feel

…that kind of thing would get newcomers feeling that “wow I can make a real app” feeling in very short order. I vote for that.

i think well chosen default parameters can go a long way to giving you simplicity with the option of getting as complex as you want.

I’m not convinced, however, of the need to provide a drag and drop facility. Since everything in Codea needs to be placed on screen using code based coordinates, I see little point writing functions to save users that trouble, just for the buttons in their app. In other words, users already know how to place things on the screen themselves.

@yojimbo2000 concerning obj.health = health{parent=self}
i like that too. But i noted that in his component template Simeon didnt want pass the parent to the component. This is puzzeling me, there should be a good reason for it (there is always a good reason for Simeon choices). Should we pass the parent, as you did, or avoid passing it? This is a very important design choice, because it will impact most of the code.
I’ve tried to code without passing the parent to the component, only having the parent sending data to the compoenent or reading from it. Maybe this makes the code too complex…? any thoughts?

It may just be for flexibility. If you’re writing a template, you have to make it very flexible, so that for example, more than one type of object can use a component, even if that object uses different names for the same data.

But if you’re writing a button class, you may not need to worry about that, and it’s just easier to pass the parent.

Simeon’s template doesn’t pass the parent (I assume you’re talking about this post http://codea.io/talk/discussion/comment/59107/#Comment_59107 ?), but then note that the updateObject method says this:

function Draggable:updateObject(obj)
    -- Somehow move the object
end

This, --Somehow move the object, is the problem of communicating between components.

There’a a nice discussion of this here:

http://gameprogrammingpatterns.com/component.html#how-do-components-communicate-with-each-other

Nystrom suggests three approaches:

  1. By modifying the container object’s state
  2. By referring directly to each other
  3. By sending messages

I’m suggesting that 1) and 2) are the simplest ways to --Somehow move the object, eg self.parent:move(), or by passing a callback into the component. Taking these in order:

  1. There is always going to be some core data, usually things like position, dimensions, that lots of the components want to share.I think as long as it’s just fairly basic data, position and dimensions, that is held in the container object, this is basically OK. Obviously a large part of the point of this kind of system is you do want to farm as much data and methods as you can out to the components, so that, eg all the fiddly stuff to do with lighting, keyframing animation etc is encapsulated in the Mesh component class, and all the fiddly timers to do with how long an entity stays shielded for etc is enclosed in Health etc.

With inheritance, I now like to have very shallow and wide inheritance (ie a game object class that all game objects inherit). This makes it easy to have a consistent interface.

If there’s a section of code that is calling the parent extensively, then you can always use local p = self.parent and then p.pos etc.

  1. With the second one, remember that because we have closures in Lua, “directly refer to each other” does not have to mean hard-wiring components together. Closures in Lua are so flexible and powerful, and they’re a well-documented feature, that lots of Codea users will be familiar with (eg from using tween, parameter, http.request etc etc). Callbacks work well I think when a component basically only has one main method. As this describes almost all interface elements, they’re perfect for wiring up buttons.

  2. I have experimented with the third option, building a messaging/ mediating/ observer system into the entity class. I think it’s appropriate for events that multiple knock-on effects, and multiple listeners: eg onCollision calls health:hit, mesh:animate, physics:recoil, tutorial:youGotHit (actually, it’s almost worth having just for doing a tutorial system) etc. But I think it’s redundant for one-way connections, or cases where there is only going to be one listener (eg stick moves → player moves). If an event (stick moving) is only going to have one listener (player), then you might as well directly wire them up with a callback (you can always rewire the callback, eg if you want to animate a cutscene).

The downside of using this kind of observer/ messenger system, is IMO, code reusability goes down, because you’re tying your code into a system.

If instead you have objects own instances of component classes, connecting them where appropriate with callbacks, you’re just using absolutely standard Codea and Lua techniques, and I find that I reuse things more.

very interesting, thanks.

Because no one asked for it ( :slight_smile: )I went ahead and wrote a simple version of what I’m thinking of. It’s a little clumsy, but it shows the ease of use I personally would have loved as a newcomer.

This is the only code necessary in main:

function draw()
     background(colorYouChoose)
     button("hello world")
     button("sweet")
end

…that gets you two simple buttons that you can position as you like, and will retain their positions between project launches.

You can add as many buttons as you like just by adding more button(name) statements to draw

I didn’t put in any callback functionality for this example because that implementation is a problem already solved elsewhere.


--# Main
-- SimpleButton

function setup()
    --[[ commented out testing code:
    local runningTest = false
    if runningTest then
        testButtonName = "hello world"
        button(testButtonName)
        local touch = {x=10,y=19}      
        buttonHandler.savePositions(testButtonName, touch)
        print("saved position")
        buttonHandler.dragButton(testButtonName, touch)
        print("dragged button")
        loadedPosition = buttonHandler.positionOf(testButtonName)
        print("loaded position is", loadedPosition.x, loadedPosition.y)
    end
    ]]
end

-- This function gets called once every frame
function draw()
    background(255, 177, 0, 255)
    button("hello world")
    button("sweet")
    
end


--# SimpleButton
button = function(name)
    if buttonHandler.configured ==  false then
        buttonHandler.configure()
    end
    pushStyle()
    strokeWidth(3)
    fill(255, 0, 0, 0)
    local buttonColor = color(250, 250, 250, 255)
    stroke(buttonColor)
    rectMode(CENTER)
    if buttonHandler.buttons[name] == nil then
        buttonHandler.buttons[name] = {x=math.random(WIDTH),y=math.random(HEIGHT), width=WIDTH/6, height=HEIGHT/15}
    end
    buttonTable = buttonHandler.buttons[name]
    rect(buttonTable.x,buttonTable.y,buttonTable.width,buttonTable.height)
    fill(buttonColor)
    text(name, buttonTable.x, buttonTable.y)
    popStyle()
    shouldRespond = buttonHandler.touchIsInside(name)
    if CurrentTouch.state == MOVING and draggable and shouldRespond then
        buttonHandler.dragButton(name, vec2( CurrentTouch.x, CurrentTouch.y))
    elseif CurrentTouch.state == ENDED then
        buttonHandler.savePositions()
    end
end

-- buttonHandler setup
buttonHandler = {}
buttonHandler.buttons = {}
buttonHandler.configured = false
buttonHandler.configure = function ()
    parameter.boolean("draggable", false)
    buttonHandler.configured = true
end

buttonHandler.touchIsInside = function(name)
    insideX = math.abs(CurrentTouch.prevX-buttonHandler.buttons[name].x) < buttonHandler.buttons[name].width /2
    insideY = math.abs(CurrentTouch.prevY-buttonHandler.buttons[name].y) < buttonHandler.buttons[name].height /2
    if insideX and insideY then
        return true
    end
    return false
end

buttonHandler.dragButton = function (name, touch)
    buttonHandler.buttons[name].x = touch.x
    buttonHandler.buttons[name].y = touch.y
end

buttonHandler.positionOf = function (name)
    return buttonHandler.buttons[name]
end

buttonHandler.savePositions = function (name, position)
    dataString = ""
    for name, buttonValues in pairs(buttonHandler.buttons) do
        dataString = dataString.."buttonHandler.buttons['"..name.."'] = {x = "..buttonValues.x
        dataString = dataString..", y = "..buttonValues.y
        dataString = dataString..", width=WIDTH/6, height=HEIGHT/15}\
"
    end
    saveProjectTab("ButtonTables",dataString)
end

--# ButtonTables
buttonHandler.buttons['sweet'] = {x = 148.0, y = 493.5, width=WIDTH/6, height=HEIGHT/15}
buttonHandler.buttons['hello world'] = {x = 148.0, y = 393.5, width=WIDTH/6, height=HEIGHT/15}

…you all seem to be off on a much different track here, though, so I’ll leave you to it. I just wanted to give a concrete example of my idea.

Re: communication patterns, pardon me if I’m saying something everyone’s already thought about, but to me completion actions always seem the best way to preserve modularity. Apple’s delegation patterns are a nightmare to me, so I love it when I see things like:

function Parent:init()
     configurations = --...whatever
     completionAction = function()
          self:somethingThatRespondsToCompletion()
     end
     self.child = Child(configurations, completionAction)
end

function Child:init(configurations, completionAction)
     --...apply configurations
     self.completionAction = completionAction
end

function Child:importantThingForParentToKnowHasHappened()
     --...do the important thing
    self.completionAction()
end

…so the child can be completely unaware of the specifics of its parent and still notify it or change it whenever it’s necessary.

I don’t always understand everything in the discussions you guys have, so I’m never totally sure I’m not being stupid and obvious, so please pardon me if that’s the case here.

@UberGoober thanks a lot for your suggestions and code examples! Everyone idea is welcome, and even more when it comes with code examples.

@uberGoober your suggestion for communication is option 2/ listed by yojimbo2000. Maybe this is the best way to go. My first choice was not passing closures, because 1/ they are long to create 2/ i fear they will prevent dead objects to be garbage-collected, but this may not be relevant for buttons.

…sorry this is off- topic, but I hope it’s a simple thing for someone: can anyone spot why my buttons change shape after the first time they load? The first time I play the project they are a certain height, then if I hit the refresh button they get thinner. I can’t see why–is it obvious to any of y’all?

when codea is compiling the program, the HEIGHT=1024, and after setup() is running it is 768, hence the change. There are a few tricks with WIDTH and HEIGHT on startup. Best would be to put button data into a function, and call this function to update them from setup, rather than during the compilation phase.
Nota: i found that by adding print(Height) in the button data tab.

Thanks @Jmv38! …it would also work to hard-code the default values, instead of making them relative to screen dimensions, right?

Since there seems to be a revival of the inetrest for buttons, here is what i have in mind:

-- spec for button library

-- the library adds 2 global objects to your project:
--      - Button: the button class
--      - screen: a predfined Button that fits the screen and contains everything

-- ##############  minimum requirement for use   ################

function setup()
    -- your first button: this is the minimum you have to do to create a useful button:
    bt = Button() -- this will create and show the button, but you also want ...
    bt.text:set("Tap to see more...") -- the button to have a text on it ...
    bt.sensor:onTap( callback_function ) -- and a function to call when you tap it
    bt:set {x=0.25, y=0.4, w=0.5, h=0.2} -- and a way to place it somewhere relative to screen
end

-- you also have to put these lines in draw, touched and orientationChanged to have all automatic
-- this is done only once, no matter how many buttons you have
function draw()
    background(0)
    screen:draw()
end
function touched(t)
    screen:touched(t)
end
function orientationChanged(o)
    screen:update()
end
-- This last one is already in the library so you don't have to rewrite it if you dont want to change it.
-- Actually, the 3 functions are in the library, so if everything is triggered by buttons (as it should be)
-- you dont even need to re-write those 3 functions.

-- ##############  more complex/usefull functions   ################

-- this was simple, but not very useful for real projects. Let's see more:

function examples()
    
    -- setting the parent button: 
    -- it is necessary to manage groups of buttons
    -- the parent triggers children draw, touched, update etc...
    -- the default parent button of a button is the screen button. It can be changed by
    bt.family:setParent( another_button )  
    -- the parent is used as the default reference for relative sizing, positionning, etc...
    -- all parent/sibling/children relationships can be accessed via the bt.family object (more functions)

    -- setting the size:
    -- a more dedicated object is available to set button size:
    bt.size:setHeight(70)    -- sets the height in pixels (integer parameter)
    bt.size:setWidth(1.0)    -- sets the width as relative to parent width (float parameter)
    bt.size:setWidth(1.0, this_button)   -- the reference for width can be another button
    bt.size:setWidth(1.0):setHeight(70)  -- commands can be chained for compactness
    bt.size:fit( another_button )        -- same size 
    bt.size:style{ margin=10 }  -- all fancy style parameters are passed in a table 
    bf.size:update() -- for efficiency, changes occur when this (or bt:update() ) is called
    -- all size data can be accessed via the bt.size object (size:get(),...)
    
    -- setting the position:
    -- the same rules hold for positionning: integer/float, chaining, style, update
    bt.position:myTop():onTopOf(panel1) -- define the reference for aligning, or ...
    bt.position:myLeft():onLeftOf()     -- use the parent as defaut reference 
    bt.position:style{xOffset=10, yOffset=0.1}  -- offsets can be defined, absolute or relative
    bt.position:update()    -- for efficiency, changes occur when this (or bt:update() ) is called
    -- horizontal alignment: myLeft|myRight|myCenter and onLeftOf|onRightOf|onCenterOf
    -- vertical alignment: myTop|myBot|myCenter and onTopOf|onBotOf|onCenterOf
    -- all position data can be accessed via the bt.position object (position:get(),...)
    
    -- setting a skin:
    -- all draw() is done by the skin object. There are some ready-to-use skins:
    bt.skin:rect()
    bt.skin:ellipse()
    bt.skin:roundedRect()
    bt.skin:directionPanel()
    bt.skin:invisible()     -- for buttons used as containers for other buttons (ex: menu bars)
    bt.skin:rect():style{color=color(119, 22, 175, 255)  , stroke=noStroke} -- setting style parameters
    bt.skin:update()    -- for efficiency, changes occur when this (or bt:update() ) is called
    -- new skins are easy to add
    -- drawing is 2D
    -- drawing order is first in - first draw, but a priority can be set to change this
    
    -- setting the text:
    bt.text:set("my text") 
    bt.text:style{font="AmericanTypewriter", fontSize=12, fill=color(255)  }
    
    -- getting the touches:
    bt.sensor:onTap( callback )     -- when tapped, callback(touch) is called
    bt.sensor:onTouch( callback, ... )  -- when touched, callback(touch,...) is called
    -- onTap | onTouch | onLongPress | onPress | onSwipeRight | onSwipeLeft | ... |
    -- all currently alive touches concerning bt can be accessed via bt.sensor
    -- new gestures are easy to add
    -- touch are blocked by buttons
    -- touch order is first in - last touched, but a priority can be set to change this
end

Opinions welcome