Created
November 9, 2017 03:36
-
-
Save DolenzSong/4766112ebc5e5db8f85d5debcb0ae237 to your computer and use it in GitHub Desktop.
Codea's built-in UI project jiggered into a single-line-command system, like ellipse(...)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--# Main | |
-- User Interface | |
-- Use this function to perform your initial setup | |
function setup() | |
print("Hello World!") | |
yelpFunction = function() print("yeeeeeowch!") end | |
end | |
-- This function gets called once every frame | |
function draw() | |
-- This sets a dark background color | |
background(40, 40, 50) | |
basicButton("howdy", 100, 50, WIDTH/2, HEIGHT - 60, yelpFunction) | |
basicButton("HERE ARE", 300, 100, WIDTH/4.5, HEIGHT - 170, yelpFunction) | |
basicButton("a", 50, 50, WIDTH/1.45, HEIGHT - 150, yelpFunction) | |
basicButton("LOT", 500, 70, WIDTH/8, HEIGHT - 250, yelpFunction) | |
basicButton("of", 400, 400, 300, HEIGHT - 665, yelpFunction) | |
basicButton("DIFFERENT", 300, 50, 10, HEIGHT - 720, yelpFunction) | |
basicButton("buttons", 300, 50, 320, HEIGHT - 720, yelpFunction) | |
basicButton("MADE", 110, 50, 630, HEIGHT - 720, yelpFunction) | |
basicButton("with", 150, 30, 10, HEIGHT - 770, yelpFunction) | |
basicButton("SINGLE", 300, 30, 170, HEIGHT - 770, yelpFunction) | |
basicButton("commands", 250, 45, 475, HEIGHT - 770, yelpFunction) | |
end | |
--# Panel | |
-- ** Panel ** | |
ui = {} | |
ui.LEFT = 1 | |
ui.CENTER = 2 | |
ui.RIGHT = 3 | |
ui.TOP = 4 | |
ui.BOTTOM = 5 | |
ui.STRETCH = 6 | |
ui.panel = class() | |
function ui.panel:init(params) | |
assert(params) | |
local x = params.x or 0 | |
local y = params.y or 0 | |
local w = params.w or 200 | |
local h = params.h or 100 | |
self.frame = {x = 0, y = 0, w = 0, h = 0} | |
self.tframe = {x = 0, y = 0, w = 0, h = 0} | |
self.pivot = params.pivot or vec2(0.5, 0.5) | |
self.anchor = vec2(0,0) | |
self.size = vec2(1,1) | |
if params.parent then | |
params.parent:addChild(self) | |
end | |
self:setFrame(x,y,w,h) | |
self.anchor = params.anchor or self.anchor | |
self.size = params.size or self.size | |
self.fill = params.fill or color(76, 76, 76, 255) | |
self.interactive = false | |
self.visible = true | |
self.opaque = params.opaque and true | |
self.needsLayout = false | |
self.children = {} | |
self.align = params.align or {h = ui.LEFT, v = ui.BOTTOM} | |
self.border = params.border or 0 | |
self.inset = params.inset or 10 | |
if params.bg then | |
x,y,w,h = self:getFrame() | |
self.bg = ui.image | |
{ | |
parent = self, | |
size = vec2(1-(2*self.border/w),1-(2*self.border/h)), | |
anchor = vec2(0.5, 0.5), | |
image=params.bg, | |
fillMode = IMAGE_STRETCH, | |
inset = params.inset, | |
align = {h = ui.STRETCH, v = ui.STRETCH} | |
} | |
end | |
self:update() | |
end | |
function ui.panel:draw() | |
if not self.visible then return end | |
if self.bg then | |
self.bg.fill = self.fill | |
end | |
pushStyle() pushMatrix() | |
translate(self.frame.x, self.frame.y) | |
for k,v in pairs(self.children) do | |
v:draw() | |
end | |
popStyle() popMatrix() | |
end | |
-- Layout and positioning | |
function ui.panel:update() | |
-- Check is parent size has changed | |
local pw, ph = WIDTH, HEIGHT | |
if self.parent then | |
pw = self.parent.frame.w | |
ph = self.parent.frame.h | |
end | |
-- Adjust width and height depending on stretching | |
if pw ~= self.pw then | |
local w = self.size.x * self.pw | |
local x = self.anchor.x * self.pw - self.pivot.x * w | |
if self.align.h == ui.LEFT then | |
x = x + self.pivot.x * w | |
self.anchor.x = x / pw | |
elseif self.align.h == ui.CENTER then | |
x = x - self.pw/2 + pw/2 + self.pivot.x * w | |
self.anchor.x = x / pw | |
elseif self.align.h == ui.RIGHT then | |
x = x - self.pw + pw + self.pivot.x * w | |
self.anchor.x = x / pw | |
elseif self.align.h == ui.STRETCH then | |
local left = x | |
local right = x - self.pw + pw + w | |
w = right - left | |
self.anchor.x = (left + self.pivot.x * w) / pw | |
end | |
self.size.x = w / pw | |
self.pw = pw | |
end | |
if ph ~= self.ph then | |
local h = self.size.y * self.ph | |
local y = self.anchor.y * self.ph - self.pivot.y * h | |
if self.align.v == ui.BOTTOM then | |
y = y + self.pivot.y * h | |
self.anchor.y = y / ph | |
elseif self.align.v == ui.CENTER then | |
y = y - self.ph/2 + ph/2 + self.pivot.y * h | |
self.anchor.y = y / ph | |
elseif self.align.v == ui.TOP then | |
y = y - self.ph + ph + self.pivot.y * h | |
self.anchor.y = y / ph | |
elseif self.align.v == ui.STRETCH then | |
local left = y | |
local right = y - self.ph + ph + h | |
h = right - left | |
self.anchor.y = (left + self.pivot.y * h) / ph | |
end | |
self.size.y = h / ph | |
self.ph = ph | |
end | |
if self.parent then | |
self.frame.w = self.parent.frame.w * self.size.x | |
self.frame.h = self.parent.frame.h * self.size.y | |
self.frame.x = self.anchor.x * self.parent.frame.w - self.frame.w * self.pivot.x | |
self.frame.y = self.anchor.y * self.parent.frame.h - self.frame.h * self.pivot.y | |
self.tframe.x = self.parent.tframe.x + self.frame.x | |
self.tframe.y = self.parent.tframe.y + self.frame.y | |
else | |
self.frame.w = WIDTH * self.size.x | |
self.frame.h = HEIGHT * self.size.y | |
self.frame.x = self.anchor.x * WIDTH - self.frame.w * self.pivot.x | |
self.frame.y = self.anchor.y * HEIGHT - self.frame.h * self.pivot.y | |
self.tframe.x = self.frame.x | |
self.tframe.y = self.frame.y | |
end | |
self.tframe.w = self.frame.w | |
self.tframe.h = self.frame.h | |
if self.needsLayout and self.layout then | |
self:layout() | |
self.needsLayout = false | |
end | |
for k,v in pairs(self.children) do | |
v:update() | |
end | |
end | |
function ui.panel:setFrame(x,y,w,h) | |
local pw, ph = WIDTH, HEIGHT | |
if self.parent then | |
pw = self.parent.frame.w | |
ph = self.parent.frame.h | |
end | |
self.anchor = vec2((x + self.pivot.x * w) / pw, (y + self.pivot.y * h) / ph) | |
self.size = vec2(w / pw, h / ph) | |
self.pw = pw | |
self.ph = ph | |
self.frame.x = x | |
self.frame.y = y | |
self.frame.w = w | |
self.frame.h = h | |
end | |
function ui.panel:getFrame() | |
if self.parent then | |
local px,py,pw,ph = self.parent:getFrame() | |
local w = self.size.x * pw | |
local h = self.size.y * ph | |
local x = self.anchor.x * pw - self.pivot.x * w | |
local y = self.anchor.y * ph - self.pivot.y * h | |
return x,y,w,h | |
else | |
local w = self.size.x * WIDTH | |
local h = self.size.y * HEIGHT | |
local x = self.anchor.x * WIDTH - self.pivot.x * w | |
local y = self.anchor.y * HEIGHT - self.pivot.y * h | |
return x,y,w,h | |
end | |
end | |
function ui.panel:top(inside) | |
local value = 0 | |
if inside then | |
for k,v in pairs(self.children) do | |
value = math.max(value, self.tframe.y + v.frame.y + v.frame.h) | |
end | |
else | |
value = self.tframe.y + self.tframe.h | |
end | |
return value | |
end | |
function ui.panel:bottom(inside) | |
local value = self.tframe.y + self.tframe.h | |
if inside then | |
for k,v in pairs(self.children) do | |
value = math.min(value, self.tframe.y + v.frame.y) | |
end | |
else | |
value = self.tframe.y | |
end | |
return value | |
end | |
function ui.panel:right(inside) | |
local value = 0 | |
if inside then | |
for k,v in pairs(self.children) do | |
value = math.max(value, self.tframe.x + v.frame.x + v.frame.w) | |
end | |
else | |
value = self.tframe.x + self.tframe.w | |
end | |
return value | |
end | |
function ui.panel:layoutHorizontal(spacing, stretch) | |
if stretch then | |
local width = (self.frame.w - spacing * (#self.children+1)) / #self.children | |
local x = spacing | |
for k,v in pairs(self.children) do | |
v.frame.x = x | |
v.frame.w = width | |
x = x + width + spacing | |
v.needsLayout = true | |
end | |
else | |
local x = spacing | |
for k,v in pairs(self.children) do | |
v.frame.x = x | |
x = x + v.frame.w + spacing | |
v.needsLayout = true | |
end | |
end | |
self:update() | |
end | |
function ui.panel:layoutVertical(spacing, stretch) | |
if stretch then | |
local height = (self.frame.h - spacing * (#self.children+1)) / #self.children | |
local y = self.frame.h - spacing | |
for k,v in pairs(self.children) do | |
v.frame.y = y - v.frame.h | |
v.frame.h = height | |
y = y - height - spacing | |
v.needsLayout = true | |
end | |
else | |
local y = self.frame.h - spacing | |
for k,v in pairs(self.children) do | |
v.frame.y = y - v.frame.h | |
y = y - v.frame.h - spacing | |
v.needsLayout = true | |
end | |
end | |
self:update() | |
end | |
-- Hierarchy | |
function ui.panel:addChild(child) | |
local x,y,w,h = child:getFrame() | |
child.parent = self | |
child:setFrame(x,y,w,h) | |
table.insert(self.children, child) | |
end | |
-- Interaction | |
function ui.panel:hitTest(x,y) | |
return x >= self.tframe.x and x <= self.tframe.x + self.tframe.w and | |
y >= self.tframe.y and y <= self.tframe.y + self.tframe.h | |
end | |
function ui.panel:touched(touch) | |
return self.interactive and self.visible and self:hitTest(touch.x, touch.y) | |
end | |
ui.swatch = class(ui.button) | |
function ui.swatch:init(x,y,w,h,c) | |
ui.button.init(self,x,y,w,h) | |
self.color = c | |
end | |
function ui.swatch:draw() | |
if not self.visible then return end | |
pushStyle() | |
pushMatrix() | |
translate(self.frame.x, self.frame.y) | |
ellipseMode(CORNER) | |
fill(self.color) | |
if self.selected then | |
ellipse(0,0,self.frame.w,self.frame.h) | |
else | |
ellipse(5,5,self.frame.w-10, self.frame.h-10) | |
end | |
popMatrix() | |
popStyle() | |
end | |
function rRect(w,h,r,c) | |
strokeWidth(0) | |
fill(c.r, c.g, c.b, c.a) | |
ellipse(r/2,h-r/2,r) ellipse(w-r/2,h-r/2,r) | |
ellipse(r/2,r/2,r) ellipse(w-r/2,r/2,r) | |
rect(0,r/2,w,h-r) rect(r/2,0,w-r,h) | |
end | |
--# Label | |
-- ** Label ** | |
ui.label = class(ui.panel) | |
function ui.label:init(params) | |
ui.panel.init(self, params) | |
self.text = params.text | |
self.alignment = params.alignment or CENTER | |
self.fill = params.fill or color(255, 255, 255, 255) | |
self.font = params.font or "SourceSansPro-Bold" | |
self.fontSize = params.fontSize or 16 | |
end | |
function ui.label:draw() | |
if self.visible and self.text then | |
pushStyle() | |
font(self.font) | |
textAlign(LEFT) | |
textMode(CORNER) | |
fontSize(self.fontSize) | |
local w,h = textSize(self.text) | |
fill(self.fill) | |
local cx = self.frame.x + self.frame.w * 0.5 - w * 0.5 | |
if self.alignment == LEFT then | |
cx = self.frame.x | |
end | |
local cy = self.frame.y + self.frame.h * 0.5 - h * 0.5 | |
text(self.text, cx, cy) | |
popStyle() | |
end | |
end | |
--# Image | |
-- ** Image ** | |
ui.image = class(ui.panel) | |
IMAGE_FILL = 1 | |
IMAGE_FIT = 2 | |
IMAGE_STRETCH = 3 | |
function ui.image:init(params) | |
ui.panel.init(self, params) | |
-- you can accept and set parameters here | |
self.flipX = params.flipX or false | |
self.flipY = params.flipY or false | |
self.fillMode = params.fillMode or IMAGE_FIT | |
self.fill = params.fill or color(255, 255, 255, 255) | |
self.opaque = params.opaque or false | |
self.rotation = params.rotation or 0 | |
if params.inset and type(params.inset) == "table" then | |
self.inset = params.inset | |
else | |
local i = params.inset | |
self.inset = i and {t = i, r = i, b = i, l = i} or | |
{t = 0, r = 0, b = 0, l = 0} | |
end | |
self:setImage(params.image) | |
self.model = mesh() | |
end | |
function ui.image:setImage(img) | |
self.img = img | |
end | |
function ui.image:draw() | |
if not self.visible then return end | |
ui.panel.draw(self) | |
pushStyle() pushMatrix() | |
--noFill() | |
--rect(self.frame.x, self.frame.x, self.frame.x + self.frame.w, self.frame.y + self.frame.h) | |
if self.img then | |
spriteMode(CENTER) | |
local w,h = spriteSize(self.img) | |
if self.fillMode == IMAGE_STRETCH then | |
w = self.frame.w | |
h = self.frame.h | |
elseif self.fillMode == IMAGE_FIT then | |
if self.frame.w >= self.frame.h then | |
else | |
end | |
elseif self.fillMode == IMAGE_FILL then | |
if self.frame.w >= self.frame.h then | |
else | |
end | |
end | |
-- 9 Patch Mode | |
local t,r,b,l = self.inset.t, self.inset.r, self.inset.b, self.inset.l | |
if t > 0 or r > 0 or b > 0 or l > 0 then | |
translate(self.frame.x, self.frame.y) | |
self.model:clear() | |
self.model.texture = self.img | |
local sx, sy = spriteSize(self.img) | |
-- Top | |
local i = self.model:addRect(self.frame.w/2, self.frame.h - t/2, w - l - r, t) | |
self.model:setRectTex(i, l/sx,(sy-t)/sy,(sx-l-r)/sx,t/sy) | |
-- Top Right | |
i = self.model:addRect(self.frame.w - r/2, self.frame.h - t/2, r, t) | |
self.model:setRectTex(i, (sx-r)/sx,(sy-t)/sy,r/sx,t/sy) | |
-- Right | |
i = self.model:addRect(self.frame.w - r/2, self.frame.h/2, r, h - t - b) | |
self.model:setRectTex(i, (sx-r)/sx,t/sy,r/sx,(sy - t - b)/sy) | |
-- Bottom Right | |
i = self.model:addRect(self.frame.w - r/2, b/2, r, b) | |
self.model:setRectTex(i, (sx-r)/sx,0,r/sx,b/sy) | |
-- Bottom | |
local i = self.model:addRect(self.frame.w/2, b/2, w - l - r, b) | |
self.model:setRectTex(i, l/sx,0,(sx-l-r)/sx,t/sy) | |
-- Bottom Left | |
i = self.model:addRect(l/2, b/2, l, b) | |
self.model:setRectTex(i, 0,0,l/sx,b/sy) | |
-- Left | |
i = self.model:addRect(l/2, self.frame.h/2, l, h - t - b) | |
self.model:setRectTex(i, 0,t/sy,l/sx,(sy - t - b)/sy) | |
-- Top Left | |
i = self.model:addRect(l/2, self.frame.h - t/2, l, t) | |
self.model:setRectTex(i, 0,(sy-t)/sy,l/sx,t/sy) | |
-- Middle | |
i = self.model:addRect(self.frame.w/2, self.frame.h/2, w-l-r, h-t-b) | |
self.model:setRectTex(i, l/sx,b/sy,(sx-l-r)/sx,(sy-b-t)/sy) | |
self.model:setColors(self.fill) | |
self.model:draw() | |
else | |
translate(self.frame.x + self.frame.w/2, | |
self.frame.y + self.frame.h/2) | |
rotate(self.rotation or 0) | |
if self.flipX then w = -w end | |
if self.flipY then h = -h end | |
tint(self.fill) | |
sprite(self.img, 0, 0, w, h) | |
end | |
end | |
popStyle() popMatrix() | |
end | |
--# Button | |
-- ** Button ** | |
ui.button = class(ui.panel) | |
function ui.button:init(params) | |
params.bg = params.normalBg | |
ui.panel.init(self, params) | |
self.textFill = color(255, 255, 255, 255) | |
self.selectedFill = color(29, 29, 29, 255) | |
self.normalFill = params.normalFill or color(255, 255, 255, 255) | |
self.highlightedFill = color(181, 181, 181, 255) | |
self.normalBg = params.normalBg | |
self.highlightedBg = params.highlightedBg or self.normalBg | |
self.selectedBg = params.selectedBg or self.highlightedBg or self.selectedBg | |
self.icon = ui.image | |
{ | |
anchor = vec2(0.5,0.5), | |
size = vec2(1,1), | |
align = {h = ui.STRETCH, v = ui.STRETCH}, | |
parent = self | |
} | |
self.label = ui.label | |
{ | |
text = params.text, | |
fontSize = params.fontSize | |
} | |
self:addChild(self.label) | |
self.label.anchor = vec2(0.5, 0.5) | |
self.label.size = vec2(1, 1) | |
self.label.align = {h = ui.STRETCH, v = ui.STRETCH} | |
self.highlighted = false | |
self.selected = false | |
self.onPressed = params.onPressed | |
self.onReleased = params.onReleased | |
self.interactive = true | |
if touches then touches.addHandler(self,-2) end | |
end | |
function ui.button:draw() | |
if not self.visible then return end | |
self.fill = self.highlighted and self.highlightedFill or | |
(self.selected and self.selectedFill or self.normalFill) | |
if self.bg then | |
self.bg.img = self.highlighted and self.highlightedBg or | |
(self.selected and self.selectedBg or self.normalBg) | |
end | |
self.label.fill = self.textFill | |
ui.panel.draw(self) | |
end | |
function ui.button:layout() | |
self.label.frame.w = self.frame.w - 10 | |
self.label.frame.h = self.frame.h - 10 | |
end | |
function ui.button:touched(touch) | |
if touch.state == BEGAN and ui.panel.touched(self, touch) then | |
self.highlighted = true | |
if self.onPressed then self.onPressed(self, touch) end | |
return true | |
elseif touch.state == MOVING then | |
if self:hitTest(touch.x, touch.y) then | |
self.highlighted = true | |
elseif touches then | |
if self.share then | |
touches.share(self, touch, -2) | |
end | |
self.highlighted = false | |
end | |
return true | |
elseif touch.state == ENDED or touch.state == CANCELLED then | |
self.highlighted = false | |
if self:hitTest(touch.x, touch.y) then | |
if self.onReleased then self.onReleased(self, touch) end | |
end | |
return true | |
end | |
return false | |
end | |
--# basicButton | |
function basicButton(title, w, h, x, y, callback) | |
if basicButtons == nil then | |
basicButtons = {} | |
end | |
if basicButtons[title] == nil then | |
basicButtons[title] = buildBasicFromSpecs(title, w, h, x, y, callback) | |
end | |
basicButtons[title]:update() | |
basicButtons[title]:draw() | |
end | |
function buildBasicFromSpecs(title, w, h, x, y, callback) | |
return ui.button{ | |
x=x, | |
y=y, | |
w=w, | |
h=h, | |
opaque=false, | |
text = title, | |
fontSize = 100/(HEIGHT/3.8)*h, | |
normalBg = readImage("UI:Blue Button11"), | |
highlightedBg = "UI:Blue Button10", | |
normalFill = color(255, 255, 255, 255), | |
align = {h = ui.CENTER, v = ui.CENTER}, | |
inset = 10, | |
onReleased = callback | |
} | |
end | |
--# Touches | |
----------------------------------------- | |
-- Touches | |
-- Written by John Millard | |
----------------------------------------- | |
-- Description: | |
-- A touch management class that simplifies handling multiple touch reciever. | |
----------------------------------------- | |
-- NOTE: if you like, you can delete this tab & use Touches project as a dependency | |
local TouchHandler = class() | |
function TouchHandler:init(target, priority, multiTouch) | |
assert(target ~= nil) | |
self.target = target | |
self.priority = priority or 0 | |
self.multiTouch = multiTouch or false | |
self.captured = {} | |
self.count = 0 | |
end | |
function TouchHandler:touched(touch) | |
if touch.state == BEGAN then | |
if self.multiTouch or self.count == 0 then | |
if self.target:touched(touch) then | |
self.captured[touch.id] = true | |
self.count = self.count + 1 | |
return true | |
end | |
end | |
elseif touch.state == MOVING then | |
if self.captured[touch.id] then | |
self.target:touched(touch) | |
return true | |
end | |
elseif touch.state == ENDED or touch.state == CANCELLED then | |
if self.captured[touch.id] then | |
self.target:touched(touch) | |
self.captured[touch.id] = nil | |
self.count = self.count - 1 | |
return true | |
end | |
end | |
return false | |
end | |
touches = {} | |
touches.handlers = {} | |
touches.shared = {} | |
function touches.share(target, touch, priority) | |
local fakeTouch = | |
{ | |
x = touch.x, | |
y = touch.y, | |
id = touch.id, | |
state = BEGAN, | |
tapCount = touch.tapCount, | |
deltaX = touch.deltaX, | |
deltaY = touch.deltaY | |
} | |
for k,v in pairs(touches.handlers) do | |
if v.target ~= target and v.priority == priority then | |
v:touched(fakeTouch) | |
end | |
end | |
end | |
function touches.addHandler(target, priority, multiTouch) | |
table.insert(touches.handlers, TouchHandler(target, priority, multiTouch)) | |
table.sort(touches.handlers, function(a,b) | |
return a.priority < b.priority | |
end) | |
end | |
function touches.removeHandler(target) | |
local i = nil | |
for k,v in pairs(touches.handlers) do | |
if v.target == target then | |
i = k | |
end | |
end | |
table.remove(touches.handlers, i) | |
end | |
function touches.touched(touch) | |
local captured = false | |
for k,v in pairs(touches.handlers) do | |
if v:touched(touch) then captured = true end | |
if touch.state == BEGAN and captured then | |
return true | |
end | |
end | |
return captured | |
end | |
function touched(touch) | |
touches.touched(touch) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment