From de4b1798ed58f4f5cf1f448ef38c4e3886ac1590 Mon Sep 17 00:00:00 2001 From: Jim Bauwens Date: Fri, 13 Mar 2015 11:28:34 +0100 Subject: [PATCH] Core widget logic Also changes to lambda! IMPORTANT You now have -> (no return) and => (return) --- ETK/etk.lua | 90 ++- ETK/eventmanager.lua | 2 + ETK/graphics.lua | 19 +- ETK/helpers.lua | 61 +- ETK/screenmanager.lua | 158 ++---- ETK/viewmanager.lua | 184 ++++++ ETK/widgetmanager.lua | 53 ++ ETK/widgets/button.lua | 88 +++ ETK/widgets/input.lua | 87 +++ README.md | 0 big.main.lua | 1173 +++++++++++++++++++++++++++++++++++++++ build | 3 + etk.komodoproject | 4 + extra/lua_additions.lua | 4 +- main.lua | 11 + main.tns | Bin 0 -> 7098 bytes test.lua | 86 +++ test.tns | Bin 0 -> 947 bytes tests/big.macrotest.lua | 0 tests/macrotest.lua | 4 +- tests/macrotest.tns | Bin 21 files changed, 1895 insertions(+), 132 deletions(-) create mode 100644 ETK/viewmanager.lua create mode 100644 ETK/widgets/button.lua create mode 100644 ETK/widgets/input.lua mode change 100644 => 100755 README.md create mode 100644 big.main.lua create mode 100755 build create mode 100644 etk.komodoproject create mode 100644 main.lua create mode 100644 main.tns create mode 100644 test.lua create mode 100644 test.tns mode change 100644 => 100755 tests/big.macrotest.lua mode change 100644 => 100755 tests/macrotest.lua mode change 100644 => 100755 tests/macrotest.tns diff --git a/ETK/etk.lua b/ETK/etk.lua index 6bf1b0c..10fcbe2 100755 --- a/ETK/etk.lua +++ b/ETK/etk.lua @@ -6,22 +6,94 @@ ---------------------------------- etk = {} -local etk = etk - ---include "helpers.lua" ---include "graphics.lua" ---include "screenmanager.lua" ---include "eventmanager.lua" ---include "widgetmanager.lua" +do + local etk = etk + + --include "helpers.lua" + --include "graphics.lua" + --include "screenmanager.lua" + --include "viewmanager.lua" + --include "eventmanager.lua" + --include "widgetmanager.lua" +end do - local myView = View() + local Button = etk.Widgets.Button + local Input = etk.Widgets.Input + local myView = etk.View() + + + local box1 = Box( + Position { + top = "50px", + left = "100px" + }, + Dimension ("100px", "10%"), + "Hello world") + + local box2 = Box( + Position { + top = "50px", + left = "2px", + alignment = { + {ref=box1, side=Position.Sides.Right} + } + }, + Dimension ("50px", "10%"), + "Hello!") + + local box3 = Box( + Position { + top = "2px", + left = "0", + alignment = { + {ref=box2, side=Position.Sides.Bottom}, + {ref=box2, side=Position.Sides.Left} + } + }, + Dimension ("50px", "20%"), + "Yolo") + + local button1 = Button { + position = Position { bottom = "2px", right = "2px" }, + text = "Button1" + } + + local button2 = Button { + position = Position { bottom = "2px", right = "2px", alignment = {{ref=button1, side=Position.Sides.Left}}}, + text = "Button2" + } + + local input1 = Input { + position = Position { top = "2px", left = "2px" }, + value = "1" + } + input1.disabled = true + + local input2 = Input { + position = Position { top = "2px", left = "2px", alignment = {{ref=input1, side=Position.Sides.Bottom}}}, + value = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + } + input2.dimension.width = Input.defaultStyle.defaultWidth * 2 + + myView:addChildren(box1, box2, box3, button1, button2, input1, input2) + + + function button2:charIn(char) + self.text = self.text .. char + + self.parent:invalidate() + end + + button2.onAction = λ -> input1.value++; + function myView:draw(gc, x, y, width, height) Logger.Log("in myView draw") - gc:drawString(string.format("%d, %d, %d, %d", x, y, width, height), 10, 10, "top") end + + etk.RootScreen:pushScreen(myView) end \ No newline at end of file diff --git a/ETK/eventmanager.lua b/ETK/eventmanager.lua index 88d1e8d..0266ca9 100755 --- a/ETK/eventmanager.lua +++ b/ETK/eventmanager.lua @@ -74,6 +74,8 @@ do end on.paint = function(gc) + gc:smartClipRect("set", 0, 0, eg.viewPortWidth, eg.viewPortHeight) + eventLinker.__index(on, "paint")(gc) eg.dimensionsChanged = false diff --git a/ETK/graphics.lua b/ETK/graphics.lua index 7e9fe33..7c1553c 100755 --- a/ETK/graphics.lua +++ b/ETK/graphics.lua @@ -14,6 +14,8 @@ do eg.needsFullRedraw = true eg.dimensionsChanged = true + eg.isColor = platform.isColorDisplay() + eg.viewPortWidth = 318 eg.viewPortHeight = 212 @@ -40,14 +42,15 @@ do local clipRectData = {} local clipRects = 0 - local old = clipRectData[clipRects] local gc_clipRect = function (gc, what, x, y, w, h) if what == "set" then clipRects = clipRects + 1 clipRectData[clipRects] = {x, y, w, h} - elseif what == "subset" and old then + elseif what == "subset" and clipRects > 0 then + local old = clipRectData[clipRects] + x = old[1] < x and x or old[1] y = old[2] < y and y or old[2] h = old[2] + old[4] > y + h and h or old[2] + old[4] - y @@ -58,18 +61,19 @@ do clipRects = clipRects + 1 clipRectData[clipRects] = {x, y, w, h} - elseif what == "restore" and old then + elseif what == "restore" and clipRects > 0 then what = "set" - x, y, w, h = old[1], old[2], old[3], old[4] clipRectData[clipRects] = nil clipRects = clipRects - 1 + local old = clipRectData[clipRects] + x, y, w, h = old[1], old[2], old[3], old[4] + elseif what == "restore" then what = "reset" - end - + gc:clipRect(what, x, y, w, h) end @@ -81,7 +85,7 @@ do platform.withGC = function (func, ...) local gc = platform.gc() gc:begin() - func(..., gc) + func(..., gc) -- BUG: if you have a parameter after ..., you will only select the first parameter from the list, <- func({...}[1], gc) end end @@ -92,7 +96,6 @@ do local addToGC = function (name, func) local gcMeta = platform.withGC(getmetatable) gcMeta[name] = func - -- It's that simple! end ------------------------ diff --git a/ETK/helpers.lua b/ETK/helpers.lua index 0b69e5f..3c18df4 100644 --- a/ETK/helpers.lua +++ b/ETK/helpers.lua @@ -39,7 +39,7 @@ end do UnitCalculator = {} UnitCalculator.GetAbsoluteValue = function (value, referenceValue) - local numberValue, unit = string.match(tostring(value), "([%d.]+)(.*)") + local numberValue, unit = string.match(tostring(value), "([-%d.]+)(.*)") local number = tonumber(numberValue) @@ -75,9 +75,21 @@ do Dimension = class() return self.cachedWidth, self.cachedHeight else + self.cachedWidth = parentWidth + self.cachedHeight = parentHeight + return parentWidth, parentHeight end end + + function Dimension:getCachedDimension() + return self.cachedWidth or 0, self.cachedHeight or 0 + end + + function Dimension:invalidate() + self.cachedWidth = nil + self.cachedHeight = nil + end end do Position = class() @@ -109,6 +121,14 @@ do Position = class() local originX = parentX local originY = parentY + if self.right then + originX = originX + parentWidth + end + + if self.bottom then + originY = originY + parentHeight + end + for _, alignment in ipairs(self.alignment) do local side = alignment.side local ref = alignment.ref @@ -129,15 +149,15 @@ do Position = class() end if self.left then - x = originX + self.left + x = originX + UnitCalculator.GetAbsoluteValue(self.left, parentWidth) elseif self.right then - x = originX - self.right - width + x = originX - UnitCalculator.GetAbsoluteValue(self.right, parentWidth) - width end - + if self.top then - y = originY + self.top + y = originY + UnitCalculator.GetAbsoluteValue(self.top, parentHeight) elseif self.bottom then - y = originY - self.bottom - height + y = originY - UnitCalculator.GetAbsoluteValue(self.bottom, parentHeight) - height end self.cachedX = x @@ -146,5 +166,34 @@ do Position = class() return self.cachedX, self.cachedY end + + function Position:invalidate() + self.cachedX = nil + self.cachedY = nil + end + + function Position:getCachedPosition() + return self.cachedX or 0, self.cachedY or 0 + end +end + +----------- +-- Color -- +----------- + +local function unpackColor(col) + return col[1] or 0, col[2] or 0, col[3] or 0 +end + +------------------- +-- Event calling -- +------------------- + +local CallEvent = function(object, event, ...) + local handler = object[event] + + if handler then + return handler, handler(object, ...) + end end \ No newline at end of file diff --git a/ETK/screenmanager.lua b/ETK/screenmanager.lua index ca04576..41971cb 100755 --- a/ETK/screenmanager.lua +++ b/ETK/screenmanager.lua @@ -24,6 +24,7 @@ do etk.RootScreen = {} screen:onPushed(args) table.insert(screens, screen) + screen.parent = self end function RootScreen:popScreen(args) @@ -53,10 +54,11 @@ end -- Screen class -- ------------------ -do Screen = class() +do etk.Screen = class() + local Screen = etk.Screen local eg = etk.graphics - function Screen:init(parent, position, dimension) + function Screen:init(position, dimension) self.parent = parent self.position = position self.dimension = dimension @@ -82,6 +84,39 @@ do Screen = class() return self.position:get(parentX, parentY, parentWidth, parentHeight, width, height, eg.dimensionsChanged) end + function Screen:containsPosition(x, y) + local cachedX, cachedY = self.position:getCachedPosition() + local cachedWidth, cachedHeight = self.dimension:getCachedDimension() + + return x >= cachedX and y >= cachedY and x < cachedX + cachedWidth and y < cachedY + cachedHeight + end + + --------------------- + -- Manage children -- + --------------------- + + function Screen:addChild(child) + table.insert(self.children, child) + child.parent = self + end + + function Screen:addChildren(...) + for k, child in ipairs{...} do + self:addChild(child) + end + end + + ---------------- + -- Invalidate -- + ---------------- + + function Screen:invalidate() + local cachedX, cachedY = self.position:getCachedPosition() + local cachedWidth, cachedHeight = self.dimension:getCachedDimension() + + eg.invalidate(cachedX, cachedY, cachedWidth, cachedHeight) + end + ------------------- -- Screen events -- ------------------- @@ -99,126 +134,37 @@ do Screen = class() -------------------- function Screen:paint(gc) + self:prepare(gc) + local width, height = self:getDimension() local x, y = self:getPosition() - self:draw(gc, x, y, width, height) + self:draw(gc, x, y, width, height, eg.isColor) for k, screen in ipairs(self.children) do - screen:paint(gc) + screen:paint(gc) + + -- Reset color to default + -- Possibly this should also be done with the pen and the font + gc:setColorRGB(0,0,0) end - self:postDraw(gc, x, y, width, height) + self:postDraw(gc, x, y, width, height, eg.isColor) end - function Screen:draw(gc, x, y, width, height) + function Screen:prepare(gc) + -- use this callback to calculate dimensions + end + + function Screen:draw(gc, x, y, width, height, isColor) -- all drawing should happen here -- called before drawing children end - function Screen:postDraw(gc, x, y, width, height) + function Screen:postDraw(gc, x, y, width, height, isColor) -- all drawing should happen here -- called after drawing children end -end - ----------------- --- View class -- ----------------- - -do View = class(Screen) - function View:init(args) - args = args or {} - - local parent = args.parent or etk.RootScreen - local dimension = args.dimension or Dimension() - local position = args.position or Position() - - Screen.init(self, parent, position, dimension) - - self.focusIndex = 0 - end - - ----------------- - -- Focus logic -- - ----------------- - - function View:switchFocus(direction, isChildView) - local children = self.children - local focusIndex = self.focusIndex - - local currentChild = children[focusIndex] - local continue = true - - if currentChild and currentChild.focusIndex then - continue = not currentChild:switchFocus(direction, true) -- do we need to handle the focus change - end - - if continue then - if currentChild then - currentChild:onBlur() - end - - local nextFocusIndex = focusIndex + direction - local childrenCount = #self.children - local wrapped = false - - if nextFocusIndex > childrenCount then - nextFocusIndex = 1 - wrapped = true - elseif nextFocusIndex <= 0 then - nextFocusIndex = childrenCount - wrapped = true - end - - if wrapped and isChildView then - return false -- we are not handling the focus change due to wrapping, the parent focus manager needs to handle it - else - self:giveFocusToChildAtIndex(nextFocusIndex) - return true -- we have handled the focus change - end - end - end - - function View:giveFocusToChildAtIndex(index) - self.focusIndex = index - local nextChild = self.children[index] - - if nextChild then - nextChild.hasFocus = true - nextChild:onFocus() - end - end - - function View:getFocusedChild() - return self.children[self.focusIndex] - end - - ------------------------------------- - -- Link tab events to focus change -- - ------------------------------------- - - function View:tabKey() - self:switchFocus(1) - end - - function View:backtabKey() - self:switchFocus(-1) - end - - ---------------------------------------- - -- Propagate other events to children -- - ---------------------------------------- - - function View:onEvent(event, eventHandler, ...) - Logger.Log("View %q - event %q - eventHandler %q", tostring(self), tostring(event), tostring(eventHandler)) - - local child = self:getFocusedChild() - - if not eventHandler and child and child[event] then - child[event](child, ...) - end - end end \ No newline at end of file diff --git a/ETK/viewmanager.lua b/ETK/viewmanager.lua new file mode 100644 index 0000000..99903bc --- /dev/null +++ b/ETK/viewmanager.lua @@ -0,0 +1,184 @@ +---------------- +-- View class -- +---------------- + +do etk.View = class(etk.Screen) + local View = etk.View + local Screen = etk.Screen + local eg = etk.graphics + + function View:init(args) + args = args or {} + + local dimension = args.dimension or Dimension() + local position = args.position or Position() + + Screen.init(self, position, dimension) + + self.focusIndex = 0 + end + + ----------------- + -- Focus logic -- + ----------------- + + function View:switchFocus(direction, isChildView, counter) + local children = self.children + + local focusIndex = self.focusIndex + + local currentChild = children[focusIndex] + local continue = true + + if currentChild and currentChild.focusIndex then + continue = not currentChild:switchFocus(direction, true, 0) -- do we need to handle the focus change + end + + if continue then + + if counter > #children then + return + else + counter = counter + 1 + end + + self:removeFocusFromChild(currentChild) + + local nextFocusIndex = focusIndex + direction + local childrenCount = #self.children + local wrapped = false + + if nextFocusIndex > childrenCount then + nextFocusIndex = 1 + wrapped = true + elseif nextFocusIndex <= 0 then + nextFocusIndex = childrenCount + wrapped = true + end + + if wrapped and isChildView then + return false -- we are not handling the focus change due to wrapping, the parent focus manager needs to handle it + else + return self:giveFocusToChildAtIndex(nextFocusIndex, direction, isChildView, counter) + end + end + end + + function View:removeFocusFromChild(child) + if child then + self.focusIndex = 0 + child.hasFocus = false + CallEvent(child, "onBlur") + end + end + + function View:removeFocusFromChildAtIndex(index) + self:removeFocusFromChild(self:getFocusedChild()) + end + + function View:giveFocusToChildAtIndex(index, direction, isChildView, counter) + local nextChild = self.children[index] + self.focusIndex = index + + if nextChild then + if nextChild.ignoreFocus and direction and counter then + self:switchFocus(direction, false, counter) + else + nextChild.hasFocus = true + CallEvent(nextChild, "onFocus") + end + end + end + + function View:getFocusedChild() + return self.children[self.focusIndex] + end + + ------------------------------------- + -- Link tab events to focus change -- + ------------------------------------- + + function View:tabKey() + self:switchFocus(1, false, 0) + + eg.invalidate() + end + + function View:backtabKey() + self:switchFocus(-1, false, 0) + eg.invalidate() + end + + ----------------------------------- + -- Link touch event focus change -- + -- and propagete the event -- + ----------------------------------- + + View.lastChildMouseDown = nil + View.lastChildMouseOver = nil + + + function View:getChildIn(x, y) + local lastChildIndex = View.lastChildMouseDown + + if lastChildIndex then + local lastChild = self.children[lastChildIndex] + if lastChild:containsPosition(x, y) then + return lastChildIndex, lastChild + end + end + + for index, child in pairs(self.children) do + if child:containsPosition(x, y) then + return index, child + end + end + end + + function View:mouseDown(x, y) + local index, child = self:getChildIn(x, y) + + local lastChild = self:getFocusedChild() + if child ~= lastChild then + self:removeFocusFromChild(lastChild) + + if index then + self:giveFocusToChildAtIndex(index) + end + end + + View.lastChildMouseDown = index + + if child then + CallEvent(child, "onMouseDown", x, y) + end + + self:invalidate() + end + + function View:mouseUp(x, y) + local lastChildIndex = View.lastChildMouseDown + + if lastChildIndex then + local lastChild = self.children[lastChildIndex] + CallEvent(lastChild, "onMouseUp", x, y, lastChild:containsPosition(x, y)) + end + + self:invalidate() + end + + --------------------------------------------- + -- Propagate other events to focused child -- + --------------------------------------------- + + function View:onEvent(event, eventHandler, ...) + Logger.Log("View %q - event %q - eventHandler %q", tostring(self), tostring(event), tostring(eventHandler)) + + local child = self:getFocusedChild() + + if not eventHandler and child then + local handler = CallEvent(child, event, ...) + CallEvent(child, "onEvent", event, handler, ...) + end + end +end \ No newline at end of file diff --git a/ETK/widgetmanager.lua b/ETK/widgetmanager.lua index e69de29..dabb323 100755 --- a/ETK/widgetmanager.lua +++ b/ETK/widgetmanager.lua @@ -0,0 +1,53 @@ +------------ +-- Widget -- +------------ + +etk.Widgets = {} + +do etk.Widget = class(etk.Screen) + local Widget = etk.Widget + local Screen = etk.Screen + + function Widget:init(position, dimension) + Screen.init(self, position, dimension) + + self.hasFocus = false; + end + +end + + +do Box = class(etk.Widget) + local Widget = etk.Widget + + function Box:init(position, dimension, text) + Widget.init(self, position, dimension) + + self.text = text + end + + function Box:draw(gc, x, y, width, height, isColor) + Logger.Log("In Box:draw %d, %d, %d, %d", x, y, width, height) + + gc:setColorRGB(0, 0, 0) + + if self.hasFocus then + gc:fillRect(x, y, width, height) + else + -- No, draw only the outline + gc:drawRect(x, y, width, height) + end + + gc:setColorRGB(128, 128, 128) + gc:setFont("sansserif", "r", 7) + + if self.text then + gc:drawString(self.text, x+2, y, "top") + gc:drawString(width .. "," .. height, x+2, y+9, "top") + end + end + +end + +--include "widgets/button.lua" +--include "widgets/input.lua" \ No newline at end of file diff --git a/ETK/widgets/button.lua b/ETK/widgets/button.lua new file mode 100644 index 0000000..6ccd3f2 --- /dev/null +++ b/ETK/widgets/button.lua @@ -0,0 +1,88 @@ +do + local Widget = etk.Widget + local Widgets = etk.Widgets + + do Widgets.Button = class(Widget) + local Widget = etk.Widget + local Button = Widgets.Button + + Button.defaultStyle = { + textColor = {{000,000,000},{000,000,000}}, + backgroundColor = {{248,252,248},{248,252,248}}, + borderColor = {{136,136,136},{160,160,160}}, + focusColor = {{040,148,184},{000,000,000}}, + + defaultWidth = 48, + defaultHeight = 27, + font = { + serif="sansserif", + style="r", + size=10 + } + } + + function Button:init(arg) + self.text = arg.text or "Button" + + local style = arg.style or Button.defaultStyle + self.style = style + + local dimension = Dimension(style.defaultWidth, style.defaultHeight) + + Widget.init(self, arg.position, dimension) + end + + function Button:prepare(gc) + local font = self.style.font + + gc:setFont(font.serif, font.style, font.size) + self.dimension.width = gc:getStringWidth(self.text) + 10 + self.dimension:invalidate() + self.position:invalidate() + end + + function Button:draw(gc, x, y, width, height, isColor) + local color = isColor and 1 or 2 + local style = self.style + + gc:setColorRGB(unpackColor(style.backgroundColor[color])) + gc:fillRect(x + 2, y + 2, width - 4, height - 4) + + gc:setColorRGB(unpackColor(style.textColor[color])) + gc:drawString(self.text, x + 5, y + 3, "top") + + if self.hasFocus then + gc:setColorRGB(unpackColor(style.focusColor[color])) + gc:setPen("medium", "smooth") + else + gc:setColorRGB(unpackColor(style.borderColor[color])) + gc:setPen("thin", "smooth") + end + + gc:fillRect(x + 2, y, width - 4, 2) + gc:fillRect(x + 2, y + height - 2, width - 4, 2) + gc:fillRect(x, y + 2, 1, height - 4) + gc:fillRect(x + 1, y + 1, 1, height - 2) + gc:fillRect(x + width - 1, y + 2, 1, height - 4) + gc:fillRect(x + width - 2, y + 1, 1, height - 2) + + if self.hasFocus then + gc:setColorRGB(unpackColor(style.focusColor[color])) + end + + gc:setPen("thin", "smooth") + end + + + function Button:onMouseUp(x, y, onMe) + if onMe then + CallEvent(self, "onAction") + end + end + + function Button:enterKey() + CallEvent(self, "onAction") + end + end + +end \ No newline at end of file diff --git a/ETK/widgets/input.lua b/ETK/widgets/input.lua new file mode 100644 index 0000000..d8fd675 --- /dev/null +++ b/ETK/widgets/input.lua @@ -0,0 +1,87 @@ +do + local Widget = etk.Widget + local Widgets = etk.Widgets + + + + do Widgets.Input = class(Widget) + local Widget = etk.Widget + local Input = Widgets.Input + + Input.defaultStyle = { + textColor = {{000,000,000},{000,000,000}}, + backgroundColor = {{255,255,255},{255,255,255}}, + borderColor = {{136,136,136},{160,160,160}}, + focusColor = {{040,148,184},{000,000,000}}, + disabledColor = {{128,128,128},{128,128,128}}, + + defaultWidth = 100, + defaultHeight = 20, + + font = { + serif="sansserif", + style="r", + size=10 + } + } + + function Input:init(arg) + self.value = arg.value or "Button" + self.disabled = arg.disabled + + local style = arg.style or Input.defaultStyle + self.style = style + + local dimension = Dimension(style.defaultWidth, style.defaultHeight) + + Widget.init(self, arg.position, dimension) + end + + function Input:draw(gc, x, y, width, height, isColor) + local color = isColor and 1 or 2 + local style = self.style + local font = style.font + + gc:setFont(font.serif, font.style, font.size) + + gc:setColorRGB(unpackColor(style.backgroundColor[color])) + gc:fillRect(x, y, width, height) + + gc:setColorRGB(unpackColor(style.borderColor[color])) + gc:drawRect(x, y, width, height) + + if self.hasFocus then + gc:setColorRGB(unpackColor(style.focusColor[color])) + gc:drawRect(x - 1, y - 1, width + 2, height + 2) + gc:setColorRGB(0, 0, 0) + end + + gc:smartClipRect("subset", x, y, width, height) + + if self.disabled or self.value == "" then + gc:setColorRGB(unpackColor(style.focusColor[color])) + end + + local text = self.value + if self.value == "" then + text = self.placeholder or "" + end + + local strWidth = gc:getStringWidth(text) + + if strWidth < width - 4 or not self.hasFocus then + gc:drawString(text, x + 2, y + 1, "top") + else + gc:drawString(text, x - 4 + width - strWidth, y + 1, "top") + end + + if self.hasFocus and self.value ~= "" then + gc:fillRect(x + (text == self.value and strWidth + 2 or width - 4), y, 1, height) + end + + gc:smartClipRect("restore") + end + + end + +end \ No newline at end of file diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/big.main.lua b/big.main.lua new file mode 100644 index 0000000..8db45d2 --- /dev/null +++ b/big.main.lua @@ -0,0 +1,1173 @@ +---------------------------------- +-- ETK 4.0 test project -- +-- -- +-- (C) 2015 Jim Bauwens -- +-- Licensed under the GNU GPLv3 -- +---------------------------------- + +-------------------------------------- +-- Start of extra/lua_additions.lua -- +-------------------------------------- + +-- The following defines/macros are supposed to be used for simple cases only +-- (within simple syntax constructs that wouldn't confuse the "parser". See examples.) + +--Lua variable pattern + + +--Lambda style! + + +--Indexing of strings, string:sub(index, index) + + +--Increment/Decrement (Note: just use that in a "standalone way", not like: print(a = a + 1) etc.) + + + +--Compound assignment operators + + +------------------------------------ +-- End of extra/lua_additions.lua -- +------------------------------------ + +-------------------------- +-- Start of etk/etk.lua -- +-------------------------- + +---------------------------------- +-- ETK 4.0 -- +-- -- +-- (C) 2015 Jim Bauwens -- +-- Licensed under the GNU GPLv3 -- +---------------------------------- + +etk = {} + +do + local etk = etk + + ------------------------------ +-- Start of etk/helpers.lua -- +------------------------------ + +---------------------------------- +-- ETK Helper Classes -- +-- make stuff more easy -- +-- -- +-- (C) 2015 Jim Bauwens -- +-- Licensed under the GNU GPLv3 -- +---------------------------------- + +------------------ +-- Enumerations -- +------------------ + +Enum = function(enumTable) + for k,v in ipairs(enumTable) do + enumTable[v] = k + end + + return enumTable +end + +------------- +-- Logging -- +------------- + +do Logger = {} + Logger.Log = function (message, ...) + print(message:format(...)) + end + + Logger.Warn = function (message, ...) + Logger.Log("Warning: " .. message, ...) + end +end + + +----------------------------------------------- +-- Handle different types of user unit input -- +----------------------------------------------- + +do UnitCalculator = {} + UnitCalculator.GetAbsoluteValue = function (value, referenceValue) + local numberValue, unit = string.match(tostring(value), "([-%d.]+)(.*)") + + local number = tonumber(numberValue) + + if not number then + Logger.Warn("UnitCalculator.GetAbsoluteValue - Invalid number value, returning 0") + return 0 + end + + if unit == "%" then + return referenceValue / 100 * number + else + return number + end + end +end + +------------------------------------------------- +-- Keep dimensions in a nice to handle wrapper -- +------------------------------------------------- + +do Dimension = class() + function Dimension:init(width, height) + self.width = width + self.height = height + end + + function Dimension:get(parentWidth, parentHeight, dirty) + if self.width then + if dirty or not self.cachedWidth then + self.cachedWidth = UnitCalculator.GetAbsoluteValue(self.width, parentWidth) + self.cachedHeight = UnitCalculator.GetAbsoluteValue(self.height, parentHeight) + end + + return self.cachedWidth, self.cachedHeight + else + self.cachedWidth = parentWidth + self.cachedHeight = parentHeight + + return parentWidth, parentHeight + end + end + + function Dimension:getCachedDimension() + return self.cachedWidth or 0, self.cachedHeight or 0 + end + + function Dimension:invalidate() + self.cachedWidth = nil + self.cachedHeight = nil + end +end + +do Position = class() + Position.Type = Enum { "Absolute", "Relative" } + Position.Sides = Enum { "Left", "Right", "Top", "Bottom" } + + function Position:init(arg) + arg = arg or {} + + self.left = arg.left + self.top = arg.top + self.bottom = arg.bottom + self.right = arg.right + + self.alignment = arg.alignment or {} + + if not (self.left or self.right) then + self.left = 0 + end + + if not (self.top or self.bottom) then + self.top = 0 + end + end + + function Position:get(parentX, parentY, parentWidth, parentHeight, width, height, dirty) + if dirty or not self.cachedX then + local x, y + local originX = parentX + local originY = parentY + + if self.right then + originX = originX + parentWidth + end + + if self.bottom then + originY = originY + parentHeight + end + + for _, alignment in ipairs(self.alignment) do + local side = alignment.side + local ref = alignment.ref + local refWidth, refHeight = ref:getDimension() + local refX, refY = ref:getPosition() + + if side == Position.Sides.Left then + originX = refX + elseif side == Position.Sides.Right then + originX = refX + refWidth + elseif side == Position.Sides.Top then + originY = refY + elseif side == Position.Sides.Bottom then + originY = refY + refHeight + else + Logger.Warn("Invalid side specified") + end + end + + if self.left then + x = originX + UnitCalculator.GetAbsoluteValue(self.left, parentWidth) + elseif self.right then + x = originX - UnitCalculator.GetAbsoluteValue(self.right, parentWidth) - width + end + + if self.top then + y = originY + UnitCalculator.GetAbsoluteValue(self.top, parentHeight) + elseif self.bottom then + y = originY - UnitCalculator.GetAbsoluteValue(self.bottom, parentHeight) - height + end + + self.cachedX = x + self.cachedY = y + end + + return self.cachedX, self.cachedY + end + + function Position:invalidate() + self.cachedX = nil + self.cachedY = nil + end + + function Position:getCachedPosition() + return self.cachedX or 0, self.cachedY or 0 + end + +end + +----------- +-- Color -- +----------- + +local function unpackColor(col) + return col[1] or 0, col[2] or 0, col[3] or 0 +end + +------------------- +-- Event calling -- +------------------- + +local CallEvent = function(object, event, ...) + local handler = object[event] + + if handler then + return handler, handler(object, ...) + end +end +---------------------------- +-- End of etk/helpers.lua -- +---------------------------- + + ------------------------------- +-- Start of etk/graphics.lua -- +------------------------------- + +---------------------------------- +-- ETK Graphics -- +-- Some flags and functions -- +-- for painting and more -- +-- -- +-- (C) 2015 Jim Bauwens -- +-- Licensed under the GNU GPLv3 -- +---------------------------------- + +do + etk.graphics = {} + local eg = etk.graphics + + eg.needsFullRedraw = true + eg.dimensionsChanged = true + + eg.isColor = platform.isColorDisplay() + + eg.viewPortWidth = 318 + eg.viewPortHeight = 212 + + eg.areaToRedraw = {0, 0, 0, 0} + + + ------------------------------------------------ + -- Replacement for platform.window:invalidate -- + ------------------------------------------------ + + eg.invalidate = function (x, y, w, h) + platform.window:invalidate(x, y, w, h) + + if x then + eg.needsFullRedraw = false + eg.areaToRedraw = {x, y, w, h} + end + end + + + ---------------------------------------------- + -- Replacement for graphicalContex:clipRect -- + ---------------------------------------------- + + local clipRectData = {} + local clipRects = 0 + + local gc_clipRect = function (gc, what, x, y, w, h) + if what == "set" then + clipRects = clipRects + 1 + clipRectData[clipRects] = {x, y, w, h} + + elseif what == "subset" and clipRects > 0 then + local old = clipRectData[clipRects] + + x = old[1] < x and x or old[1] + y = old[2] < y and y or old[2] + h = old[2] + old[4] > y + h and h or old[2] + old[4] - y + w = old[1] + old[3] > x + w and w or old[1] + old[3] - x + + what = "set" + + clipRects = clipRects + 1 + clipRectData[clipRects] = {x, y, w, h} + + elseif what == "restore" and clipRects > 0 then + what = "set" + + clipRectData[clipRects] = nil + clipRects = clipRects - 1 + + local old = clipRectData[clipRects] + x, y, w, h = old[1], old[2], old[3], old[4] + + elseif what == "restore" then + what = "reset" + end + + gc:clipRect(what, x, y, w, h) + end + + -------------------------------------- + -- platform.withGC for apiLevel < 2 -- + -------------------------------------- + + if not platform.withGC then + platform.withGC = function (func, ...) + local gc = platform.gc() + gc:begin() + func(..., gc) -- BUG: if you have a parameter after ..., you will only select the first parameter from the list, <- func({...}[1], gc) + end + end + + --------------------------------- + -- Patch the Graphical Context -- + --------------------------------- + + local addToGC = function (name, func) + local gcMeta = platform.withGC(getmetatable) + gcMeta[name] = func + end + + ------------------------ + -- Apply some patches -- + ------------------------ + + addToGC("smartClipRect", gc_clipRect) + +end + +----------------------------- +-- End of etk/graphics.lua -- +----------------------------- + + ------------------------------------ +-- Start of etk/screenmanager.lua -- +------------------------------------ + +---------------------------------- +-- ETK Screenmanager -- +-- stuff and stuff I guess -- +-- cookies -- +-- -- +-- (C) 2015 Jim Bauwens -- +-- Licensed under the GNU GPLv3 -- +---------------------------------- + +do etk.RootScreen = {} + local RootScreen = etk.RootScreen + local eg = etk.graphics + + local x, y = 0, 0 + + --------------------- + -- Screen handling -- + --------------------- + + RootScreen.screens = {} + local screens = RootScreen.screens + + function RootScreen:pushScreen(screen, args) + screen:onPushed(args) + + table.insert(screens, screen) + screen.parent = self + end + + function RootScreen:popScreen(args) + screen:onPopped(args) + + return table.remove(screens) + end + + function RootScreen:peekScreen() + return screens[#screens] or RootScreen + end + + ---------------------------- + -- Dimension and position -- + ---------------------------- + + function RootScreen:getDimension() + return eg.viewPortWidth, eg.viewPortHeight + end + + function RootScreen:getPosition() + return x, y + end +end + +------------------ +-- Screen class -- +------------------ + +do etk.Screen = class() + local Screen = etk.Screen + local eg = etk.graphics + + function Screen:init(position, dimension) + self.parent = parent + self.position = position + self.dimension = dimension + + self.children = {} + end + + -------------------------------- + -- Dimension helper functions -- + -------------------------------- + + function Screen:getDimension() + local parentWidth, parentHeight = self.parent:getDimension() + + return self.dimension:get(parentWidth, parentHeight, eg.dimensionsChanged) + end + + function Screen:getPosition() + local parentX, parentY = self.parent:getPosition() + local parentWidth, parentHeight = self.parent:getDimension() + local width, height = self:getDimension() + + return self.position:get(parentX, parentY, parentWidth, parentHeight, width, height, eg.dimensionsChanged) + end + + function Screen:containsPosition(x, y) + local cachedX, cachedY = self.position:getCachedPosition() + local cachedWidth, cachedHeight = self.dimension:getCachedDimension() + + return x >= cachedX and y >= cachedY and x < cachedX + cachedWidth and y < cachedY + cachedHeight + end + + --------------------- + -- Manage children -- + --------------------- + + function Screen:addChild(child) + table.insert(self.children, child) + child.parent = self + end + + function Screen:addChildren(...) + for k, child in ipairs{...} do + self:addChild(child) + end + end + + ---------------- + -- Invalidate -- + ---------------- + + function Screen:invalidate() + local cachedX, cachedY = self.position:getCachedPosition() + local cachedWidth, cachedHeight = self.dimension:getCachedDimension() + + eg.invalidate(cachedX, cachedY, cachedWidth, cachedHeight) + end + + ------------------- + -- Screen events -- + ------------------- + + function Screen:onPushed(args) + -- when pushed + end + + function Screen:onPopped(args) + -- when popped + end + + -------------------- + -- Drawing events -- + -------------------- + + function Screen:paint(gc) + self:prepare(gc) + + local width, height = self:getDimension() + local x, y = self:getPosition() + + self:draw(gc, x, y, width, height, eg.isColor) + + for k, screen in ipairs(self.children) do + screen:paint(gc) + + -- Reset color to default + -- Possibly this should also be done with the pen and the font + gc:setColorRGB(0,0,0) + end + + self:postDraw(gc, x, y, width, height, eg.isColor) + end + + function Screen:prepare(gc) + -- use this callback to calculate dimensions + end + + function Screen:draw(gc, x, y, width, height, isColor) + -- all drawing should happen here + + -- called before drawing children + end + + function Screen:postDraw(gc, x, y, width, height, isColor) + -- all drawing should happen here + + -- called after drawing children + end +end +---------------------------------- +-- End of etk/screenmanager.lua -- +---------------------------------- + + ---------------------------------- +-- Start of etk/viewmanager.lua -- +---------------------------------- + +---------------- +-- View class -- +---------------- + +do etk.View = class(etk.Screen) + local View = etk.View + local Screen = etk.Screen + local eg = etk.graphics + + function View:init(args) + args = args or {} + + local dimension = args.dimension or Dimension() + local position = args.position or Position() + + Screen.init(self, position, dimension) + + self.focusIndex = 0 + end + + ----------------- + -- Focus logic -- + ----------------- + + function View:switchFocus(direction, isChildView, counter) + local children = self.children + + local focusIndex = self.focusIndex + + local currentChild = children[focusIndex] + local continue = true + + if currentChild and currentChild.focusIndex then + continue = not currentChild:switchFocus(direction, true, 0) -- do we need to handle the focus change + end + + if continue then + + if counter > #children then + return + else + counter = counter + 1 + end + + self:removeFocusFromChild(currentChild) + + local nextFocusIndex = focusIndex + direction + local childrenCount = #self.children + local wrapped = false + + if nextFocusIndex > childrenCount then + nextFocusIndex = 1 + wrapped = true + elseif nextFocusIndex <= 0 then + nextFocusIndex = childrenCount + wrapped = true + end + + if wrapped and isChildView then + return false -- we are not handling the focus change due to wrapping, the parent focus manager needs to handle it + else + return self:giveFocusToChildAtIndex(nextFocusIndex, direction, isChildView, counter) + end + end + end + + function View:removeFocusFromChild(child) + if child then + self.focusIndex = 0 + child.hasFocus = false + CallEvent(child, "onBlur") + end + end + + function View:removeFocusFromChildAtIndex(index) + self:removeFocusFromChild(self:getFocusedChild()) + end + + function View:giveFocusToChildAtIndex(index, direction, isChildView, counter) + local nextChild = self.children[index] + self.focusIndex = index + + if nextChild then + if nextChild.ignoreFocus and direction and counter then + self:switchFocus(direction, false, counter) + else + nextChild.hasFocus = true + CallEvent(nextChild, "onFocus") + end + end + end + + function View:getFocusedChild() + return self.children[self.focusIndex] + end + + ------------------------------------- + -- Link tab events to focus change -- + ------------------------------------- + + function View:tabKey() + self:switchFocus(1, false, 0) + + eg.invalidate() + end + + function View:backtabKey() + self:switchFocus(-1, false, 0) + eg.invalidate() + end + + ----------------------------------- + -- Link touch event focus change -- + -- and propagete the event -- + ----------------------------------- + + View.lastChildMouseDown = nil + View.lastChildMouseOver = nil + + + function View:getChildIn(x, y) + local lastChildIndex = View.lastChildMouseDown + + if lastChildIndex then + local lastChild = self.children[lastChildIndex] + if lastChild:containsPosition(x, y) then + return lastChildIndex, lastChild + end + end + + for index, child in pairs(self.children) do + if child:containsPosition(x, y) then + return index, child + end + end + end + + function View:mouseDown(x, y) + local index, child = self:getChildIn(x, y) + + local lastChild = self:getFocusedChild() + if child ~= lastChild then + self:removeFocusFromChild(lastChild) + + if index then + self:giveFocusToChildAtIndex(index) + end + end + + View.lastChildMouseDown = index + + if child then + CallEvent(child, "onMouseDown", x, y) + end + + self:invalidate() + end + + function View:mouseUp(x, y) + local lastChildIndex = View.lastChildMouseDown + + if lastChildIndex then + local lastChild = self.children[lastChildIndex] + CallEvent(lastChild, "onMouseUp", x, y, lastChild:containsPosition(x, y)) + end + + self:invalidate() + end + + --------------------------------------------- + -- Propagate other events to focused child -- + --------------------------------------------- + + function View:onEvent(event, eventHandler, ...) + Logger.Log("View %q - event %q - eventHandler %q", tostring(self), tostring(event), tostring(eventHandler)) + + local child = self:getFocusedChild() + + if not eventHandler and child then + local handler = CallEvent(child, event, ...) + CallEvent(child, "onEvent", event, handler, ...) + end + end +end +-------------------------------- +-- End of etk/viewmanager.lua -- +-------------------------------- + + ----------------------------------- +-- Start of etk/eventmanager.lua -- +----------------------------------- + +---------------------------------- +-- ETK Event Manager -- +-- Handle the events! -- +-- -- +-- (C) 2015 Jim Bauwens -- +-- Licensed under the GNU GPLv3 -- +---------------------------------- + +do + etk.eventmanager = {} + etk.eventhandlers = {} + + local em = etk.eventmanager + local eh = etk.eventhandlers + local eg = etk.graphics + local rs = etk.RootScreen + + ----------- + -- TOOLS -- + ----------- + + -- We will use this function when calling events + local callEventHandler = function (func, ...) + if func then + func(...) + end + end + + + ------------------- + -- EVENT LINKING -- + ------------------- + + local eventLinker = {} + local triggeredEvent + + local eventDistributer = function (...) + local currentScreen = rs:peekScreen() + local eventHandler = currentScreen[triggeredEvent] + + if eventHandler then + eventHandler(currentScreen, ...) + end + + local genericEventHandler = currentScreen.onEvent + if genericEventHandler then + genericEventHandler(currentScreen, triggeredEvent, eventHandler, ...) + end + end + + eventLinker.__index = function (on, event) + triggeredEvent = event + return eventDistributer + end + + setmetatable(on, eventLinker) + + on.activate = function () + eg.needsFullRedraw = true + end + + on.getFocus = function () + eg.needsFullRedraw = true + end + + on.resize = function (width, height) + Logger.Log("Viewport dimensions changed to %dx%d", width, height) + + eg.dimensionsChanged = true + eg.needsFullRedraw = true + + eg.viewPortWidth = width + eg.viewPortHeight = height + end + + on.paint = function(gc) + gc:smartClipRect("set", 0, 0, eg.viewPortWidth, eg.viewPortHeight) + + eventLinker.__index(on, "paint")(gc) + + eg.dimensionsChanged = false + end + +end + +--------------------------------- +-- End of etk/eventmanager.lua -- +--------------------------------- + + ------------------------------------ +-- Start of etk/widgetmanager.lua -- +------------------------------------ + +------------ +-- Widget -- +------------ + +etk.Widgets = {} + +do etk.Widget = class(etk.Screen) + local Widget = etk.Widget + local Screen = etk.Screen + + function Widget:init(position, dimension) + Screen.init(self, position, dimension) + + self.hasFocus = false; + end + +end + + +do Box = class(etk.Widget) + local Widget = etk.Widget + + function Box:init(position, dimension, text) + Widget.init(self, position, dimension) + + self.text = text + end + + function Box:draw(gc, x, y, width, height, isColor) + Logger.Log("In Box:draw %d, %d, %d, %d", x, y, width, height) + + gc:setColorRGB(0, 0, 0) + + if self.hasFocus then + gc:fillRect(x, y, width, height) + else + -- No, draw only the outline + gc:drawRect(x, y, width, height) + end + + gc:setColorRGB(128, 128, 128) + gc:setFont("sansserif", "r", 7) + + if self.text then + gc:drawString(self.text, x+2, y, "top") + gc:drawString(width .. "," .. height, x+2, y+9, "top") + end + end + +end + +------------------------------------- +-- Start of etk/widgets/button.lua -- +------------------------------------- + +do + local Widget = etk.Widget + local Widgets = etk.Widgets + + do Widgets.Button = class(Widget) + local Widget = etk.Widget + local Button = Widgets.Button + + Button.defaultStyle = { + textColor = {{000,000,000},{000,000,000}}, + backgroundColor = {{248,252,248},{248,252,248}}, + borderColor = {{136,136,136},{160,160,160}}, + focusColor = {{040,148,184},{000,000,000}}, + + defaultWidth = 48, + defaultHeight = 27, + font = { + serif="sansserif", + style="r", + size=10 + } + } + + function Button:init(arg) + self.text = arg.text or "Button" + + local style = arg.style or Button.defaultStyle + self.style = style + + local dimension = Dimension(style.defaultWidth, style.defaultHeight) + + Widget.init(self, arg.position, dimension) + end + + function Button:prepare(gc) + local font = self.style.font + + gc:setFont(font.serif, font.style, font.size) + self.dimension.width = gc:getStringWidth(self.text) + 10 + self.dimension:invalidate() + self.position:invalidate() + end + + function Button:draw(gc, x, y, width, height, isColor) + local color = isColor and 1 or 2 + local style = self.style + + gc:setColorRGB(unpackColor(style.backgroundColor[color])) + gc:fillRect(x + 2, y + 2, width - 4, height - 4) + + gc:setColorRGB(unpackColor(style.textColor[color])) + gc:drawString(self.text, x + 5, y + 3, "top") + + if self.hasFocus then + gc:setColorRGB(unpackColor(style.focusColor[color])) + gc:setPen("medium", "smooth") + else + gc:setColorRGB(unpackColor(style.borderColor[color])) + gc:setPen("thin", "smooth") + end + + gc:fillRect(x + 2, y, width - 4, 2) + gc:fillRect(x + 2, y + height - 2, width - 4, 2) + gc:fillRect(x, y + 2, 1, height - 4) + gc:fillRect(x + 1, y + 1, 1, height - 2) + gc:fillRect(x + width - 1, y + 2, 1, height - 4) + gc:fillRect(x + width - 2, y + 1, 1, height - 2) + + if self.hasFocus then + gc:setColorRGB(unpackColor(style.focusColor[color])) + end + + gc:setPen("thin", "smooth") + end + + + function Button:onMouseUp(x, y, onMe) + if onMe then + CallEvent(self, "onAction") + end + end + + function Button:enterKey() + CallEvent(self, "onAction") + end + end + +end +----------------------------------- +-- End of etk/widgets/button.lua -- +----------------------------------- + +------------------------------------ +-- Start of etk/widgets/input.lua -- +------------------------------------ + +do + local Widget = etk.Widget + local Widgets = etk.Widgets + + + + do Widgets.Input = class(Widget) + local Widget = etk.Widget + local Input = Widgets.Input + + Input.defaultStyle = { + textColor = {{000,000,000},{000,000,000}}, + backgroundColor = {{255,255,255},{255,255,255}}, + borderColor = {{136,136,136},{160,160,160}}, + focusColor = {{040,148,184},{000,000,000}}, + disabledColor = {{128,128,128},{128,128,128}}, + + defaultWidth = 100, + defaultHeight = 20, + + font = { + serif="sansserif", + style="r", + size=10 + } + } + + function Input:init(arg) + self.value = arg.value or "Button" + self.disabled = arg.disabled + + local style = arg.style or Input.defaultStyle + self.style = style + + local dimension = Dimension(style.defaultWidth, style.defaultHeight) + + Widget.init(self, arg.position, dimension) + end + + function Input:draw(gc, x, y, width, height, isColor) + local color = isColor and 1 or 2 + local style = self.style + local font = style.font + + gc:setFont(font.serif, font.style, font.size) + + gc:setColorRGB(unpackColor(style.backgroundColor[color])) + gc:fillRect(x, y, width, height) + + gc:setColorRGB(unpackColor(style.borderColor[color])) + gc:drawRect(x, y, width, height) + + if self.hasFocus then + gc:setColorRGB(unpackColor(style.focusColor[color])) + gc:drawRect(x - 1, y - 1, width + 2, height + 2) + gc:setColorRGB(0, 0, 0) + end + + gc:smartClipRect("subset", x, y, width, height) + + if self.disabled or self.value == "" then + gc:setColorRGB(unpackColor(style.focusColor[color])) + end + + local text = self.value + if self.value == "" then + text = self.placeholder or "" + end + + local strWidth = gc:getStringWidth(text) + + if strWidth < width - 4 or not self.hasFocus then + gc:drawString(text, x + 2, y + 1, "top") + else + gc:drawString(text, x - 4 + width - strWidth, y + 1, "top") + end + + if self.hasFocus and self.value ~= "" then + gc:fillRect(x + (text == self.value and strWidth + 2 or width - 4), y, 1, height) + end + + gc:smartClipRect("restore") + end + + end + +end +---------------------------------- +-- End of etk/widgets/input.lua -- +---------------------------------- + +---------------------------------- +-- End of etk/widgetmanager.lua -- +---------------------------------- + +end + +do + local Button = etk.Widgets.Button + local Input = etk.Widgets.Input + local myView = etk.View() + + + local box1 = Box( + Position { + top = "50px", + left = "100px" + }, + Dimension ("100px", "10%"), + "Hello world") + + local box2 = Box( + Position { + top = "50px", + left = "2px", + alignment = { + {ref=box1, side=Position.Sides.Right} + } + }, + Dimension ("50px", "10%"), + "Hello!") + + local box3 = Box( + Position { + top = "2px", + left = "0", + alignment = { + {ref=box2, side=Position.Sides.Bottom}, + {ref=box2, side=Position.Sides.Left} + } + }, + Dimension ("50px", "20%"), + "Yolo") + + local button1 = Button { + position = Position { bottom = "2px", right = "2px" }, + text = "Button1" + } + + local button2 = Button { + position = Position { bottom = "2px", right = "2px", alignment = {{ref=button1, side=Position.Sides.Left}}}, + text = "Button2" + } + + local input1 = Input { + position = Position { top = "2px", left = "2px" }, + value = "1" + } + input1.disabled = true + + local input2 = Input { + position = Position { top = "2px", left = "2px", alignment = {{ref=input1, side=Position.Sides.Bottom}}}, + value = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + } + input2.dimension.width = Input.defaultStyle.defaultWidth * 2 + + myView:addChildren(box1, box2, box3, button1, button2, input1, input2) + + + function button2:charIn(char) + self.text = self.text .. char + + self.parent:invalidate() + end + + button2.onAction = (function ( ) return input1.value = input1.value + 1 end) + + + function myView:draw(gc, x, y, width, height) + Logger.Log("in myView draw") + end + + + + etk.RootScreen:pushScreen(myView) +end +------------------------ +-- End of etk/etk.lua -- +------------------------ + + + diff --git a/build b/build new file mode 100755 index 0000000..4f7235b --- /dev/null +++ b/build @@ -0,0 +1,3 @@ +#!/bin/bash + +./buildtools/build.lua main.lua main.tns \ No newline at end of file diff --git a/etk.komodoproject b/etk.komodoproject new file mode 100644 index 0000000..ba28b24 --- /dev/null +++ b/etk.komodoproject @@ -0,0 +1,4 @@ + + + + diff --git a/extra/lua_additions.lua b/extra/lua_additions.lua index d1f50a1..67f6d0a 100755 --- a/extra/lua_additions.lua +++ b/extra/lua_additions.lua @@ -5,7 +5,9 @@ --define "__LuaVar__" "[%w%._%[%]]+" --Lambda style! ---define "λ(.-)->(.-);" "(function (%1) return %2 end)" +--define "λ(.-)->(.-);" "(function (%1) %2 end)" +--define "λ(.-)=>(.-);" "(function (%1) return %2 end)" + --Indexing of strings, string[~index] --define "(%w+)%[%~(%w+)%]" "%1:sub(%2, %2)" diff --git a/main.lua b/main.lua new file mode 100644 index 0000000..fc96fbe --- /dev/null +++ b/main.lua @@ -0,0 +1,11 @@ +---------------------------------- +-- ETK 4.0 test project -- +-- -- +-- (C) 2015 Jim Bauwens -- +-- Licensed under the GNU GPLv3 -- +---------------------------------- + +--include "extra/lua_additions.lua" +--include "etk/etk.lua" + + diff --git a/main.tns b/main.tns new file mode 100644 index 0000000000000000000000000000000000000000..cafa82db2227ff1354ebf895774c33222fb2e638 GIT binary patch literal 7098 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 literal 0 HcmV?d00001 diff --git a/test.lua b/test.lua new file mode 100644 index 0000000..2f204e0 --- /dev/null +++ b/test.lua @@ -0,0 +1,86 @@ +a = [[ + + ! + " + # + % + ' + ( + ) + * + + + , + - + . + / + 0 + 1 + 2 + 3 + 4 + 5 + 8 + : + + = + + A + B + C + D + E + F + G + H + I + J + K + L + M + N + O + P + R + S + T + U + V + W + X + Y + [ + \n + \t + ] + _ + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + { + } + + +]] diff --git a/test.tns b/test.tns new file mode 100644 index 0000000000000000000000000000000000000000..06568d83819278dad9435b200fb53284c7d74cfa GIT binary patch literal 947 zcmdN2@$~fxFfcbT5Mf|o;00m@1|FtpK?6nx1`y@}in!z_m*%GCmFQLE=J21pajB85 zE!xZL=(&|*pRUTT(9dH~`t;%8GPZKxx@8BfKE#ylTIG^(HsV>qb%$?r#P58VQojGx zz3)t%CzCeDzE}DcXuhCvqQ}E#-?bcux)&}#)=gL~d}xODTgzxp70XHbJXJnZOD0`=d3Btj{bs^0=?_!-2s0hxVP*a6NoQaKf@T!3%yzyJb7BeHpxa z`t6OEG9G<1<5x2)o&8eYW9>`LbcP7Y!v}=Q7I+nZ-guc);u_oC=_1yt7v zb@i@ z#^A?H2JcBJPa>Y};dFTsV-fyjR%_Ja^oJApPMz;QpcL`#byVxs?W?2NL@OW8*&LyK ze6Gqwuf(=KKwB_y_0L{;VmXF%EVSX7}@S^xmv-& z;W?pA@aU2F*>{`|OBuJY?Q6TZC`OT~#$)-3ynCOoeR(du=yGmeCoB8weFi(8Nm}32 zOcik{kp3_?^;pm0+;jV~g3}nzy}VYxBzAFP-qqeYx7R=P`mwtAUYrv{yvc_%#S67Q z^=$s=DKbs*^pXeG|L*5+V`Bfc`*qTUu@5ZJEc{E3U=RbG6`V*ba%# zrmFQ4^Y%pVdtJT#>aP}Uf9*teSJ=Opz!%#S4;&Gj-K}5mkRm*G%G;b)c@7<51v_{=0EQfJs_D7Qa|VK(luVobpyN^nHU%l rQHh>OfU$`Lkh2WBPV_{8(3Sv97RX5=#52GJB+CSZB|ut}8N>qssZ5Q= literal 0 HcmV?d00001 diff --git a/tests/big.macrotest.lua b/tests/big.macrotest.lua old mode 100644 new mode 100755 diff --git a/tests/macrotest.lua b/tests/macrotest.lua old mode 100644 new mode 100755 index 79a3d5b..87ddcc4 --- a/tests/macrotest.lua +++ b/tests/macrotest.lua @@ -25,10 +25,10 @@ local c = 84 c>>=1 print(21<<1, c) -newStringA = myString:gsub("(.)", λ x -> x..x;) +newStringA = myString:gsub("(.)", λ x => x..x;) print(newStringA) -newStringB = myString:gsub("()", λ x -> myString[~x];) +newStringB = myString:gsub("()", λ x => myString[~x];) print(newStringB) print("ETK") diff --git a/tests/macrotest.tns b/tests/macrotest.tns old mode 100644 new mode 100755