I cannot catch mouse signals on widgets within a wibox.container.scroll - lua

I am constructing widgets dynamically from from the output of iw dev interface scan I then add them to a scroll box held in a wibox. The problem I find is that the mouse::enter and mouse::leave signals I connect the widgets to do not catch the mouse signals when inside the wibox.container.scroll. All the rest of the code works as intended and the signals are caught as intended if I omit the wibox.container.scroll wrapper.
The widget template to be added to the scroll container:
function wifitbox.new(ssid, screen, interface)
tbox = wibox.widget{
{
{
wibox.widget.textbox(ssid[2] .. " " .. ssid[3]),
{
wibox.widget.textbox(ssid[4]),
halign = "right",
widget = wibox.container.place
},
layout = wibox.layout.ratio.horizontal
},
margins = beautiful.xresources.apply_dpi(10, screen),
widget = wibox.container.margin
},
id = "tbox",
bg = beautiful.wifi_tbox_bg or "#928374",
shape = function(cr, width, height)
gears.shape.rounded_rect(cr, width, height, 5)
end,
widget = wibox.container.background
}
awful.spawn.easy_async('bash -c "sudo iw dev ' .. interface .. ' station dump | awk \'FNR == 1 {print($2)}\'"', function(stdout)
if(gears.string.split(stdout, "\n")[1] == ssid[1]) then
tbox.bg = "#b16286"
end
end)
tbox:connect_signal("mouse::enter", function()
tbox.bg_cache = tbox.bg
tbox.bg = "#689d6a"
end)
tbox:connect_signal("mouse::leave", function()
tbox.bg = tbox.bg_cache
end)
return tbox
end
The two :signal_connect() statements are the ones in questions.
The widgets are added to the scroll container a follows:
awful.spawn.with_line_callback(cmd, {
stdout = function(line)
local ssid = gears.string.split(line, "\t")
wifitbox_table:get_children_by_id("tbox_list")[1]:add(wifitbox(ssid, screen, interface))
end,
output_done = function()
self:set_widget(wifitbox_table)
end
})
The widget the containing the scroll container is as follows:
local wifitbox_table = wibox.widget{
scrollbtn,
{
id = "scroll_box",
speed = 100,
extra_space = beautiful.xresources.apply_dpi(5, screen),
layout = wibox.container.scroll.vertical,
step_function = wibox.container.scroll.step_functions.linear_increase,
{
id= "tbox_list",
spacing = beautiful.xresources.apply_dpi(5, screen),
layout = wibox.layout.fixed.vertical()
}
},
spacing = beautiful.xresources.apply_dpi(5, screen),
layout = wibox.layout.fixed.vertical,
}
As I mentioned before if i remove the "scroll_box" widget around the "tbox_list" widget the signals connect without issue. But then obviously I don't get scrolling.
And finally all of it put together:
local awful = require('awful')
local wibox = require('wibox')
local gears = require('gears')
local beautiful = require('beautiful')
local wifimodal = { mt = {} }
local wifitbox = { mt = {} }
local setmetatable = setmetatable
function wifitbox.new(ssid, screen, interface)
tbox = wibox.widget{
{
{
wibox.widget.textbox(ssid[2] .. " " .. ssid[3]),
{
wibox.widget.textbox(ssid[4]),
halign = "right",
widget = wibox.container.place
},
layout = wibox.layout.ratio.horizontal
},
margins = beautiful.xresources.apply_dpi(10, screen),
widget = wibox.container.margin
},
id = "tbox",
bg = beautiful.wifi_tbox_bg or "#928374",
shape = function(cr, width, height)
gears.shape.rounded_rect(cr, width, height, 5)
end,
widget = wibox.container.background,
}
awful.spawn.easy_async('bash -c "sudo iw dev ' .. interface .. ' station dump | awk \'FNR == 1 {print($2)}\'"', function(stdout)
if(gears.string.split(stdout, "\n")[1] == ssid[1]) then
tbox.bg = "#b16286"
end
end)
tbox:connect_signal("mouse::enter", function()
tbox.bg_cache = tbox.bg
tbox.bg = "#689d6a"
end)
tbox:connect_signal("mouse::leave", function()
tbox.bg = tbox.bg_cache
end)
return tbox
end
function wifitbox.mt.__call(_, ...)
return wifitbox.new(...)
end
setmetatable(wifitbox, wifitbox.mt)
function wifimodal.new(screen, interface, curSSID)
self = wibox {
screen = screen,
width = screen.geometry.width / 5,
type = 'modal',
height = screen.workarea.height/2,
x = screen.geometry.width - screen.geometry.width/5,
y = beautiful.xresources.apply_dpi(beautiful.wibar_height or 25, screen),
ontop = true,
visible = true,
bg = beautiful.bg_normal,
fg = "black",
opacity = 0.8,
shape = function(cr, width, height)
gears.shape.rounded_rect(cr, width, height, 5)
end
}
self:connect_signal('mouse::leave', function()
self.visible = false
self = nil
end)
local scrollbtn = wibox.widget{
{
{
widget = wibox.widget.imagebox,
resize = true,
image = gears.filesystem.get_configuration_dir() .. "widgets/wifi/arrow_up.png",
forced_height = beautiful.xresources.apply_dpi(25, screen),
},
widget = wibox.container.place,
valign = "center",
},
shape = function(cr, width, height)
gears.shape.rounded_rect(cr, width, height, 5)
end,
widget = wibox.container.background,
bg = beautiful.wifi_scroll_btn_bg or "#d79921",
}
scrollbtn:connect_signal("mouse::enter", function()
scrollbtn:emit_signal_recursive("scroll::continue")
scrollbtn.bg = beautiful.wifi_scroll_btn_bg_hover or "#458588"
end)
scrollbtn:connect_signal("mouse::leave", function()
scrollbtn:emit_signal_recursive("scroll::pause")
scrollbtn.bg = beautiful.wifi_scroll_btn_bg or "#d79921"
end)
local scan_awk = gears.filesystem.get_configuration_dir() .. "widgets/wifi/scan.awk"
local cmd = 'bash -c "sudo iw ' .. interface .. ' scan | awk -f ' .. scan_awk .. '"'
local wifitbox_table = wibox.widget{
scrollbtn,
{
id = "scroll_box",
speed = 100,
extra_space = beautiful.xresources.apply_dpi(5, screen),
layout = wibox.container.scroll.vertical,
step_function = wibox.container.scroll.step_functions.linear_increase,
{
id= "tbox_list",
spacing = beautiful.xresources.apply_dpi(5, screen),
layout = wibox.layout.fixed.vertical()
}
},
spacing = beautiful.xresources.apply_dpi(5, screen),
layout = wibox.layout.fixed.vertical,
}
local scrollbox = wifitbox_table:get_children_by_id("scroll_box")[1]
scrollbox:pause()
wifitbox_table:connect_signal("scroll::continue", function()
scrollbox:continue()
end)
wifitbox_table:connect_signal("scroll::pause", function()
scrollbox:pause()
end)
awful.spawn.with_line_callback(cmd, {
stdout = function(line)
local ssid = gears.string.split(line, "\t")
wifitbox_table:get_children_by_id("tbox_list")[1]:add(wifitbox(ssid, screen, interface))
end,
output_done = function()
self:set_widget(wifitbox_table)
end
})
return self
end
function wifimodal.mt.__call(_, ...)
return wifimodal.new(...)
end
return setmetatable(wifimodal, wifimodal.mt)
Here is a screen shot for some context:
Also, if you don't have the answer but notice something else you can critic feel free. Thank you

The problem I find is that the mouse::enter and mouse::leave signals I connect the widgets to do not catch the mouse signals when inside the wibox.container.scroll
The third sentence on https://awesomewm.org/doc/api/classes/wibox.container.scroll.html is:
Please note that mouse events do not propagate to widgets inside of the scroll container.
So, this is "working as intended".
Also: https://github.com/awesomeWM/awesome/issues/3076

Related

Highcharts sankey node without links

I have a highcharts sankey diagram with two sides:
There are situations where some of my nodes have empty links (=with 0 weight). I would like the node to being displayed despite having no link from or to it.
Any chance I can achieve this?
I read on this thread that I have to fake it with weight=1 connexions, I could make the link transparent, and twitch the tooltip to hide those, but that's very painful for something that feels pretty basic.
Maybe a custom call of the generateNode call or something?
Thanks for the help
You can use the following wrap to show a node when the weight is 0.
const isObject = Highcharts.isObject,
merge = Highcharts.merge
function getDLOptions(
params
) {
const optionsPoint = (
isObject(params.optionsPoint) ?
params.optionsPoint.dataLabels : {}
),
optionsLevel = (
isObject(params.level) ?
params.level.dataLabels : {}
),
options = merge({
style: {}
}, optionsLevel, optionsPoint);
return options;
}
Highcharts.wrap(
Highcharts.seriesTypes.sankey.prototype,
'translateNode',
function(proceed, node, column) {
var translationFactor = this.translationFactor,
series = this,
chart = this.chart,
options = this.options,
sum = node.getSum(),
nodeHeight = Math.max(Math.round(sum * translationFactor),
this.options.minLinkWidth),
nodeWidth = Math.round(this.nodeWidth),
crisp = Math.round(options.borderWidth) % 2 / 2,
nodeOffset = column.sankeyColumn.offset(node,
translationFactor),
fromNodeTop = Math.floor(Highcharts.pick(nodeOffset.absoluteTop, (column.sankeyColumn.top(translationFactor) +
nodeOffset.relativeTop))) + crisp,
left = Math.floor(this.colDistance * node.column +
options.borderWidth / 2) + Highcharts.relativeLength(node.options.offsetHorizontal || 0,
nodeWidth) +
crisp,
nodeLeft = chart.inverted ?
chart.plotSizeX - left :
left;
node.sum = sum;
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
if (1) {
// Draw the node
node.shapeType = 'rect';
node.nodeX = nodeLeft;
node.nodeY = fromNodeTop;
let x = nodeLeft,
y = fromNodeTop,
width = node.options.width || options.width || nodeWidth,
height = node.options.height || options.height || nodeHeight;
if (chart.inverted) {
x = nodeLeft - nodeWidth;
y = chart.plotSizeY - fromNodeTop - nodeHeight;
width = node.options.height || options.height || nodeWidth;
height = node.options.width || options.width || nodeHeight;
}
// Calculate data label options for the point
node.dlOptions = getDLOptions({
level: (this.mapOptionsToLevel)[node.level],
optionsPoint: node.options
});
// Pass test in drawPoints
node.plotX = 1;
node.plotY = 1;
// Set the anchor position for tooltips
node.tooltipPos = chart.inverted ? [
(chart.plotSizeY) - y - height / 2,
(chart.plotSizeX) - x - width / 2
] : [
x + width / 2,
y + height / 2
];
node.shapeArgs = {
x,
y,
width,
height,
display: node.hasShape() ? '' : 'none'
};
} else {
node.dlOptions = {
enabled: false
};
}
}
);
Demo:
http://jsfiddle.net/BlackLabel/uh6fp89j/
In the above solution, another node arrangement would be difficult to achieve and may require a lot of modifications beyond our scope of support.
You can consider using mentioned "tricky solution", since might return a better positioning result. This solution is based on changing 0 weight nodes on the chart.load() event and converting the tooltip as well, so it may require adjustment to your project.
chart: {
events: {
load() {
this.series[0].points.forEach(point => {
if (point.weight === 0) {
point.update({
weight: 0.1,
color: 'transparent'
})
}
})
}
}
},
tooltip: {
nodeFormatter: function() {
return `${this.name}: <b>${Math.floor(this.sum)}</b><br/>`
},
pointFormatter: function() {
return `${this.fromNode.name} → ${this.toNode.name}: <b>${Math.floor(this.weight)}</b><br/>`
}
},
Demo:
http://jsfiddle.net/BlackLabel/0dqpabku/

BottomNavigationItems padding

Is there any way to remove this padding from the BottomNavigationItem?
Image
If I have very large text, I have to use ResponsiveText to manage this, but that's not what I intended. What I need is that it doesn't have that side padding/margin, both on the left and on the right, in order to occupy as much space as possible.
My code:
#Composable
fun BottomNavBar(
backStackEntryState: State<NavBackStackEntry?>,
navController: NavController,
bottomNavItems: List<NavigationItem>
) {
BottomNavigation(
backgroundColor = DarkGray.copy(alpha = 0.6f),
elevation = Dimen0,
modifier = Modifier
.padding(Dimen10, Dimen20, Dimen10, Dimen20)
.clip(RoundedCornerShape(Dimen13, Dimen13, Dimen13, Dimen13))
) {
bottomNavItems.forEach { item ->
val isSelected = item.route == backStackEntryState.value?.destination?.route
BottomNavigationItem(
icon = {
Icon(
painter = painterResource(id = item.icon.orZero()),
contentDescription = stringResource(id = item.title)
)
},
label = {
ResponsiveText(
text = stringResource(id = item.title),
textStyle = TextStyle14,
maxLines = 1
)
},
selectedContentColor = Color.White,
unselectedContentColor = Color.White,
alwaysShowLabel = true,
selected = isSelected,
onClick = {
navController.navigate(item.route) {
navController.graph.startDestinationRoute?.let { route ->
popUpTo(route = route) {
saveState = true
}
}
launchSingleTop = true
restoreState = true
}
},
modifier = if (isSelected) {
Modifier
.clip(RoundedCornerShape(Dimen13, Dimen13, Dimen13, Dimen13))
.background(color = DarkGray)
} else {
Modifier.background(color = Color.Unspecified)
}
)
}
}
}
Apparently this is currently (I am using compose version '1.2.0-rc03') not possible when using BottomNavigation, as there is padding set for each element in these lines:
.padding(horizontal = BottomNavigationItemHorizontalPadding)
Here is what is said about this value:
/**
* Padding at the start and end of a [BottomNavigationItem]
*/
private val BottomNavigationItemHorizontalPadding = 12.dp
[Solution]
Just copy BottomNavigation from androidx and remov this line:
.padding(horizontal = BottomNavigationItemHorizontalPadding)
However, it is necessary that the first and last elements still have padding, so add the innerHorizontalPaddings parameter to the your CustomBottomNavigation constructor
There are a few more changes, you can see the full code of my CustomBottomNavigation here
Example of usage:
CustomBottomNavigation(
...,
innerHorizontalPaddings = 12.dp
) {
items.forEach { item ->
BottomNavigationItem(
icon = {
Icon(...)
},
label = {
Text(
...
softWrap = false,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(horizontal = 2.dp)
)
},
...
)
}
}
Another solution is to wrap the label in a BoxWithConstraints and draw outside of it:
BottomNavigationItem(
label = {
/**
* Because of [BottomNavigationItemHorizontalPadding] (12.dp), we need to
* think (and draw) outside the box.
*/
BoxWithConstraints {
Text(
modifier = Modifier
.wrapContentWidth(unbounded = true)
.requiredWidth(maxWidth + 24.dp), // 24.dp = the padding * 2
text = "Centered text and clipped at the end if too long",
softWrap = false,
textAlign = TextAlign.Center
)
}
},
...
)
To get a little bit of padding, you can set requiredWidth(maxWidth + 18.dp).
With this solution, you don't need to copy the enire BottomNavigation :)

for loop help and sprite addition

I have two questions. what I am
trying to do is every time I shoot a enemy/vine it should be
removed and a explosion should occur. the removal works perfect
but the sprite is not called to explode
What's this in the for loop #sections[sectInt]["vines"] ?
Are they parent/ child references? Can someone break this
down to the letter even telling me what the # is?
How can I use my explosion sprite after each vine is destroyed?
I am having a difficult time figuring out how to call every x and y
vine in the for loop to explode when removed.
Code:
local sections = require("sectionData")
local lastSection = 0
function createSection()
--Create a random number. If its eqaul to the last one, random it again.
local sectInt = mR(1,#sections)
if sectInt == lastSection then sectInt = mR(1,#sections) end
lastSection = sectInt
--Get a random section from the sectionData file and then
--Loop through creating everything with the right properties.
local i
-- the random creation of vines throughout the screen
for i=1, #sections[sectInt]["vines"] do
local object = sections[sectInt]["vines"][i]
local vine = display.newImageRect(objectGroup, "images/vine"..object["type"]..".png", object["widthHeight"][1], object["widthHeight"][2])
vine.x = object["position"][1]+(480*object["screen"]); vine.y = object["position"][2]; vine.name = "vine"
local rad = (vine.width*0.5)-8; local height = (vine.height*0.5)-8
local physicsShape = { -rad, -height, rad, -height, rad, height, -rad, height }
physics.addBody( vine, "static", { isSensor = true, shape = physicsShape } )
end
end
-- explosion sprite
options1 =
{
width = 96, height = 96,
numFrames = 16,
sheetContentWidth = 480,
sheetContentHeight = 384
}
playerSheet1 = graphics.newImageSheet( "images/explosion.png", options1)
playerSprite1 = {
{name="explosion", start=1, count=16, time = 400, loopCount = 1 },
}
explode = display.newSprite(playerSheet1, playerSprite1)
explode.anchorX = 0.5
explode.anchorY = 1
--player:setReferencePoint(display.BottomCenterReferencePoint)
-- i want to reference the for loop position if each vine so it plays sprite when they are removed
explode.x = "vine.x" ; explode.y = "vine .y"
explode.name = "explode"
explode.position=1
extraGroup:insert(explode)
1) What's this in the for loop #sections[sectInt]["vines"] ? Are they parent/ child references? Can someone break this down to the letter even telling me what the # is?
As I said in my comment the # is table length.
The loop is looping over each bit of "vine" data in the selected segment data (whatever that means exactly in terms of the game) and then creates the objects for those vines.
When it's time to make your vine explode, you play the sprite. If you want to explode every vine, you would have something like:
sheetOptions =
{
width = 96, height = 96,
numFrames = 16,
sheetContentWidth = 480,
sheetContentHeight = 384
}
playerSheet = graphics.newImageSheet( "images/explosion.png", sheetOptions)
spriteSequence = {
{name="explosion", start=1, count=16, time = 400, loopCount = 1 },
}
for i, vine in ipairs(objectGroup) do
local explode = display.newSprite(playerSheet, spriteSequence)
explode.anchorX = 0.5
explode.anchorY = 1
explode.x = vine.x
explode.y = vine.y
explode.name = "explode"
explode.position=1
explode:play()
extraGroup:insert(explode)
end
Note: not tested, let me know of if any issues you can't resolve.
ok so, this is what i did to get my object to have a explosion, for anybody that is having the same issue.
function isShot( event )
print('shot!!!')
if (event.other.class == "enemy") then
if (event.other.name == "limitLine") then
event.target:doRemove() --remove bullet if hits the limitLine
elseif (event.other.name == "vine") then
event.other:doRemove()
spriteExplode(event.other.x, event.other.y) -- this function calls itself & runs the sprite
if event.other.name == "vine" then
if event.other.name == "explode" then
event.other:doRemove() --this removes the explosion sprite
end
end
end
-- remove the bullet & explosion sprite
timer.performWithDelay( 5000, function(event) explode:doRemove(); end, 1 )
event.target:doRemove()
end
return true
end
function spriteExplode( x, y)
explode.isVisible = true
explode:play()
print("play explode")
if (x and y) then -- this is the code that keeps my sprite updating upon removal of vine
explode.x, explode.y = x, y
end
function explode:doRemove(event)
timer.performWithDelay(
1,
function(event)
display.remove(self)
self= nil
end,
1 )
end
end
i added the isShot function eventListener inside of the Bullet function
bullet:addEventListener("collision", isShot) & a bullet:doRemove function is inside the bullet function as well.
hope this helps.

How can I access a lua file with a tab bar?

I am having an error when trying to access a file with a tab bar constructed within it. Does anyone know what I am doing wrong. Corona SDK tells me my error is in the section of code where I actually create the tab bar, not the buttons. Thanks
Below is my code for the MainPage.lua scene in which the tab bar is created.
local tabBar = nil
function scene: create (event)
local group = scene.view
local tabButtons =
{
{
width = 20,
height = 32,
defaultFile = "assets/home.png",
overFile = "assets/PressHome.png",
label = "Home",
font = tabLabelFont,
size = tabLabelFontSize,
onPress = function() composer.gotoScene( "Home" ); end,
},
{
width = 20,
height = 32,
defaultFile = "assets/Explore.png",
overFile = "assets/Explore.png",
label = "Explore",
font = tabLabelFont,
size = tabLabelFontSize,
onPress = function() composer.gotoScene( "Explore" ); end,
},
{
width = 20,
height = 32,
defaultFile = "assets/Post.png",
overFile = "assets/Post.png",
label = "Post",
font = tabLabelFont,
size = tabLabelFontSize,
onPress = function() composer.gotoScene( "Post" ); end,
},
{
width = 20,
height = 32,
defaultFile = "assets/Notification.png",
overFile = "assets/Notification.png",
label = "Notification",
font = tabLabelFont,
size = tabLabelFontSize,
onPress = function() composer.gotoScene( "Notification" ); end,
},
{
width = 20,
height = 32,
defaultFile = "assets/Profile.png",
overFile = "assets/Profile.png",
label = "Profile",
font = tabLabelFont,
size = tabLabelFontSize,
onPress = function() composer.gotoScene( "Profile" ); end,
}
}
-- Create a tab-bar and place it at the bottom of the screen
tabBar = widget.newTabBar
{
top = display.contentHeight - 50,
width = display.contentWidth,
backgroundFile = "assets/tabbar.png",
tabSelectedLeftFile = "assets/tabBar_tabSelectedLeft.png",
tabSelectedMiddleFile = "assets/tabBar_tabSelectedMiddle.png",
tabSelectedRightFile = "assets/tabBar_tabSelectedRight.png",
tabSelectedFrameWidth = 20,
tabSelectedFrameHeight = 52,
buttons = tabButtons
}
group: insert(tabBar)
composer.gotoScene("Home")
end
scene:addEventListener( "create", scene )
return scene
here is the method from the log in page that is supposed to access the tab bar page.
local function EnterPage( event )
composer.gotoScene("MainPage")
end
this is what the error is saying
stack traceback:
?: in function '?'
?: in function <?:703>
(tail call): ?
?: in function <?:122>
(tail call): ?
MainPage.lua:127: in main chunk
[C]: in function 'require'
?: in function <?:797>
(tail call): ?
LogIn.lua:78: in function <LogIn.lua:70>
?: in function <?:218>
line 127 of Main Page is the line that states "tabBar = widget.newTabBar"
The errors in the log in page just go to the gotoScene(MainPage) method
"File: ? Attempt to index a nil value" is very helpful: it says that you are doing an operation of the form a.b like a.b = something or a.b() where a is nil. Given the line it happens on, this is surely because you forgot
widget = require 'widget'
near the top of your file.
If that line is already there (in MainPage.lua), then check that widget hasn't been overwritten:
print('widget is', widget)
tabBar = widget.newTabBar
...
If it is not nil then you have made an error in the information you posted (wrong line number, etc) or maybe you tried many things and info got mixed up. You could try, just to be sure,
widget = require 'widget'
tabBar = widget.newTabBar
If that works, then you know you are overwriting widget somewhere in your MainPage.lua, in code not shown. You should also try
test = widget.newButton
{
left = 100, top = 200,
label = "Default",
onEvent = function() print('hi') end
}
If that works, but the newTabBar still doesn't work, try replacing your options with those of the example on corona newTabBar site.

lua table constructor

how do you make a default table and then use it when making other tables?
example
--default table
Button = {
x = 0,
y = 0,
w = 10,
h = 10,
Texture = "buttonimg.png",
onClick = function() end
}
newbutton = Button {
onClick = function()
print("button 1 pressed")
end
}
newbutton2 = Button {
x = 12,
onClick = function()
print("button 2 pressed")
end
}
newbuttons will get y, w, h and texture set to default value but anything set in the brackets get overwritten
You can achieve what you want by merging Doug's answer with your original scenario, like this:
Button = {
x = 0,
y = 0,
w = 10,
h = 10,
Texture = "buttonimg.png",
onClick = function() end
}
setmetatable(Button,
{ __call = function(self, init)
return setmetatable(init or {}, { __index = Button })
end })
newbutton = Button {
onClick = function()
print("button 1 pressed")
end
}
newbutton2 = Button {
x = 12,
onClick = function()
print("button 2 pressed")
end
}
(I actually tested this, it works.)
Edit: You can make this a bit prettier and reusable like this:
function prototype(class)
return setmetatable(class,
{ __call = function(self, init)
return setmetatable(init or {},
{ __index = class })
end })
end
Button = prototype {
x = 0,
y = 0,
w = 10,
h = 10,
Texture = "buttonimg.png",
onClick = function() end
}
...
If you set the new table's metatable's __index to point to Button it will use the default values from the Button table.
--default table
Button = {
x = 0,
y = 0,
w = 10,
h = 10,
Texture = "buttonimg.png",
onClick = function() end
}
function newButton () return setmetatable({},{__index=Button}) end
Now when you make buttons with newButton() they use the default values from the Button table.
This technique can be used for class or prototype object oriented programming. There are many examples here.

Resources