0 Members and 3 Guests are viewing this topic.
-- Balance chemical equation-- John Powers 2010-06-03--------------------------------------------------------------------- ViewView = class()function View:init(window) self.window = window self.widgetList = {} self.focusList = {} self.currentFocus = 1 -- Previous location of mouse pointer self.prev_mousex = 0 self.prev_mousey = 0endfunction View:invalidate() self.window:invalidate()endfunction View:add(o) table.insert(self.widgetList, o) if o.acceptsFocus then table.insert(self.focusList, o) end return oendfunction View:sendStringToFocus(str) self.focusList[self.currentFocus]:addString(str) self.window:invalidate()endfunction View:sendBackspaceToFocus() self.focusList[self.currentFocus]:delChar() self.window:invalidate()endfunction View:tabForward() local nextFocus = self.currentFocus + 1 if nextFocus > #self.focusList then nextFocus = 1 end self.currentFocus = nextFocus self.window:invalidate()endfunction View:tabBackward() local nextFocus = self.currentFocus - 1 if nextFocus < 1 then nextFocus = #self.focusList end self.currentFocus = nextFocus self.window:invalidate()endfunction View:onMouseDown(x, y) -- Find a widget that has a mouse down handler and bounds the click point for _, o in ipairs(self.widgetList) do local md = o.onMouseDown if md and o:contains(x, y) then self.mouseCaptured = o md(o, window, x - o.x, y - o.y) break end endendfunction View:onMouseMove(x, y) local prev_mousex = self.prev_mousex local prev_mousey = self.prev_mousey for _, o in ipairs(self.widgetList) do local xyin = o:contains(x, y) local prev_xyin = o:contains(prev_mousex, prev_mousey) if xyin and not prev_xyin then -- Mouse entered widget o:onMouseEnter(x, y) elseif prev_xyin and not xyin then -- Mouse left widget o:onMouseLeave(x, y) end end self.prev_mousex = x self.prev_mousey = yendfunction View:onMouseUp(x, y) local mc = self.mouseCaptured if mc then self.mouseCaptured = nil if mc:contains(x, y) then mc:onMouseUp(x - mc.x, y - mc.y) else mc:cancelClick() end endendfunction View:enterHandler() -- Does the focused widget accept Enter? local o = self.focusList[self.currentFocus] if o.acceptsEnter then o:enterHandler() self.window:invalidate() else -- look for a default Enter handler for _, o in ipairs(self.widgetList) do if o.visible and o.default then o:enterHandler() self.window:invalidate() end end endendfunction View:paint(gc) local fo = self.focusList[self.currentFocus] for _, o in ipairs(self.widgetList) do if o.visible then o:paint(gc, fo == o) end endend--------------------------------------------------------------------- WidgetWidget = class()function Widget:init(view, x, y, w, h) self.view = view self.x = x self.y = y self.w = w self.h = h self.acceptsFocus = false self.visible = trueendfunction Widget:contains(x, y) return x >= self.x and x <= self.x + self.w and y >= self.y and y <= self.y + self.hendfunction Widget:onMouseEnter(x, y) -- Implemented in subclassesendfunction Widget:onMouseLeave(x, y) -- Implemented in subclassesend--------------------------------------------------------------------- TextText = class(Widget)function Text:init(view, x, y, w, h, text, just, style) if w <= 0 then w = 10 * #text end if h <= 0 then h = 22 end Widget.init(self, view, x, y, w, h) self.text = text or "" if just == "right" then self.just = 2 -- right justified elseif just == "center" then self.just = 1 -- centered else self.just = 0 -- left justified end self.style = style or "r"end-- Adds a string to the end of the textfunction Text:addString(str) self.text = self.text .. str self.view:invalidate()endfunction Text:stringWidth() local gc = platform.gc() gc:begin() local w = gc:getStringWidth(self.text) gc:finish() return wendfunction Text:stringHeight() local gc = platform.gc() gc:begin() local h = gc:getStringHeight(self.text) gc:finish() return hendfunction Text:setText(str) self.text = tostring(str) self.view:invalidate()end-- Deletes character from end of textfunction Text:delChar() if #self.text > 0 then self.text = self.text:usub(1, -2) self.view:invalidate() endend-- Returns a formatted version of the textfunction Text:format() return self.textendfunction Text:paint(gc, focused) local text = self:format() gc:setColorRGB(0, 0, 0) if focused then gc:setPen("thin", "dotted") gc:drawRect(self.x, self.y, self.w, self.h) text = text .. "_" end gc:setFont("sansserif", self.style, 11) local x = self.x + 1 if self.just == 1 then -- centered x = self.x + (self.w - gc:getStringWidth(text))/2 elseif self.just == 2 then -- right justified x = self.x + self.w - gc:getStringWidth(text) - 1 end gc:drawString(text, x, self.y + self.h, "bottom")end--------------------------------------------------------------------- EntryEntry = class(Text)function Entry:init(...) Text.init(self, ...) self.acceptsFocus = trueend--------------------------------------------------------------------- ButtonButton = class(Widget)function Button:init(view, x, y, w, h, text, default, command) Widget.init(self, view, x, y, w, h) -- Button configuration self.text = text self.default = default -- is default button when ENTER is pressed self.command = command or function() end -- what to do when pressed self.acceptsFocus = true self.acceptsEnter = true -- Current button state self.clicked = false self.highlighted = falseend-- Act on key press on buttonfunction Button:addString(str) if str == " " then self:command() endendfunction Button:onMouseDown(x, y) self.clicked = true self.highlighted = true self.view:invalidate()endfunction Button:onMouseEnter(x, y) if self.clicked and not self.highlighted then self.highlighted = true self.view:invalidate() endendfunction Button:onMouseLeave(x, y) if self.clicked and self.highlighted then self.highlighted = false self.view:invalidate() endendfunction Button:onMouseUp(x, y) if self.clicked then self.highlighted = false self.clicked = false self:command() self.view:invalidate() endendfunction Button:cancelClick() self.clicked = falseendfunction Button:enterHandler() self:command()endfunction Button:paint(gc, focused) local x = self.x local y = self.y local w = self.w local h = self.h local radius = 5 local diam = 2*radius if focused then gc:setPen("thin", "dotted") elseif self.default then gc:setPen("thin", "smooth") else gc:setPen("thin", "smooth") end local bkgr, bkgg, bkgb = 0xE0, 0xE0, 0xE0 if self.highlighted then bkgr = 0xC0 bkgg = 0xC0 bkgb = 0xC0 end -- Draw background gc:setColorRGB(bkgr, bkgg, bkgb) gc:fillRect(x+radius, y, w-2*radius, h) gc:fillRect(x, y+radius, w, h-2*radius) -- Draw border gc:setColorRGB(0, 0, 0) gc:drawLine(x+radius, y, x+w-radius, y) gc:drawLine(x+radius, y+h, x+w-radius, y+h) gc:drawLine(x, y+radius, x, y+h-radius) gc:drawLine(x+w, y+radius, x+w, y+h-radius) -- top left corner gc:setColorRGB(bkgr, bkgg, bkgb) gc:fillArc(x, y, diam, diam, 90, 90) gc:setColorRGB(0, 0, 0) gc:drawArc(x, y, diam, diam, 90, 90) -- bottom left corner gc:setColorRGB(bkgr, bkgg, bkgb) gc:fillArc(x, y + h - diam, diam, diam, 180, 90) gc:setColorRGB(0, 0, 0) gc:drawArc(x, y + h - diam, diam, diam, 180, 90) -- bottom right corner gc:setColorRGB(bkgr, bkgg, bkgb) gc:fillArc(x + w - diam, y + h - diam, diam, diam, 270, 90) gc:setColorRGB(0, 0, 0) gc:drawArc(x + w - diam, y + h - diam, diam, diam, 270, 90) -- top right corner gc:setColorRGB(bkgr, bkgg, bkgb) gc:fillArc(x + w - diam, y, diam, diam, 0, 90) gc:setColorRGB(0, 0, 0) gc:drawArc(x + w - diam, y, diam, diam, 0, 90) -- Draw label gc:setFont("sansserif", "b", 10) gc:drawString(self.text, x + (w - gc:getStringWidth(self.text))/2, y+1, "top")end--------------------------------------------------------------------- SpinBox ArrowSpinBoxArrow = class(Button)function SpinBoxArrow:init(view, x, y, w, h, dir, command) Button.init(self, view, x, y, w, h, nil, false, command) self.dir = dir -- "up" or "down"endfunction SpinBoxArrow:onMouseDown(x, y) self:command()endfunction SpinBoxArrow:onMouseEnter(x, y)endfunction SpinBoxArrow:onMouseLeave(x, y)endfunction SpinBoxArrow:onMouseUp(x, y)endfunction SpinBoxArrow:paint(gc, focused) local x1 = self.x + self.w/2 local x2 = x1 + 5 local x3 = x2 - 10 local y1, y2, y3 if self.dir == "up" then y1 = self.y y2 = y1 + self.h y3 = y2 else y1 = self.y + self.h y2 = self.y y3 = y2 end gc:setPen("thin", "smooth") gc:drawPolyLine({x1, y1, x2, y2, x3, y3, x1, y1}) if focused then gc:setPen("thin", "dotted") gc:drawRect(x3-2, self.y-2, 14, self.h+4) endend --------------------------------------------------------------------- SpinBoxSpinBox = class(Text)function SpinBox:init(view, x, y, w, h, value) Text.init(self, view, x, y, w, h, tostring(value or 1), "center") self.acceptsFocus = true self.view:add(SpinBoxArrow(view, x, y-10, w, 7, "up", function() self:up() end )) self.view:add(SpinBoxArrow(view, x, y+self.h+3, w, 7, "down", function() self:down() end ))endfunction SpinBox:value() return tonumber(self.text)endfunction SpinBox:up() self.text = tostring(tonumber(self.text) + 1) self.view:invalidate()endfunction SpinBox:down() if tonumber(self.text) > 1 then self.text = tostring(tonumber(self.text) - 1) self.view:invalidate() endend--------------------------------------------------------------------- Molecule input-- This is a subclass of an Input box. It converts digits to subscripts before-- being displayed.MoleculeEntry = class(Entry)function MoleculeEntry:init(...) Entry.init(self, ...)end-- Subscript digits 0 ... 9Subscript = { ["0"] = string.uchar(0x2080), ["1"] = string.uchar(0x2081), ["2"] = string.uchar(0x2082), ["3"] = string.uchar(0x2083), ["4"] = string.uchar(0x2084), ["5"] = string.uchar(0x2085), ["6"] = string.uchar(0x2086), ["7"] = string.uchar(0x2087), ["8"] = string.uchar(0x2088), ["9"] = string.uchar(0x2089),}function MoleculeEntry:format() return self.text:gsub("%d", Subscript)end--------------------------------------------------------------------- Molecule Text-- This is a subclass of an Text box. It converts digits to subscripts before-- being displayed.MoleculeText = class(Text)function MoleculeText:init(view, x, y, w, h, spinbox, molecule) Text.init(self, view, x, y, w, h, molecule.formula) self.spinbox = spinbox self.elements = molecule.elementsendfunction MoleculeText:value() return self.spinbox:value()endfunction MoleculeText:format() return self.text:gsub("%d", Subscript)end--------------------------------------------------------------------- MoleculeMolecule = class()-- Splits up elements in the formula for a molecule.-- Returns a Molecule-- .formula = molecular formula-- .elements = {element = count, ...}function Molecule.splitElements(formula) local m = {} m.formula = formula local elems = {} for elem, count in formula:gmatch("(%u%l?)(%d*)") do if #count == 0 then count = 1 else count = tonumber(count) end elems[elem] = count end m.elements = elems return mend-- Splits up molecules in the formula for a reaction.-- Returns a list of Moleculesfunction Molecule.splitMolecules(formula) local molecules = {} for mol in formula:gmatch("[^%s+]+") do molecules[#molecules + 1] = Molecule.splitElements(mol) end return moleculesend--------------------------------------------------------------------- Element TextElementText = class(Text)function ElementText:init(view, x, y, w, h, name, molecules) Text.init(self, view, x, y, w, h) self.name = name self.molecules = moleculesend-- Count total atoms in moleculesfunction ElementText:total() local total = 0 for _, m in ipairs(self.molecules) do local count = m.elements[self.name] if count then total = total + m.controller:value() * count end end return totalendfunction ElementText:format() return self:total() .. " " .. self.nameend--------------------------------------------------------------------- Beam balance---- This is a visual widget that looks like a balance. It leans in the direction-- of the heavier side or is level when the same number of elements on each side.Balance = class(Widget)-- The balance needs references to the elements on left and right sides. These-- must be ElementText objects.function Balance:init(view, x, y, leftElement, rightElement) Widget.init(self, view, x, y, self.width, self.height) self.left = leftElement self.right = rightElementendBalance.width = 80Balance.height = 18Balance.leftx = 5Balance.rightx = Balance.width - Balance.leftxBalance.topy = 0Balance.bottomy = Balance.heightBalance.midy = Balance.height / 2Balance.smallRadius = 5Balance.largeRadius = 6function Balance:paint(gc) local left = self.left:total() local right = self.right:total() local x = self.x local y = self.y local color = {0, 0, 255} -- blue local leftBeamEndpoint = {0, self.midy} local rightBeamEndpoint = {self.width, self.midy} local leftRadius = self.smallRadius local rightRadius = self.smallRadius if left > right then -- Left side is heavier color = {255, 0, 0} -- red leftBeamEndpoint = {self.leftx, self.bottomy} rightBeamEndpoint = {self.rightx, 0} leftRadius = self.largeRadius rightRadius = self.smallRadius elseif left < right then -- Right side is heavier color = {255, 0, 0} leftBeamEndpoint = {self.leftx, 0} rightBeamEndpoint = {self.rightx, self.bottomy} leftRadius = self.smallRadius rightRadius = self.largeRadius end gc:setPen("thin", "smooth") gc:setColorRGB(unpack(color)) -- Draw pedestal local x1 = self.width/2 - 3 local y1 = self.height local x2 = self.width/2 local y2 = self.height/2 local x3 = self.width/2 + 3 local y3 = self.height gc:drawPolyLine({x1+x, y1+y, x2+x, y2+y, x3+x, y3+y, x1+x, y1+y}) -- Draw cross beam x1, y1 = unpack(leftBeamEndpoint) x2, y2 = unpack(rightBeamEndpoint) gc:drawLine(x1+x, y1+y, x2+x, y2+y) -- Draw elements local diam = 2*leftRadius y1 = y1 - diam * 0.87 gc:fillArc(x1+x, y1+y, diam, diam, 0, 360) diam = 2*rightRadius x2 = x2 - diam y2 = y2 - diam * 0.87 gc:fillArc(x2+x, y2+y, diam, diam, 0, 360)end--------------------------------------------------------------------- Edit screenfunction createEditScreen(window, input_formula, output_formula) theView = View(window) -- Title theView:add(Text(theView, 10, 2, 300, 22, "Edit Chemical Formula", "center", "b")) -- Input molecular formula input = MoleculeEntry(theView, 10, 24, 140, 22, input_formula or "", "right") theView:add(input) -- Right-pointing arrow theView:add(Text(theView, 150, 24, 20, 22, string.uchar(0x2192), "center")) -- Output molecular formula output = MoleculeEntry(theView, 170, 24, 140, 22, output_formula or "") theView:add(output) -- Balance button theView:add(Button(theView, 110, 187, 100, 22, "Balance", true, function() createBalanceScreen(window, input.text, output.text) end ))end--------------------------------------------------------------------- Balance screenfunction createBalanceScreen(window, input_formula, output_formula) theView = View(window) -- Title theView:add(Text(theView, 10, 2, 300, 22, "Balance Chemical Formula", "center", "b")) local input_molecules = Molecule.splitMolecules(input_formula) local output_molecules = Molecule.splitMolecules(output_formula) -- Survey the elements local elements = {} for _, m in ipairs(input_molecules) do for elem, count in pairs(m.elements) do elements[elem] = true end end local x = 10 local y = 33 local sb_width = 15 -- Display reactants for i, m in ipairs(input_molecules) do local sb = SpinBox(theView, x, y, 15, 22) theView:add(sb) x = x + sb_width local molecule = MoleculeText(theView, x, y, 0, 0, sb, m) theView:add(molecule) m.controller = molecule x = x + molecule:stringWidth() if i < #input_molecules then local plus = Text(theView, x, y, 0, 0, "+") theView:add(plus) x = x + plus:stringWidth() end end -- Arrow theView:add(Text(theView, x+3, y, 14, 22, string.uchar(0x2192), "center")) x = x + 17 -- Display resultants for i, m in ipairs(output_molecules) do local sb = SpinBox(theView, x, y, 15, 22) theView:add(sb) x = x + sb_width local molecule = MoleculeText(theView, x, y, 0, 0, sb, m) theView:add(molecule) m.controller = molecule x = x + molecule:stringWidth() if i < #output_molecules then local plus = Text(theView, x, y, 0, 0, "+") theView:add(plus) x = x + plus:stringWidth() end end -- Display elements local col1x = 30 local col3x = 210 y = 75 for elename, _ in pairs(elements) do -- Reactants local left = ElementText(theView, col1x, y, 70, 22, elename, input_molecules) theView:add(left) -- Resultants local right = ElementText(theView, col3x, y, 70, 22, elename, output_molecules) theView:add(right) -- Balance theView:add(Balance(theView, 110, y, left, right)) y = y + 34 end -- Edit button theView:add(Button(theView, 110, y, 100, 22, "Edit", true, function() createEditScreen(window, input_formula, output_formula) end ))endcreateEditScreen(platform.window)--------------------------------------------------------------------- Event handlersfunction on.resize() print("resize")endfunction on.create() print("create")endfunction on.charIn(ch) theView:sendStringToFocus(ch)endfunction on.backspaceKey(ch) theView:sendBackspaceToFocus()endfunction on.tabKey() theView:tabForward()endfunction on.backtabKey() theView:tabBackward()endfunction on.paint(gc) theView:paint(gc)endfunction on.enterKey(gc) theView:enterHandler()endfunction on.mouseDown(x, y) theView:onMouseDown(x, y)endfunction on.mouseMove(x, y) theView:onMouseMove(x, y)endfunction on.mouseUp(x, y) theView:onMouseUp(x, y)endfunction on.save() return {input = input.text, output = output.text}endfunction on.restore(state) createEditScreen(platform.window, state.input, state.output)endGenerated by the BBify'r (http://clrhome.org/resources/bbify/)
→Generated by the BBify'r (http://clrhome.org/resources/bbify/)
"THE GAME→Str00For(I,1,830Ans+inData("ABCDEFGHIJKLMNOPQRSTUVWXYZtheta :?",sub(Str0,I,1EndAns→J" →Str0JFor(I,1,8Ans/30→Jsub("ABCDEFGHIJKLMNOPQRSTUVWXYZtheta :?",round(3�float{Ans),�),1)+Str0→Str0nib{JEndStr0Generated by the BBify'r (http://clrhome.org/resources/bbify/)
"THE GAME→Str00For(I,1,830Ans+inData("ABCDEFGHIJKLMNOPQRSTUVWXYZtheta :?",sub(Str0,I,1EndAns→J" →Str0JFor(I,1,8Ans/30→Jsub("ABCDEFGHIJKLMNOPQRSTUVWXYZtheta :?",round(3�float{Ans),�),1)+Str0→Str0nib{JEndStr0