├── .gitignore ├── LICENSE ├── README.md ├── package.cmd ├── src ├── gui.lua ├── gui │ ├── button.lua │ ├── check.lua │ ├── common.lua │ ├── common │ │ ├── anchor.lua │ │ ├── client_size.lua │ │ ├── color.lua │ │ ├── destroyable.lua │ │ ├── focusable.lua │ │ ├── label.lua │ │ ├── position.lua │ │ ├── resizable.lua │ │ ├── size.lua │ │ ├── text_color.lua │ │ └── value.lua │ ├── dialog.lua │ ├── file_dialog.lua │ ├── image.lua │ ├── keyboard.lua │ ├── label.lua │ ├── menu.lua │ ├── menu_bar.lua │ ├── menu_item.lua │ ├── mouse.lua │ ├── text_box.lua │ ├── timer.lua │ └── window.lua ├── plugin.lua └── test │ ├── button.lua │ ├── main.lua │ ├── menu.lua │ ├── text_box.lua │ ├── unit_test.lua │ └── window.lua └── zbplugin.lua /.gitignore: -------------------------------------------------------------------------------- 1 | src/untitled.lua 2 | package/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Open Software License ("OSL") v 3.0 2 | 3 | This Open Software License (the "License") applies to any original work of 4 | authorship (the "Original Work") whose owner (the "Licensor") has placed the 5 | following licensing notice adjacent to the copyright notice for the Original 6 | Work: 7 | 8 | Licensed under the Open Software License version 3.0 9 | 10 | 1) Grant of Copyright License. Licensor grants You a worldwide, royalty-free, 11 | non-exclusive, sublicensable license, for the duration of the copyright, to do 12 | the following: 13 | 14 | a) to reproduce the Original Work in copies, either alone or as part of a 15 | collective work; 16 | 17 | b) to translate, adapt, alter, transform, modify, or arrange the Original 18 | Work, thereby creating derivative works ("Derivative Works") based 19 | upon the Original Work; 20 | 21 | c) to distribute or communicate copies of the Original Work and Derivative 22 | Works to the public, with the proviso that copies of Original Work or 23 | Derivative Works that You distribute or communicate shall be licensed 24 | under this Open Software License; 25 | 26 | d) to perform the Original Work publicly; and 27 | 28 | e) to display the Original Work publicly. 29 | 30 | 2) Grant of Patent License. Licensor grants You a worldwide, royalty-free, 31 | non-exclusive, sublicensable license, under patent claims owned or controlled 32 | by the Licensor that are embodied in the Original Work as furnished by the 33 | Licensor, for the duration of the patents, to make, use, sell, offer for sale, 34 | have made, and import the Original Work and Derivative Works. 35 | 36 | 3) Grant of Source Code License. The term "Source Code" means the preferred 37 | form of the Original Work for making modifications to it and all available 38 | documentation describing how to modify the Original Work. Licensor agrees to 39 | provide a machine-readable copy of the Source Code of the Original Work along 40 | with each copy of the Original Work that Licensor distributes. Licensor 41 | reserves the right to satisfy this obligation by placing a machine-readable 42 | copy of the Source Code in an information repository reasonably calculated to 43 | permit inexpensive and convenient access by You for as long as Licensor 44 | continues to distribute the Original Work. 45 | 46 | 4) Exclusions From License Grant. Neither the names of Licensor, nor the names 47 | of any contributors to the Original Work, nor any of their trademarks or 48 | service marks, may be used to endorse or promote products derived from this 49 | Original Work without express prior permission of the Licensor. Except as 50 | expressly stated herein, nothing in this License grants any license to 51 | Licensor's trademarks, copyrights, patents, trade secrets or any other 52 | intellectual property. No patent license is granted to make, use, sell, offer 53 | for sale, have made, or import embodiments of any patent claims other than the 54 | licensed claims defined in Section 2. No license is granted to the trademarks 55 | of Licensor even if such marks are included in the Original Work. Nothing in 56 | this License shall be interpreted to prohibit Licensor from licensing under 57 | terms different from this License any Original Work that Licensor otherwise 58 | would have a right to license. 59 | 60 | 5) External Deployment. The term "External Deployment" means the use, 61 | distribution, or communication of the Original Work or Derivative Works in any 62 | way such that the Original Work or Derivative Works may be used by anyone 63 | other than You, whether those works are distributed or communicated to those 64 | persons or made available as an application intended for use over a network. 65 | As an express condition for the grants of license hereunder, You must treat 66 | any External Deployment by You of the Original Work or a Derivative Work as a 67 | distribution under section 1(c). 68 | 69 | 6) Attribution Rights. You must retain, in the Source Code of any Derivative 70 | Works that You create, all copyright, patent, or trademark notices from the 71 | Source Code of the Original Work, as well as any notices of licensing and any 72 | descriptive text identified therein as an "Attribution Notice." You must cause 73 | the Source Code for any Derivative Works that You create to carry a prominent 74 | Attribution Notice reasonably calculated to inform recipients that You have 75 | modified the Original Work. 76 | 77 | 7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that 78 | the copyright in and to the Original Work and the patent rights granted herein 79 | by Licensor are owned by the Licensor or are sublicensed to You under the 80 | terms of this License with the permission of the contributor(s) of those 81 | copyrights and patent rights. Except as expressly stated in the immediately 82 | preceding sentence, the Original Work is provided under this License on an "AS 83 | IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without 84 | limitation, the warranties of non-infringement, merchantability or fitness for 85 | a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK 86 | IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this 87 | License. No license to the Original Work is granted by this License except 88 | under this disclaimer. 89 | 90 | 8) Limitation of Liability. Under no circumstances and under no legal theory, 91 | whether in tort (including negligence), contract, or otherwise, shall the 92 | Licensor be liable to anyone for any indirect, special, incidental, or 93 | consequential damages of any character arising as a result of this License or 94 | the use of the Original Work including, without limitation, damages for loss 95 | of goodwill, work stoppage, computer failure or malfunction, or any and all 96 | other commercial damages or losses. This limitation of liability shall not 97 | apply to the extent applicable law prohibits such limitation. 98 | 99 | 9) Acceptance and Termination. If, at any time, You expressly assented to this 100 | License, that assent indicates your clear and irrevocable acceptance of this 101 | License and all of its terms and conditions. If You distribute or communicate 102 | copies of the Original Work or a Derivative Work, You must make a reasonable 103 | effort under the circumstances to obtain the express assent of recipients to 104 | the terms of this License. This License conditions your rights to undertake 105 | the activities listed in Section 1, including your right to create Derivative 106 | Works based upon the Original Work, and doing so without honoring these terms 107 | and conditions is prohibited by copyright law and international treaty. 108 | Nothing in this License is intended to affect copyright exceptions and 109 | limitations (including "fair use" or "fair dealing"). This License shall 110 | terminate immediately and You may no longer exercise any of the rights granted 111 | to You by this License upon your failure to honor the conditions in Section 112 | 1(c). 113 | 114 | 10) Termination for Patent Action. This License shall terminate automatically 115 | and You may no longer exercise any of the rights granted to You by this 116 | License as of the date You commence an action, including a cross-claim or 117 | counterclaim, against Licensor or any licensee alleging that the Original Work 118 | infringes a patent. This termination provision shall not apply for an action 119 | alleging patent infringement by combinations of the Original Work with other 120 | software or hardware. 121 | 122 | 11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this 123 | License may be brought only in the courts of a jurisdiction wherein the 124 | Licensor resides or in which Licensor conducts its primary business, and under 125 | the laws of that jurisdiction excluding its conflict-of-law provisions. The 126 | application of the United Nations Convention on Contracts for the 127 | International Sale of Goods is expressly excluded. Any use of the Original 128 | Work outside the scope of this License or after its termination shall be 129 | subject to the requirements and penalties of copyright or patent law in the 130 | appropriate jurisdiction. This section shall survive the termination of this 131 | License. 132 | 133 | 12) Attorneys' Fees. In any action to enforce the terms of this License or 134 | seeking damages relating thereto, the prevailing party shall be entitled to 135 | recover its costs and expenses, including, without limitation, reasonable 136 | attorneys' fees and costs incurred in connection with such action, including 137 | any appeal of such action. This section shall survive the termination of this 138 | License. 139 | 140 | 13) Miscellaneous. If any provision of this License is held to be 141 | unenforceable, such provision shall be reformed only to the extent necessary 142 | to make it enforceable. 143 | 144 | 14) Definition of "You" in This License. "You" throughout this License, 145 | whether in upper or lower case, means an individual or a legal entity 146 | exercising rights under, and complying with all of the terms of, this License. 147 | For legal entities, "You" includes any entity that controls, is controlled by, 148 | or is under common control with you. For purposes of this definition, 149 | "control" means (i) the power, direct or indirect, to cause the direction or 150 | management of such entity, whether by contract or otherwise, or (ii) ownership 151 | of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial 152 | ownership of such entity. 153 | 154 | 15) Right to Use. You may use the Original Work in all ways not otherwise 155 | restricted or conditioned by this License or by law, and Licensor promises not 156 | to interfere with or be responsible for such uses by You. 157 | 158 | 16) Modification of This License. This License is Copyright © 2005 Lawrence 159 | Rosen. Permission is granted to copy, distribute, or communicate this License 160 | without modification. Nothing in this License permits You to modify this 161 | License as applied to the Original Work or to Derivative Works. However, You 162 | may modify the text of this License and copy, distribute or communicate your 163 | modified version (the "Modified License") and apply it to other original works 164 | of authorship subject to the following conditions: (i) You may not indicate in 165 | any way that your Modified License is the "Open Software License" or "OSL" and 166 | you may not use those names in the name of your Modified License; (ii) You 167 | must replace the notice specified in the first paragraph above with the notice 168 | "Licensed under " or with a notice of your own 169 | that is not confusingly similar to the notice in this License; and (iii) You 170 | may not claim that your original works are open source software unless your 171 | Modified License has been approved by Open Source Initiative (OSI) and You 172 | comply with its license review and certification process. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Lua GUI 2 | 3 | Lua GUI is a library for creating GUIs with Lua aimed at unexperienced programmers. The library is being developed with the following goals in mind. 4 | 5 | * Easy to use. 6 | * Using conventions common to Lua. (It shouldn't feel like a wrapper around, for example, a C++ library.) 7 | 8 | ##Installation 9 | 10 | Just put the entire contents of the src-folder somewhere that's in Lua's package path. 11 | 12 | If you are using [ZeroBrane Studio](http://studio.zerobrane.com/) and you have [Brane Plug](https://github.com/williamwilling/braneplug) installed, you can use that package manager to download and install Lua GUI. 13 | 14 | ##Documentation 15 | You can find a reference manual and code samples on the [wiki](https://github.com/williamwilling/luagui/wiki). 16 | 17 | ##License 18 | Lua GUI is licensed under the terms of the [Open Software License 3.0](http://opensource.org/licenses/OSL-3.0). You should read the entire license text, but here is the gist. 19 | 20 | * You can use the Lua GUI source code as is for pretty much any purpose you want. 21 | * You can create an application that uses Lua GUI and release your application under whatever terms you like. 22 | * If you modify Lua GUI in any way, you must release the source code of your modifications under the Open Software License 3.0. 23 | * Don't pretend you wrote Lua GUI. 24 | * Don't blame me when something goes wrong. -------------------------------------------------------------------------------- /package.cmd: -------------------------------------------------------------------------------- 1 | md package 2 | copy LICENSE package 3 | copy zbplugin.lua package 4 | copy src\gui.lua package 5 | copy src\plugin.lua package 6 | copy src\gui\*.lua package 7 | 8 | cd src\gui\common 9 | for %%i in (*) do copy %%i ..\..\..\package\common.%%i 10 | cd ..\..\.. 11 | -------------------------------------------------------------------------------- /src/gui.lua: -------------------------------------------------------------------------------- 1 | local print_backup = print 2 | 3 | require 'wx' 4 | local Window = require 'gui.window' 5 | local FileDialog = require 'gui.file_dialog' 6 | local Timer = require 'gui.timer' 7 | 8 | gui = { 9 | mouse = require 'gui.mouse', 10 | keyboard = require 'gui.keyboard', 11 | garbage = {}, 12 | version = 4 13 | } 14 | 15 | wx.wxGetApp():Connect(wx.wxEVT_IDLE, function() 16 | for _, control in ipairs(gui.garbage) do 17 | control:Destroy() 18 | end 19 | 20 | gui.garbage = {} 21 | end) 22 | 23 | function gui.create_window() 24 | return Window.create() 25 | end 26 | 27 | function gui.create_file_dialog() 28 | return FileDialog.create() 29 | end 30 | 31 | function gui.create_timer() 32 | return Timer.create() 33 | end 34 | 35 | function gui.run() 36 | wx.wxGetApp():MainLoop() 37 | end 38 | 39 | print = print_backup 40 | 41 | function gui.reload() 42 | package.loaded.gui = nil 43 | 44 | for name,_ in pairs(package.loaded) do 45 | if string.match(name, 'gui%..+') then 46 | package.loaded[name] = nil 47 | end 48 | end 49 | 50 | return require 'gui' 51 | end -------------------------------------------------------------------------------- /src/gui/button.lua: -------------------------------------------------------------------------------- 1 | local common = require 'gui.common' 2 | 3 | local Button = {} 4 | common.is_destroyable(Button) 5 | common.is_focusable(Button) 6 | 7 | local metatable = common.create_metatable(Button) 8 | common.add_position(metatable, 'button') 9 | common.add_size(metatable, 'button') 10 | common.add_label(metatable, 'button', 'text') 11 | common.add_anchor(metatable, 'button') 12 | common.add_color(metatable, 'button') 13 | common.add_text_color(metatable, 'button') 14 | 15 | function metatable.set_word_wrap(object, value) 16 | if value and string.sub(object.text, -1) ~= '\n' then 17 | object.wx:SetLabel(object.text .. '\n') 18 | end 19 | 20 | if not value and string.sub(object.text, -1) == '\n' then 21 | object.wx:SetLabel(string.sub(object.text, 1, -2)) 22 | end 23 | end 24 | 25 | function metatable.set_alignment(object, value) 26 | local flags = { 27 | left = wx.wxBU_LEFT, 28 | right = wx.wxBU_RIGHT, 29 | top = wx.wxBU_TOP, 30 | bottom = wx.wxBU_BOTTOM 31 | } 32 | 33 | local style = 0 34 | for k, v in pairs(flags) do 35 | if string.match(value, k) then 36 | style = style + v 37 | end 38 | end 39 | 40 | object.wx:SetWindowStyleFlag(style) 41 | end 42 | 43 | local base_set_text = metatable.set_text 44 | function metatable.set_text(object, value) 45 | local result = base_set_text(object, value) 46 | object.word_wrap = object.word_wrap 47 | 48 | return result 49 | end 50 | 51 | function Button.create(parent) 52 | local button = { 53 | parent = parent, 54 | wx_events = {} 55 | } 56 | 57 | button.wx = wx.wxButton( 58 | parent.wx_panel or parent.wx, 59 | wx.wxID_ANY, 60 | '', 61 | wx.wxDefaultPosition, 62 | wx.wxDefaultSize) 63 | 64 | common.propagate_events(button) 65 | common.add_mouse_events(button) 66 | common.add_anchor_event(button) 67 | common.add_event(button, 'on_click', wx.wxEVT_COMMAND_BUTTON_CLICKED) 68 | 69 | setmetatable(button, metatable) 70 | button.anchor = 'top left' 71 | button.alignment = 'center' 72 | 73 | return button 74 | end 75 | 76 | function Button:click() 77 | local event = wx.wxCommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED) 78 | self.wx:ProcessEvent(event) 79 | end 80 | 81 | return Button -------------------------------------------------------------------------------- /src/gui/check.lua: -------------------------------------------------------------------------------- 1 | local check = {} 2 | 3 | local articles = { a = 'an', e = 'an', i = 'an', o = 'an' } 4 | local article_exceptions = {} 5 | 6 | local function add_article(word) 7 | local first_letter = string.sub(word, 1, 1) 8 | local article = article_exceptions[word] or articles[first_letter] or 'a' 9 | return article .. ' ' .. word 10 | end 11 | 12 | local function describe_type(type_name) 13 | if type_name == 'nil' then 14 | return 'a nil value' 15 | else 16 | return add_article(type_name) 17 | end 18 | end 19 | 20 | local function contains(table, value) 21 | for _,v in ipairs(table) do 22 | if v == value then 23 | return true 24 | end 25 | end 26 | 27 | return false 28 | end 29 | 30 | function check.parameter_type(expected, value, object_description, property_name) 31 | if type(expected) == 'string' then 32 | expected = { expected } 33 | end 34 | 35 | if not contains(expected, type(value)) then 36 | local message = string.format('The %s of %s must be %s, not %s.', 37 | property_name, 38 | add_article(object_description), 39 | describe_type(expected[1]), 40 | describe_type(type(value))) 41 | 42 | error(message, 4) 43 | end 44 | end 45 | 46 | return check -------------------------------------------------------------------------------- /src/gui/common.lua: -------------------------------------------------------------------------------- 1 | local Keyboard = require 'gui.keyboard' 2 | 3 | local common = {} 4 | common.add_position = require 'gui.common.position' 5 | common.add_client_size = require 'gui.common.client_size' 6 | common.add_size = require 'gui.common.size' 7 | common.add_label = require 'gui.common.label' 8 | common.add_value = require 'gui.common.value' 9 | common.add_anchor = require 'gui.common.anchor' 10 | common.add_color = require 'gui.common.color' 11 | common.add_text_color = require 'gui.common.text_color' 12 | common.add_resizable = require 'gui.common.resizable' 13 | common.is_destroyable = require 'gui.common.destroyable' 14 | common.is_focusable = require 'gui.common.focusable' 15 | 16 | function common.create_metatable(class) 17 | return { 18 | -- Handles the setting of properties. 19 | -- object: the object on which to set the property 20 | -- key: the name of the property 21 | -- value: the value the property should be set to 22 | __newindex = function(object, key, value) 23 | local self = getmetatable(object) 24 | local getter = self['get_' .. key] 25 | local setter = self['set_' .. key] 26 | 27 | -- If there is no getter and no setter for the specified key, then it's 28 | -- business as usual and we shouldn't interfere. 29 | if getter == nil and setter == nil then 30 | rawset(object, key, value) 31 | end 32 | 33 | -- If there is a setter for the specified key, we should store the value 34 | -- returned by the setter somewhere, so the getter can access it later. 35 | -- We create a special value table for the object to do this and store 36 | -- the value table in the metatable. 37 | if setter ~= nil then 38 | self[object] = self[object] or {} 39 | self[object][key] = setter(object, value) or value 40 | end 41 | 42 | -- If there is a getter for the specified key, but no setter, we should 43 | -- store the raw value in the value table, so the getter can access it 44 | -- later. We can't store the value in the object itself, because then 45 | -- the __index metamethod won't be called anymore. 46 | if getter ~= nil and setter == nil then 47 | self[object] = self[object] or {} 48 | self[object][key] = value 49 | end 50 | end, 51 | 52 | -- Handles the getting of properties. 53 | -- object: the object of which to get the property 54 | -- key: the name of the property 55 | -- returns: the value of the property 56 | __index = function(object, key) 57 | local self = getmetatable(object) 58 | local getter = self['get_' .. key] 59 | local setter = self['set_' .. key] 60 | 61 | -- If there is a getter for the specified key, call it and return its 62 | -- value. 63 | if getter ~= nil then 64 | return getter(object) 65 | end 66 | 67 | -- If there is no getter for the specified key, but there is a setter, 68 | -- then the setter might have stored a value in the value table. If not, 69 | -- the setter hasn't been called yet and we should return nil. 70 | if setter ~= nil then 71 | if self[object] == nil then 72 | return nil 73 | end 74 | 75 | return self[object][key] 76 | end 77 | 78 | -- If there is no getter and no setter for the specified key, we should 79 | -- treat the object as a normal table. If the object doesn't contain the 80 | -- specified key, look up the key in the object's class. 81 | return rawget(object, key) or class[key] 82 | end 83 | } 84 | end 85 | 86 | function common.add_event(object, event_name, wx_event, ...) 87 | local params = { ... } 88 | local trigger_event = function(event) 89 | if wx_event == wx.wxEVT_KEY_DOWN then 90 | event:Skip() 91 | end 92 | 93 | if type(object[event_name]) == 'function' then 94 | if type(params[1]) == 'function' then 95 | object[event_name](object, params[1](event)) 96 | else 97 | object[event_name](object, unpack(params)) 98 | end 99 | end 100 | end 101 | 102 | local event_source = object.wx 103 | if object.wx_panel and wx_event ~= wx.wxEVT_MOVE then 104 | event_source = object.wx_panel 105 | end 106 | 107 | event_source:Connect(wx_event, trigger_event) 108 | 109 | -- Store the code that unregisters the event, so we can call it when the 110 | -- object is destroyed. Note that the code is only stored if the object 111 | -- has a wx_events property. 112 | table.insert(object.wx_events or {}, function() 113 | event_source:Disconnect(wx.wxID_ANY, wx.wxID_ANY, wx_event) 114 | end) 115 | end 116 | 117 | function common.propagate_events(object, wx_events) 118 | local propagate = function(event) 119 | event:ResumePropagation(1) 120 | event:Skip() 121 | end 122 | 123 | wx_events = wx_events or { 124 | wx.wxEVT_MOTION, 125 | wx.wxEVT_LEFT_DOWN, 126 | wx.wxEVT_LEFT_UP, 127 | wx.wxEVT_MIDDLE_DOWN, 128 | wx.wxEVT_MIDDLE_UP, 129 | wx.wxEVT_RIGHT_UP, 130 | wx.wxEVT_RIGHT_DOWN, 131 | wx.wxEVT_KEY_UP, 132 | wx.wxEVT_KEY_DOWN } 133 | 134 | for _, wx_event in ipairs(wx_events) do 135 | object.wx:Connect(wx_event, propagate) 136 | end 137 | end 138 | 139 | function common.add_anchor_event(object, f) 140 | object.parent.wx:Connect(wx.wxEVT_SIZE, function(event) 141 | local metatable = getmetatable(object) 142 | if metatable then 143 | metatable.update_anchor(object) 144 | 145 | if type(f) == 'function' then 146 | f() 147 | end 148 | end 149 | 150 | event:Skip() 151 | end) 152 | end 153 | 154 | function common.add_mouse_events(object) 155 | common.mouse_listeners = common.mouse_listeners or {} 156 | table.insert(common.mouse_listeners, object) 157 | 158 | -- Store the code that unsubscribed from mouse events, so we can call it when 159 | -- the object is destroyed. Note that the code is only stored if the object 160 | -- has a wx_events property. 161 | table.insert(object.wx_events or {}, function() 162 | for i, v in ipairs(common.mouse_listeners) do 163 | if v == object then 164 | table.remove(common.mouse_listeners, i) 165 | break 166 | end 167 | end 168 | end) 169 | end 170 | 171 | function common.add_keyboard_events(object) 172 | local function process_keyboard_event(event) 173 | local key = event:GetKeyCode() 174 | local modifiers = { 175 | shift = event:ShiftDown(), 176 | ctrl = event:ControlDown(), 177 | alt = event:AltDown(), 178 | windows = event:MetaDown(), 179 | apple = event:MetaDown(), 180 | cmd = event:CmdDown(), 181 | } 182 | modifiers.any = shift or ctrl or alt or windows or apple or cmd 183 | 184 | return Keyboard.key_names[key], modifiers 185 | end 186 | 187 | common.add_event(object, 'on_key_up', wx.wxEVT_KEY_UP, process_keyboard_event) 188 | common.add_event(object, 'on_key_down', wx.wxEVT_KEY_DOWN, process_keyboard_event) 189 | end 190 | 191 | function common.forward_mouse_events(object) 192 | -- Since event propagation for mouse events doesn't work properly in wxLua, the window has 193 | -- to take care of raising mouse events on interested controls. 194 | local function send_mouse_event(wx_event, handler_name, param) 195 | return function(event) 196 | event:Skip() 197 | 198 | for _, mouse_listener in ipairs(common.mouse_listeners) do 199 | if mouse_listener[handler_name] ~= nil then 200 | local x, y, width, height 201 | 202 | if mouse_listener.wx == nil then -- is this an image? 203 | x, y = object.wx:ScreenToClient(wx.wxGetMousePosition()):GetXY() 204 | x = x - mouse_listener.x 205 | y = y - mouse_listener.y 206 | width = mouse_listener.width 207 | height = mouse_listener.height 208 | else 209 | x, y = mouse_listener.wx:ScreenToClient(wx.wxGetMousePosition()):GetXY() 210 | width = mouse_listener.wx:GetSize():GetWidth() 211 | height = mouse_listener.wx:GetSize():GetHeight() 212 | end 213 | 214 | if x >= 0 and y >= 0 and x < width and y < height then 215 | if param == nil then 216 | mouse_listener[handler_name](mouse_listener, x, y) 217 | else 218 | mouse_listener[handler_name](mouse_listener, param, x, y) 219 | end 220 | end 221 | end 222 | end 223 | end 224 | end 225 | 226 | object.wx_panel:Connect(wx.wxEVT_LEFT_UP, send_mouse_event(wx.wxEVT_LEFT_DOWN, 'on_mouse_up', 'left')) 227 | object.wx_panel:Connect(wx.wxEVT_LEFT_DOWN, send_mouse_event(wx.wxEVT_LEFT_DOWN, 'on_mouse_down', 'left')) 228 | object.wx_panel:Connect(wx.wxEVT_MIDDLE_UP, send_mouse_event(wx.wxEVT_MIDDLE_DOWN, 'on_mouse_up', 'middle')) 229 | object.wx_panel:Connect(wx.wxEVT_MIDDLE_DOWN, send_mouse_event(wx.wxEVT_MIDDLE_DOWN, 'on_mouse_down', 'middle')) 230 | object.wx_panel:Connect(wx.wxEVT_RIGHT_UP, send_mouse_event(wx.wxEVT_RIGHT_DOWN, 'on_mouse_up', 'right')) 231 | object.wx_panel:Connect(wx.wxEVT_RIGHT_DOWN, send_mouse_event(wx.wxEVT_RIGHT_DOWN, 'on_mouse_down', 'right')) 232 | 233 | -- Tell the global mouse object its coordinates relative to this window. Otherwise, the mouse 234 | -- object can only know the screen coordinates. 235 | object.wx_panel:Connect(wx.wxEVT_MOTION, function(event) 236 | gui.mouse.x, gui.mouse.y = object.wx:ScreenToClient(wx.wxGetMousePosition()):GetXY() 237 | send_mouse_event(wx.wxEVT_MOTION, 'on_mouse_move')(event) 238 | end) 239 | end 240 | 241 | function common.paint_images(window) 242 | if #window.images == 0 then return end 243 | local dc = wx.wxPaintDC(window.wx_panel) 244 | 245 | for _, image in ipairs(window.images) do 246 | local bitmap = wx.wxBitmap(image.image:Scale(image.width, image.height)) 247 | dc:DrawBitmap(bitmap, image.x, image.y, image.image:HasMask()) 248 | bitmap:delete() 249 | end 250 | 251 | dc:delete() 252 | end 253 | 254 | return common -------------------------------------------------------------------------------- /src/gui/common/anchor.lua: -------------------------------------------------------------------------------- 1 | local check = require 'gui.check' 2 | 3 | return function(metatable, object_description) 4 | if metatable.set_x == nil or metatable.set_y == nil then 5 | error('You must give a control position capabilities before you can give it anchor capabilities.', 2) 6 | end 7 | 8 | if metatable.set_width == nil or metatable.set_height == nil then 9 | error('You must give a control size capabilities before you can give it anchor capabilities.', 2) 10 | end 11 | 12 | local set_x = metatable.set_x 13 | local set_y = metatable.set_y 14 | local set_width = metatable.set_width 15 | local set_height = metatable.set_height 16 | 17 | metatable.set_x = function(object, value) 18 | check.parameter_type('number', value, object_description, 'x-coordinate') 19 | 20 | object.anchoring.left = value 21 | object.anchoring.right = object.parent.width - object.width - object.anchoring.left 22 | 23 | return set_x(object, value) 24 | end 25 | 26 | metatable.set_y = function(object, value) 27 | check.parameter_type('number', value, object_description, 'y-coordinate') 28 | 29 | object.anchoring.top = value 30 | object.anchoring.bottom = object.parent.height - object.height - object.anchoring.top 31 | 32 | return set_y(object, value) 33 | end 34 | 35 | metatable.set_width = function(object, value) 36 | check.parameter_type('number', value, object_description, 'width') 37 | 38 | object.anchoring.right = object.parent.width - object.x - value 39 | object.anchoring.left = object.x 40 | 41 | return set_width(object, value) 42 | end 43 | 44 | metatable.set_height = function(object, value) 45 | check.parameter_type('number', value, object_description, 'height') 46 | 47 | object.anchoring.bottom = object.parent.height - object.y - value 48 | object.anchoring.top = object.y 49 | 50 | return set_height(object, value) 51 | end 52 | 53 | metatable.set_anchor = function(object, value) 54 | check.parameter_type('string', value, object_description, 'anchor') 55 | 56 | object.anchoring = { 57 | left = object.x, 58 | right = object.parent.width - object.x - object.width, 59 | top = object.y, 60 | bottom = object.parent.height - object.y - object.height 61 | } 62 | end 63 | 64 | metatable.update_anchor = function(object) 65 | local x, y, width, height 66 | 67 | local anchor_left = string.find(object.anchor, 'left') or string.find(object.anchor, 'all') 68 | local anchor_right = string.find(object.anchor, 'right') or string.find(object.anchor, 'all') 69 | local anchor_top = string.find(object.anchor, 'top') or string.find(object.anchor, 'all') 70 | local anchor_bottom = string.find(object.anchor, 'bottom') or string.find(object.anchor, 'all') 71 | 72 | if anchor_left and anchor_right then 73 | x = object.x 74 | width = object.parent.width - object.x - object.anchoring.right 75 | elseif anchor_left and not anchor_right then 76 | x = object.x 77 | width = object.width 78 | elseif not anchor_left and anchor_right then 79 | x = object.parent.width - object.anchoring.right - object.width 80 | width = object.width 81 | else 82 | local difference = (object.parent.width - object.width) - (object.anchoring.left + object.anchoring.right) 83 | x = object.anchoring.left + difference / 2 84 | width = object.width 85 | end 86 | 87 | if anchor_top and anchor_bottom then 88 | y = object.y 89 | height = object.parent.height - object.y - object.anchoring.bottom 90 | elseif anchor_top and not anchor_bottom then 91 | y = object.y 92 | height = object.height 93 | elseif not anchor_top and anchor_bottom then 94 | y = object.parent.height - object.anchoring.bottom - object.height 95 | height = object.height 96 | else 97 | local difference = (object.parent.height - object.height) - (object.anchoring.top + object.anchoring.bottom) 98 | y = object.anchoring.top + difference / 2 99 | height = object.height 100 | end 101 | 102 | if object.wx == nil then -- is the object an image? images don't wrap a wx control 103 | object.x = x 104 | object.y = y 105 | object.width = width 106 | object.height = height 107 | else 108 | object.wx:Move(x, y) 109 | object.wx:SetSize(width, height) -- NOTE: this line assumes that anchored controls are always 110 | -- sized based on the entire control, not just the client 111 | -- area. If this ever becomes not true, we'll have to figure 112 | -- out whether to call SetSize() or SetClientSize(). 113 | end 114 | end 115 | end -------------------------------------------------------------------------------- /src/gui/common/client_size.lua: -------------------------------------------------------------------------------- 1 | local check = require 'gui.check' 2 | 3 | return function(metatable, object_description) 4 | metatable.get_width = function(object) 5 | return object.wx:GetClientSize():GetWidth() 6 | end 7 | 8 | metatable.get_height = function(object) 9 | return object.wx:GetClientSize():GetHeight() 10 | end 11 | 12 | metatable.set_width = function(object, value) 13 | check.parameter_type('number', value, object_description, 'width') 14 | 15 | local height = object.height 16 | object.wx:SetClientSize(value, height) 17 | 18 | return object.wx:GetClientSize():GetWidth() 19 | end 20 | 21 | metatable.set_height = function(object, value) 22 | check.parameter_type('number', value, object_description, 'height') 23 | 24 | local width = object.width 25 | object.wx:SetClientSize(width, value) 26 | 27 | return object.wx:GetClientSize():GetHeight() 28 | end 29 | end -------------------------------------------------------------------------------- /src/gui/common/color.lua: -------------------------------------------------------------------------------- 1 | local check = require 'gui.check' 2 | 3 | return function(metatable, object_description) 4 | metatable.get_background_color = function(object) 5 | local color = object.wx:GetBackgroundColour() 6 | return { 7 | red = color:Red() / 255, 8 | green = color:Green() / 255, 9 | blue = color:Blue() / 255, 10 | alpha = color:Alpha() / 255 11 | } 12 | end 13 | 14 | metatable.set_background_color = function(object, value) 15 | check.parameter_type('table', value, object_description, 'background color') 16 | 17 | local red, green, blue, alpha 18 | 19 | if value.red ~= nil or value.blue ~= nil or value.green ~= nil or value.alpha ~= nil then 20 | red = value.red or 0 21 | green = value.green or 0 22 | blue = value.blue or 0 23 | alpha = value.alpha or 1 24 | else 25 | red = value[1] or 0 26 | green = value[2] or 0 27 | blue = value[3] or 0 28 | alpha = value[4] or 1 29 | end 30 | 31 | local color = wx.wxColour( 32 | math.ceil(red * 255), 33 | math.ceil(green * 255), 34 | math.ceil(blue * 255), 35 | math.ceil(alpha * 255)) 36 | 37 | object.wx:SetBackgroundColour(color) 38 | object.wx:Refresh() 39 | end 40 | end -------------------------------------------------------------------------------- /src/gui/common/destroyable.lua: -------------------------------------------------------------------------------- 1 | return function(object) 2 | object.destroy = function(self) 3 | -- Unregister the control's event handlers. 4 | for _, unregister_event_handler in ipairs(self.wx_events) do 5 | unregister_event_handler() 6 | end 7 | 8 | -- Mark the control for destruction 9 | table.insert(gui.garbage, self.wx) 10 | 11 | -- Make sure that you can't inadvertently do anything with the destroyed 12 | -- control anymore. 13 | setmetatable(self, nil) 14 | 15 | for k in pairs(self) do 16 | self[k] = nil 17 | end 18 | end 19 | end -------------------------------------------------------------------------------- /src/gui/common/focusable.lua: -------------------------------------------------------------------------------- 1 | return function(object) 2 | object.focus = function(self) 3 | self.wx:SetFocus() 4 | end 5 | end -------------------------------------------------------------------------------- /src/gui/common/label.lua: -------------------------------------------------------------------------------- 1 | local check = require 'gui.check' 2 | 3 | return function(metatable, object_description, property_name) 4 | property_name = property_name or 'label' 5 | 6 | metatable['get_' .. property_name] = function(object) 7 | return object.wx:GetLabel() 8 | end 9 | 10 | metatable['set_' .. property_name] = function(object, value) 11 | check.parameter_type({ 'string', 'number' }, value, object_description, property_name) 12 | 13 | object.wx:SetLabel(tostring(value)) 14 | end 15 | end -------------------------------------------------------------------------------- /src/gui/common/position.lua: -------------------------------------------------------------------------------- 1 | local check = require 'gui.check' 2 | 3 | return function(metatable, object_description) 4 | metatable.get_x = function(object) 5 | return object.wx:GetPosition():GetX() 6 | end 7 | 8 | metatable.get_y = function(object) 9 | return object.wx:GetPosition():GetY() 10 | end 11 | 12 | metatable.set_x = function(object, value) 13 | check.parameter_type('number', value, object_description, 'x-coordinate') 14 | 15 | local y = object.y 16 | object.wx:Move(value, y) 17 | end 18 | 19 | metatable.set_y = function(object, value) 20 | check.parameter_type('number', value, object_description, 'y-coordinate') 21 | 22 | local x = object.x 23 | object.wx:Move(x, value) 24 | end 25 | end -------------------------------------------------------------------------------- /src/gui/common/resizable.lua: -------------------------------------------------------------------------------- 1 | local check = require 'gui.check' 2 | 3 | return function(metatable, object_description) 4 | metatable.set_resizable = function(object, value) 5 | check.parameter_type('boolean', value, object_description, 'resizable property') 6 | 7 | if value ~= object.resizable then 8 | local width = object.width 9 | local height = object.height 10 | 11 | if value then 12 | object.wx:SetWindowStyleFlag(wx.wxDEFAULT_DIALOG_STYLE + wx.wxRESIZE_BORDER) 13 | else 14 | object.wx:SetWindowStyleFlag(wx.wxDEFAULT_DIALOG_STYLE) 15 | end 16 | 17 | object.wx:Refresh() 18 | object.width = width 19 | object.height = height 20 | end 21 | 22 | return value 23 | end 24 | end -------------------------------------------------------------------------------- /src/gui/common/size.lua: -------------------------------------------------------------------------------- 1 | local check = require 'gui.check' 2 | 3 | return function(metatable, object_description) 4 | metatable.get_width = function(object) 5 | return object.wx:GetSize():GetWidth() 6 | end 7 | 8 | metatable.get_height = function(object) 9 | return object.wx:GetSize():GetHeight() 10 | end 11 | 12 | metatable.set_width = function(object, value) 13 | check.parameter_type('number', value, object_description, 'width') 14 | 15 | local height = object.height 16 | object.wx:SetSize(value, height) 17 | 18 | return object.wx:GetSize():GetWidth() 19 | end 20 | 21 | metatable.set_height = function(object, value) 22 | check.parameter_type('number', value, object_description, 'height') 23 | 24 | local width = object.width 25 | object.wx:SetSize(width, value) 26 | 27 | return object.wx:GetSize():GetHeight() 28 | end 29 | end -------------------------------------------------------------------------------- /src/gui/common/text_color.lua: -------------------------------------------------------------------------------- 1 | local check = require 'gui.check' 2 | 3 | return function(metatable, object_description) 4 | metatable.get_text_color = function(object) 5 | local color = object.wx:GetForegroundColour() 6 | return { 7 | red = color:Red() / 255, 8 | green = color:Green() / 255, 9 | blue = color:Blue() / 255, 10 | alpha = color:Alpha() / 255 11 | } 12 | end 13 | 14 | metatable.set_text_color = function(object, value) 15 | check.parameter_type('table', value, object_description, 'text color') 16 | 17 | local red, green, blue, alpha 18 | 19 | if value.red ~= nil or value.blue ~= nil or value.green ~= nil or value.alpha ~= nil then 20 | red = value.red or 0 21 | green = value.green or 0 22 | blue = value.blue or 0 23 | alpha = value.alpha or 1 24 | else 25 | red = value[1] or 0 26 | green = value[2] or 0 27 | blue = value[3] or 0 28 | alpha = value[4] or 1 29 | end 30 | 31 | local color = wx.wxColour( 32 | math.ceil(red * 255), 33 | math.ceil(green * 255), 34 | math.ceil(blue * 255), 35 | math.ceil(alpha * 255)) 36 | 37 | object.wx:SetForegroundColour(color) 38 | object.wx:Refresh() 39 | end 40 | end -------------------------------------------------------------------------------- /src/gui/common/value.lua: -------------------------------------------------------------------------------- 1 | local check = require 'gui.check' 2 | 3 | return function(metatable, object_description, property_name) 4 | metatable.get_text = function(object) 5 | return object.wx:GetValue() 6 | end 7 | 8 | metatable.set_text = function(object, value) 9 | check.parameter_type({ 'string', 'number' }, value, object_description, property_name) 10 | 11 | object.wx:SetValue(tostring(value)) 12 | end 13 | end -------------------------------------------------------------------------------- /src/gui/dialog.lua: -------------------------------------------------------------------------------- 1 | local common = require 'gui.common' 2 | local Button = require 'gui.button' 3 | local Image = require 'gui.image' 4 | local Label = require 'gui.label' 5 | local TextBox = require 'gui.text_box' 6 | 7 | local Dialog = {} 8 | local metatable = common.create_metatable(Dialog) 9 | common.add_position(metatable, 'dialog') 10 | common.add_client_size(metatable, 'dialog') 11 | common.add_label(metatable, 'dialog', 'title') 12 | common.add_color(metatable, 'dialog') 13 | common.add_resizable(metatable, 'dialog') 14 | 15 | function Dialog.create(parent) 16 | local dialog = { 17 | parent = parent, 18 | resizable = true, 19 | images = {} 20 | } 21 | 22 | -- The delay prevents the dialog window from being created before the creation of its parent has 23 | -- finished. Without the delay, it sometimes happens that the dialog is shown immediately, 24 | -- without a call to show_modal() or show_modeless(). 25 | wx.wxMilliSleep(50) 26 | 27 | dialog.wx = wx.wxDialog() 28 | dialog.wx:Create( 29 | dialog.parent.wx, 30 | wx.wxID_ANY, 31 | '', 32 | wx.wxDefaultPosition, 33 | wx.wxDefaultSize, 34 | wx.wxDEFAULT_DIALOG_STYLE + wx.wxRESIZE_BORDER) 35 | 36 | dialog.wx_panel = wx.wxPanel( 37 | dialog.wx, 38 | wx.wxID_ANY, 39 | wx.wxDefaultPosition, 40 | wx.wxDefaultSize) 41 | 42 | common.forward_mouse_events(dialog) 43 | common.add_mouse_events(dialog) 44 | common.add_keyboard_events(dialog) 45 | common.add_event(dialog, 'on_resize', wx.wxEVT_SIZE) 46 | common.add_event(dialog, 'on_move', wx.wxEVT_MOVE) 47 | 48 | dialog.wx:Connect(wx.wxEVT_CLOSE_WINDOW, function(event) 49 | if type(dialog.on_closing) ~= 'function' or dialog:on_closing() ~= false or not event:CanVeto() then 50 | dialog.wx:Destroy() 51 | end 52 | end) 53 | 54 | dialog.wx_panel:Connect(wx.wxEVT_PAINT, function(event) 55 | common.paint_images(dialog) 56 | end) 57 | 58 | setmetatable(dialog, metatable) 59 | return dialog 60 | end 61 | 62 | function Dialog:add_button() 63 | return Button.create(self) 64 | end 65 | 66 | function Dialog:add_image() 67 | return Image.create(self) 68 | end 69 | 70 | function Dialog:add_label() 71 | return Label.create(self) 72 | end 73 | 74 | function Dialog:add_text_box() 75 | return TextBox.create(self) 76 | end 77 | 78 | function Dialog:show_modal() 79 | self.wx:ShowModal() 80 | end 81 | 82 | function Dialog:show_modeless() 83 | self.wx:Show() 84 | end 85 | 86 | function Dialog:close() 87 | self.wx:Close() 88 | end 89 | 90 | return Dialog -------------------------------------------------------------------------------- /src/gui/file_dialog.lua: -------------------------------------------------------------------------------- 1 | local common = require 'gui.common' 2 | 3 | local FileDialog = {} 4 | local metatable = common.create_metatable(FileDialog) 5 | 6 | function metatable.set_filters(object, value) 7 | if (type(value) ~= 'table' and value ~= nil) then 8 | error('The filters are not specified in the correct format.', 3) 9 | end 10 | 11 | for _, filter in ipairs(value) do 12 | if type(filter) ~= 'table' or #filter < 2 then 13 | error('The filters are not specified in the correct format.', 3) 14 | end 15 | end 16 | 17 | return value 18 | end 19 | 20 | local function create_filter_string(filters) 21 | if filters == nil then 22 | return '' 23 | end 24 | 25 | local result = '' 26 | 27 | for _, filter in ipairs(filters) do 28 | local description = filter[1] 29 | local pattern = '' 30 | 31 | for i = 2, #filter do 32 | if #pattern > 0 then 33 | pattern = pattern .. ';' 34 | end 35 | 36 | pattern = pattern .. filter[i] 37 | end 38 | 39 | if #result > 0 then 40 | result = result .. '|' 41 | end 42 | 43 | result = result .. description .. '|' .. pattern 44 | end 45 | 46 | return result 47 | end 48 | 49 | function FileDialog.create(parent) 50 | local file_dialog = { 51 | parent = parent, 52 | multiselect = false, 53 | title = '', 54 | default_folder = '', 55 | default_file = '' 56 | } 57 | 58 | setmetatable(file_dialog, metatable) 59 | return file_dialog 60 | end 61 | 62 | local function show_dialog(dialog, style) 63 | if dialog.multiselect then 64 | style = style + wx.wxFD_MULTIPLE 65 | end 66 | 67 | local parent_wx = wx.NULL 68 | if parent ~= nil then 69 | parent_wx = parent.wx 70 | end 71 | 72 | local filter = create_filter_string(dialog.filters) 73 | 74 | local wx_dialog = wx.wxFileDialog(parent_wx, dialog.title, dialog.default_folder, dialog.default_file, filter, style) 75 | 76 | local result = wx_dialog:ShowModal() == wx.wxID_OK 77 | dialog.file_name = wx_dialog:GetPath() 78 | dialog.file_names = wx_dialog:GetPaths() 79 | 80 | return result 81 | end 82 | 83 | function FileDialog:open() 84 | return show_dialog(self, wx.wxFD_OPEN) 85 | end 86 | 87 | function FileDialog:save() 88 | return show_dialog(self, wx.wxFD_SAVE) 89 | end 90 | 91 | return FileDialog -------------------------------------------------------------------------------- /src/gui/image.lua: -------------------------------------------------------------------------------- 1 | local common = require 'gui.common' 2 | 3 | local Image = {} 4 | local metatable = common.create_metatable(Image) 5 | 6 | metatable.set_x = function(object, value) 7 | object.parent.wx_panel:Refresh(true, wx.wxRect(object.x, object.y, object.width, object.height)) 8 | object.parent.wx_panel:Refresh(true, wx.wxRect(value, object.y, object.width, object.height)) 9 | end 10 | 11 | metatable.set_y = function(object, value) 12 | object.parent.wx_panel:Refresh(true, wx.wxRect(object.x, object.y, object.width, object.height)) 13 | object.parent.wx_panel:Refresh(true, wx.wxRect(object.x, value, object.width, object.height)) 14 | end 15 | 16 | metatable.set_width = function(object, value) 17 | object.parent.wx_panel:Refresh(true, wx.wxRect(object.x, object.y, object.width, object.height)) 18 | object.parent.wx_panel:Refresh(true, wx.wxRect(object.x, object.y, value, object.height)) 19 | end 20 | 21 | metatable.set_height = function(object, value) 22 | object.parent.wx_panel:Refresh(true, wx.wxRect(object.x, object.y, object.width, object.height)) 23 | object.parent.wx_panel:Refresh(true, wx.wxRect(object.x, object.y, object.width, value)) 24 | end 25 | 26 | metatable.set_file_name = function(object, value) 27 | object.image = wx.wxImage(value) 28 | object.width = object.image:GetWidth() 29 | object.height = object.image:GetHeight() 30 | 31 | return value 32 | end 33 | 34 | common.add_anchor(metatable, 'image') 35 | 36 | function Image.create(parent) 37 | local image = { 38 | parent = parent, 39 | wx_events = {} 40 | } 41 | 42 | common.add_mouse_events(image) 43 | 44 | setmetatable(image, metatable) 45 | 46 | -- Set the initial values of the image, so we don't have to check for nil in 47 | -- the setters all the time. See common.create_metatable() to see how this 48 | -- table of values is used. 49 | metatable[image] = { 50 | x = 0, 51 | y = 0, 52 | width = 0, 53 | height = 0 54 | } 55 | 56 | image.anchor = 'top left' 57 | common.add_anchor_event(image) 58 | 59 | assert(parent.images, "Parent must be a window or a dialog.") 60 | table.insert(parent.images, image) 61 | 62 | return image 63 | end 64 | 65 | function Image:destroy() 66 | for i, image in ipairs(self.parent.images) do 67 | if image == self then 68 | table.remove(self.parent.images, i) 69 | break 70 | end 71 | end 72 | 73 | self.parent.wx_panel:Refresh(false, wx.wxRect(self.x, self.y, self.width, self.height)) 74 | setmetatable(self, nil) 75 | end 76 | 77 | return Image -------------------------------------------------------------------------------- /src/gui/keyboard.lua: -------------------------------------------------------------------------------- 1 | local Keyboard = {} 2 | 3 | Keyboard.key_up = {} 4 | setmetatable(Keyboard.key_up, { 5 | __index = function(table, key) 6 | local key_code = Keyboard.key_codes[key] 7 | 8 | if key_code then 9 | return not wx.wxGetKeyState(key_code) 10 | end 11 | end 12 | }) 13 | 14 | Keyboard.key_down = {} 15 | setmetatable(Keyboard.key_down, { 16 | __index = function(table, key) 17 | local key_code = Keyboard.key_codes[key] 18 | 19 | if key_code then 20 | return wx.wxGetKeyState(key_code) 21 | end 22 | end 23 | }) 24 | 25 | Keyboard.key_names = { 26 | [wx.WXK_BACK] = 'backspace', 27 | [wx.WXK_TAB] = 'tab', 28 | [wx.WXK_RETURN] = 'enter', 29 | [wx.WXK_ESCAPE] = 'escape', 30 | [wx.WXK_SPACE] = 'space', 31 | [wx.WXK_DELETE] = 'delete', 32 | [wx.WXK_SHIFT] = 'shift', 33 | [wx.WXK_ALT] = 'alt', 34 | [wx.WXK_CONTROL] = 'ctrl', 35 | [wx.WXK_MENU] = 'menu', 36 | [wx.WXK_START] = 'start', 37 | [wx.WXK_PAUSE] = 'pause', 38 | [wx.WXK_CAPITAL] = 'caps lock', 39 | [wx.WXK_END] = 'end', 40 | [wx.WXK_HOME] = 'home', 41 | [wx.WXK_LEFT] = 'left', 42 | [wx.WXK_RIGHT] = 'right', 43 | [wx.WXK_UP] = 'up', 44 | [wx.WXK_DOWN] = 'down', 45 | [321] = 'print screen', 46 | [wx.WXK_INSERT] = 'insert', 47 | [wx.WXK_NUMPAD0] = 'numpad 0', 48 | [wx.WXK_NUMPAD1] = 'numpad 1', 49 | [wx.WXK_NUMPAD2] = 'numpad 2', 50 | [wx.WXK_NUMPAD3] = 'numpad 3', 51 | [wx.WXK_NUMPAD4] = 'numpad 4', 52 | [wx.WXK_NUMPAD5] = 'numpad 5', 53 | [wx.WXK_NUMPAD6] = 'numpad 6', 54 | [wx.WXK_NUMPAD7] = 'numpad 7', 55 | [wx.WXK_NUMPAD8] = 'numpad 8', 56 | [wx.WXK_NUMPAD9] = 'numpad 9', 57 | [wx.WXK_NUMPAD_MULTIPLY] = 'numpad *', 58 | [wx.WXK_NUMPAD_ADD] = 'numpad +', 59 | [wx.WXK_NUMPAD_SUBTRACT] = 'numpad -', 60 | [wx.WXK_NUMPAD_DECIMAL] = 'numpad .', 61 | [wx.WXK_NUMPAD_DIVIDE] = 'numpad /', 62 | [wx.WXK_NUMPAD_HOME] = 'numpad 7', 63 | [wx.WXK_NUMPAD_LEFT] = 'numpad 4', 64 | [wx.WXK_NUMPAD_UP] = 'numpad 8', 65 | [wx.WXK_NUMPAD_RIGHT] = 'numpad 6', 66 | [wx.WXK_NUMPAD_DOWN] = 'numpad 2', 67 | [wx.WXK_NUMPAD_PAGEUP] = 'numpad 9', 68 | [wx.WXK_NUMPAD_PAGEDOWN] = 'numpad 3', 69 | [wx.WXK_NUMPAD_END] = 'numpad 1', 70 | [wx.WXK_NUMPAD_DELETE] = 'numpad .', 71 | [wx.WXK_NUMPAD_INSERT] = 'numpad 0', 72 | [wx.WXK_NUMPAD_ENTER] = 'numpad enter', 73 | [305] = 'numpad 5', 74 | [wx.WXK_F1] = 'f1', 75 | [wx.WXK_F2] = 'f2', 76 | [wx.WXK_F3] = 'f3', 77 | [wx.WXK_F4] = 'f4', 78 | [wx.WXK_F5] = 'f5', 79 | [wx.WXK_F6] = 'f6', 80 | [wx.WXK_F7] = 'f7', 81 | [wx.WXK_F8] = 'f8', 82 | [wx.WXK_F9] = 'f9', 83 | [wx.WXK_F10] = 'f10', 84 | [wx.WXK_F11] = 'f11', 85 | [wx.WXK_F12] = 'f12', 86 | [wx.WXK_NUMLOCK] = 'num lock', 87 | [wx.WXK_SCROLL] = 'scroll lock', 88 | [wx.WXK_PAGEUP] = 'page up', 89 | [wx.WXK_PAGEDOWN] = 'page down', 90 | [96] = '~', 91 | [49] = '1', 92 | [50] = '2', 93 | [51] = '3', 94 | [52] = '4', 95 | [53] = '5', 96 | [54] = '6', 97 | [55] = '7', 98 | [56] = '8', 99 | [57] = '9', 100 | [48] = '0', 101 | [45] = '-', 102 | [61] = '+', 103 | [65] = 'a', 104 | [66] = 'b', 105 | [67] = 'c', 106 | [68] = 'd', 107 | [69] = 'e', 108 | [70] = 'f', 109 | [71] = 'g', 110 | [72] = 'h', 111 | [73] = 'i', 112 | [74] = 'j', 113 | [75] = 'k', 114 | [76] = 'l', 115 | [77] = 'm', 116 | [78] = 'n', 117 | [79] = 'o', 118 | [80] = 'p', 119 | [81] = 'q', 120 | [82] = 'r', 121 | [83] = 's', 122 | [84] = 't', 123 | [85] = 'u', 124 | [86] = 'v', 125 | [87] = 'w', 126 | [88] = 'x', 127 | [89] = 'y', 128 | [90] = 'z', 129 | [91] = '[', 130 | [93] = ']', 131 | [92] = '\\', 132 | [59] = ';', 133 | [39] = '\'', 134 | [44] = ',', 135 | [46] = '.', 136 | [47] = '/' 137 | } 138 | 139 | Keyboard.key_codes = {} 140 | 141 | for code, name in pairs(Keyboard.key_names) do 142 | Keyboard.key_codes[name] = code 143 | end 144 | 145 | return Keyboard -------------------------------------------------------------------------------- /src/gui/label.lua: -------------------------------------------------------------------------------- 1 | local common = require 'gui.common' 2 | 3 | local Label = {} 4 | common.is_destroyable(Label) 5 | 6 | local metatable = common.create_metatable(Label) 7 | common.add_position(metatable, 'label') 8 | common.add_color(metatable, 'label') 9 | common.add_text_color(metatable, 'label') 10 | 11 | local function word_wrap(label) 12 | local width, height = label.width, label.height 13 | 14 | label.wx:SetLabel(metatable[label].original_text) 15 | label.wx:Wrap(width) 16 | 17 | if label.width < width then 18 | label.wx:SetSize(width, label.height) 19 | end 20 | 21 | if metatable[label].fixed_height then 22 | label.wx:SetSize(label.width, height) 23 | end 24 | end 25 | 26 | function metatable.get_text(object) 27 | return object.wx:GetLabel() 28 | end 29 | 30 | function metatable.set_text(object, value) 31 | metatable[object].original_text = value 32 | local width, height = object.width, object.height 33 | 34 | object.wx:SetLabel(value) 35 | 36 | if metatable[object].fixed_width then 37 | object.wx:SetSize(width, object.height) 38 | end 39 | 40 | if metatable[object].fixed_height then 41 | object.wx:SetSize(object.width, height) 42 | end 43 | 44 | if object.word_wrap then 45 | if object.x + object.width > object.parent.width then 46 | object.width = object.parent.width - object.x 47 | end 48 | 49 | word_wrap(object) 50 | end 51 | end 52 | 53 | function metatable.set_word_wrap(object, value) 54 | if value == true then 55 | if object.x + object.width > object.parent.width then 56 | object.width = object.parent.width - object.x 57 | end 58 | 59 | word_wrap(object) 60 | end 61 | end 62 | 63 | function metatable.get_width(object) 64 | return object.wx:GetSize():GetWidth() 65 | end 66 | 67 | function metatable.set_width(object, value) 68 | metatable[object].fixed_width = true 69 | object.wx:SetSize(value, object.height) 70 | 71 | if object.word_wrap then 72 | word_wrap(object) 73 | end 74 | 75 | return object.wx:GetSize():GetWidth() 76 | end 77 | 78 | function metatable.get_height(object) 79 | return object.wx:GetSize():GetHeight() 80 | end 81 | 82 | function metatable.set_height(object, value) 83 | metatable[object].fixed_height = true 84 | object.wx:SetSize(object.width, value) 85 | return object.wx:GetSize():GetHeight() 86 | end 87 | 88 | common.add_anchor(metatable, 'label') 89 | 90 | function Label.create(parent) 91 | local label = { 92 | parent = parent, 93 | wx_events = {} 94 | } 95 | 96 | metatable[label] = { 97 | size = {} 98 | } 99 | 100 | label.wx = wx.wxStaticText( 101 | parent.wx_panel or parent.wx, 102 | wx.wxID_ANY, 103 | '', 104 | wx.wxDefaultPosition, 105 | wx.wxDefaultSize 106 | ) 107 | 108 | common.propagate_events(label) 109 | common.add_mouse_events(label) 110 | common.add_anchor_event(label, function() 111 | if label.word_wrap then 112 | word_wrap(label) 113 | end 114 | end) 115 | 116 | setmetatable(label, metatable) 117 | label.anchor = 'top left' 118 | 119 | return label 120 | end 121 | 122 | return Label -------------------------------------------------------------------------------- /src/gui/menu.lua: -------------------------------------------------------------------------------- 1 | local common = require 'gui.common' 2 | local MenuItem = require 'gui.menu_item' 3 | 4 | local Menu = {} 5 | local metatable = common.create_metatable(Menu) 6 | 7 | metatable.get_text = function(object) 8 | local text = object.wx:GetTitle() 9 | return wx.wxMenuItem.GetLabelText(text) 10 | end 11 | 12 | metatable.set_text = function(object, value) 13 | local menu_id = object.parent.wx:FindMenu(object.wx:GetTitle()) 14 | object.parent.wx:SetLabelTop(menu_id, value) 15 | end 16 | 17 | local menu_items = {} 18 | 19 | local function on_item_select(event_args) 20 | local menu_item_id = event_args:GetId() 21 | local menu_item = menu_items[menu_item_id] 22 | 23 | if menu_item ~= nil and type(menu_item.on_select) == 'function' then 24 | menu_item.checked = not menu_item.checked -- prevent wxWidgets from changing the state automatically 25 | menu_item:on_select() 26 | end 27 | end 28 | 29 | function Menu.create(name, parent) 30 | local menu = { 31 | parent = parent, 32 | } 33 | 34 | menu.wx = wx.wxMenu() 35 | 36 | menu.wx:Connect(wx.wxEVT_COMMAND_MENU_SELECTED, on_item_select) 37 | 38 | setmetatable(menu, metatable) 39 | return menu 40 | end 41 | 42 | function Menu:add_item(text) 43 | local item = MenuItem.create(self, text) 44 | menu_items[item.wx:GetId()] = item 45 | self.wx:Append(item.wx) 46 | return item 47 | end 48 | 49 | function Menu:add_separator() 50 | self.wx:AppendSeparator() 51 | end 52 | 53 | return Menu -------------------------------------------------------------------------------- /src/gui/menu_bar.lua: -------------------------------------------------------------------------------- 1 | local common = require 'gui.common' 2 | local Menu = require 'gui.menu' 3 | 4 | local MenuBar = {} 5 | local metatable = common.create_metatable(MenuBar) 6 | 7 | function MenuBar.create() 8 | local menuBar = {} 9 | menuBar.wx = wx.wxMenuBar() 10 | 11 | setmetatable(menuBar, metatable) 12 | return menuBar 13 | end 14 | 15 | function MenuBar:add_menu(text) 16 | local menu = Menu.create(text, self) 17 | self.wx:Append(menu.wx, text) 18 | return menu 19 | end 20 | 21 | return MenuBar -------------------------------------------------------------------------------- /src/gui/menu_item.lua: -------------------------------------------------------------------------------- 1 | local common = require 'gui.common' 2 | 3 | local MenuItem = {} 4 | local metatable = common.create_metatable(MenuItem) 5 | 6 | metatable.get_text = function(object) 7 | return object.wx:GetItemLabelText() 8 | end 9 | 10 | metatable.set_text = function(object, value) 11 | object.wx:SetItemLabel(value) 12 | end 13 | 14 | metatable.get_checked = function(object) 15 | return object.wx:IsChecked() 16 | end 17 | 18 | metatable.set_checked = function(object, value) 19 | return object.wx:Check(value) 20 | end 21 | 22 | function MenuItem.create(parent, text) 23 | local menu_item = { 24 | parent = parent 25 | } 26 | 27 | menu_item.wx = wx.wxMenuItem(parent.wx, wx.wxID_ANY, text, '', wx.wxITEM_CHECK) 28 | 29 | setmetatable(menu_item, metatable) 30 | return menu_item 31 | end 32 | 33 | function MenuItem:select() 34 | local event = wx.wxCommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED, self.wx:GetId()) 35 | self.parent.wx:ProcessEvent(event) 36 | end 37 | 38 | return MenuItem -------------------------------------------------------------------------------- /src/gui/mouse.lua: -------------------------------------------------------------------------------- 1 | local common = require 'gui.common' 2 | 3 | local Mouse = {} 4 | local metatable = common.create_metatable(Mouse) 5 | 6 | function metatable.get_screen_x() 7 | return wx.wxGetMousePosition():GetX() 8 | end 9 | 10 | function metatable.get_screen_y() 11 | return wx.wxGetMousePosition():GetY() 12 | end 13 | 14 | function metatable.get_button_down() 15 | local buttons = {} 16 | local state = wx.wxGetMouseState() 17 | 18 | buttons.left = state:LeftDown() 19 | buttons.right = state:RightDown() 20 | buttons.middle = state:MiddleDown() 21 | buttons.any = buttons.left or buttons.right or buttons.middle 22 | buttons.all = buttons.left and buttons.right and buttons.middle 23 | 24 | return buttons 25 | end 26 | 27 | function metatable.get_button_up() 28 | local buttons = {} 29 | local state = wx.wxGetMouseState() 30 | 31 | buttons.left = not state:LeftDown() 32 | buttons.right = not state:RightDown() 33 | buttons.middle = not state:MiddleDown() 34 | buttons.any = buttons.left or buttons.right or buttons.middle 35 | buttons.all = buttons.left and buttons.right and buttons.middle 36 | 37 | return buttons 38 | end 39 | 40 | local mouse = {} 41 | setmetatable(mouse, metatable) 42 | 43 | return mouse -------------------------------------------------------------------------------- /src/gui/text_box.lua: -------------------------------------------------------------------------------- 1 | local common = require 'gui.common' 2 | 3 | local TextBox = {} 4 | common.is_destroyable(TextBox) 5 | common.is_focusable(TextBox) 6 | 7 | local metatable = common.create_metatable(TextBox) 8 | common.add_position(metatable, 'text box') 9 | common.add_size(metatable, 'text box') 10 | common.add_anchor(metatable, 'text box') 11 | common.add_value(metatable, 'text box', 'text') 12 | common.add_color(metatable, 'text_box') 13 | common.add_text_color(metatable, 'text_box') 14 | 15 | local function create_text_box(text_box) 16 | local style = 0 17 | 18 | if text_box.multiline then 19 | style = style + wx.wxTE_MULTILINE 20 | end 21 | 22 | if text_box.word_wrap then 23 | style = style + wx.wxTE_BESTWRAP 24 | elseif text_box.multiline then 25 | style = style + wx.wxTE_DONTWRAP 26 | end 27 | 28 | text_box.wx = wx.wxTextCtrl( 29 | text_box.parent.wx_panel or text_box.parent.wx, 30 | wx.wxID_ANY, 31 | text_box.text or '', 32 | wx.wxDefaultPosition, 33 | wx.wxDefaultSize, 34 | style or 0) 35 | 36 | common.propagate_events(text_box) 37 | common.add_mouse_events(text_box) 38 | common.add_anchor_event(text_box) 39 | common.add_event(text_box, 'on_text_changed', wx.wxEVT_COMMAND_TEXT_UPDATED) 40 | 41 | setmetatable(text_box, metatable) 42 | text_box.anchor = 'top left' 43 | end 44 | 45 | local function recreate_text_box(text_box) 46 | local values = getmetatable(text_box)[text_box] 47 | 48 | local copy = {} 49 | for k,v in pairs(values) do 50 | if k ~= 'multiline' and k ~= 'word_wrap' then 51 | copy[k] = v 52 | end 53 | end 54 | 55 | local wx = text_box.wx 56 | create_text_box(text_box) 57 | wx:Destroy() 58 | 59 | for k,v in pairs(copy) do 60 | text_box[k] = v 61 | end 62 | end 63 | 64 | function metatable.set_multiline(object, value) 65 | local values = getmetatable(object)[object] 66 | values.multiline = value 67 | 68 | recreate_text_box(object) 69 | end 70 | 71 | function metatable.set_word_wrap(object, value) 72 | local values = getmetatable(object)[object] 73 | values.word_wrap = value 74 | 75 | recreate_text_box(object) 76 | end 77 | 78 | function TextBox.create(parent) 79 | local text_box = { 80 | parent = parent, 81 | wx_events = {} 82 | } 83 | 84 | create_text_box(text_box) 85 | return text_box 86 | end 87 | 88 | return TextBox -------------------------------------------------------------------------------- /src/gui/timer.lua: -------------------------------------------------------------------------------- 1 | local common = require 'gui.common' 2 | 3 | local Timer = {} 4 | local timers = {} 5 | local stop_watch 6 | 7 | local function initialize() 8 | stop_watch = wx.wxStopWatch() 9 | stop_watch:Start() 10 | 11 | wx:wxGetApp():Connect(wx.wxEVT_IDLE, function(event) 12 | if #timers == 0 then 13 | return 14 | end 15 | 16 | local current_time = stop_watch:Time() 17 | 18 | for _, timer in ipairs(timers) do 19 | local delta_time = (current_time - timer.last_time) 20 | if type(timer.on_tick) == 'function' and delta_time >= timer.interval * 1000 then 21 | timer.last_time = current_time 22 | timer:on_tick(delta_time / 1000) 23 | end 24 | end 25 | 26 | event:RequestMore() 27 | end) 28 | end 29 | 30 | function Timer.create() 31 | if not stop_watch then 32 | initialize() 33 | end 34 | 35 | local timer = { 36 | interval = 1, 37 | last_time = stop_watch:Time() 38 | } 39 | 40 | function timer:start() 41 | table.insert(timers, self) 42 | end 43 | 44 | function timer:stop() 45 | for i, t in ipairs(timers) do 46 | if t == timer then 47 | table.remove(timers, i) 48 | break 49 | end 50 | end 51 | end 52 | 53 | return timer 54 | end 55 | 56 | return Timer -------------------------------------------------------------------------------- /src/gui/window.lua: -------------------------------------------------------------------------------- 1 | local common = require 'gui.common' 2 | local Button = require 'gui.button' 3 | local Label = require 'gui.label' 4 | local MenuBar = require 'gui.menu_bar' 5 | local TextBox = require 'gui.text_box' 6 | local Dialog = require 'gui.dialog' 7 | local FileDialog = require 'gui.file_dialog' 8 | local Image = require 'gui.image' 9 | 10 | local Window = {} 11 | local metatable = common.create_metatable(Window) 12 | common.add_position(metatable, 'window') 13 | common.add_client_size(metatable, 'window') 14 | common.add_label(metatable, 'window', 'title') 15 | common.add_color(metatable, 'window') 16 | common.add_resizable(metatable, 'window') 17 | 18 | function metatable.set_cursor(object, value) 19 | local cursors = { 20 | ["arrow"] = wx.wxCURSOR_ARROW, 21 | ["right arrow"] = wx.wxCURSOR_RIGHT_ARROW, 22 | ["hand"] = wx.wxCURSOR_HAND, 23 | ["magnifier"] = wx.wxCURSOR_MAGNIFIER, 24 | ["no entry"] = wx.wxCURSOR_NO_ENTRY, 25 | ["question"] = wx.wxCURSOR_QUESTION_ARROW, 26 | ["size sinister"] = wx.wxCURSOR_SIZENESW, 27 | ["size baroque"] = wx.wxCURSOR_SIZENWSE, 28 | ["size horizontal"] = wx.wxCURSOR_SIZEWE, 29 | ["size vertical"] = wx.wxCURSOR_SIZENS, 30 | ["move"] = wx.wxCURSOR_SIZING, 31 | ["wait"] = wx.wxCURSOR_WAIT, 32 | ["wait arrow"] = wx.wxCURSOR_ARROWWAIT 33 | } 34 | 35 | object.wx:SetCursor(wx.wxCursor(cursors[value] or wx.wxCURSOR_ARROW)) 36 | end 37 | 38 | function Window.create() 39 | local window = { 40 | images = {} 41 | } 42 | 43 | window.wx = wx.wxFrame( 44 | wx.NULL, 45 | wx.wxID_ANY, 46 | '', 47 | wx.wxDefaultPosition, 48 | wx.wxSize(640, 480), 49 | wx.wxDEFAULT_FRAME_STYLE) 50 | 51 | window.wx_panel = wx.wxPanel( 52 | window.wx, 53 | wx.wxID_ANY, 54 | wx.wxDefaultPosition, 55 | wx.wxDefaultSize) 56 | 57 | window.menu_bar = MenuBar.create() 58 | window.wx:SetMenuBar(window.menu_bar.wx) 59 | 60 | window.wx_panel:Connect(wx.wxEVT_SIZE, function() 61 | if type(window.on_resize) == 'function' then 62 | window:on_resize() 63 | end 64 | end) 65 | 66 | window.wx:Connect(wx.wxEVT_CLOSE_WINDOW, function(event) 67 | if type(window.on_closing) ~= 'function' or window:on_closing() ~= false or not event:CanVeto() then 68 | window.wx:Destroy() 69 | end 70 | end) 71 | 72 | window.wx_panel:Connect(wx.wxEVT_PAINT, function(event) 73 | common.paint_images(window) 74 | end) 75 | 76 | window.wx:Show(true) 77 | 78 | common.forward_mouse_events(window) 79 | common.add_mouse_events(window) 80 | common.add_keyboard_events(window) 81 | common.add_event(window, 'on_resize', wx.wxEVT_SIZE) 82 | common.add_event(window, 'on_move', wx.wxEVT_MOVE) 83 | 84 | setmetatable(window, metatable) 85 | window.cursor = "arrow" 86 | 87 | return window 88 | end 89 | 90 | function Window:create_file_dialog() 91 | return FileDialog.create(self) 92 | end 93 | 94 | function Window:create_dialog() 95 | return Dialog.create(self) 96 | end 97 | 98 | function Window:add_button() 99 | return Button.create(self) 100 | end 101 | 102 | function Window:add_label() 103 | return Label.create(self) 104 | end 105 | 106 | function Window:add_image() 107 | return Image.create(self) 108 | end 109 | 110 | function Window:add_text_box() 111 | return TextBox.create(self) 112 | end 113 | 114 | function Window:close() 115 | self.wx:Close() 116 | end 117 | 118 | return Window -------------------------------------------------------------------------------- /src/plugin.lua: -------------------------------------------------------------------------------- 1 | local api = { 2 | gui = { 3 | type = 'lib', 4 | description = 'Allows you to create a graphical user interface in Lua.', 5 | 6 | childs = { 7 | create_timer = { 8 | type = 'function', 9 | description = 'Creates a timer.', 10 | args = '()', 11 | returns = '(timer)', 12 | valuetype = 'timer' 13 | }, 14 | 15 | create_window = { 16 | type = 'function', 17 | description = 'Creates a new window.', 18 | args = '()', 19 | returns = '(window)', 20 | valuetype = 'window' 21 | }, 22 | 23 | run = { 24 | type = 'function', 25 | description = 'Starts the main event loop.', 26 | args = '()', 27 | returns = '()' 28 | } 29 | } 30 | }, 31 | 32 | button = { 33 | type = 'class', 34 | description = 'A button.', 35 | 36 | childs = { 37 | alignment = { 38 | type = 'value', 39 | description = "The position of text on the button. Valid values are 'top left', 'top right', 'bottom left', 'bottom right', 'top', 'bottom', 'left', 'right', and 'center'." 40 | }, 41 | 42 | anchor = { 43 | type = 'value', 44 | description = "The sides of the button's parent to which the button is anchored. When the button is anchored to a side, the distance between the button and the parent's side don't change when you resize the parent. Valid values are 'left', 'right', 'top', 'bottom', or combinations thereof separated by a space. 'all' is a shortcut for 'top left bottom right'." 45 | }, 46 | 47 | background_color = { 48 | type = 'value', 49 | description = 'The background color of the button. You can specify the color as a list of numbers in the order red, green, blue, or as a table with the fields red, green and blue. So, the color orange would be either { 1.0, 0.4, 0 } or { red = 1.0, green = 0.4, blue = 0 }. When you read the background color, it is always specified in the second format. This property is not supported under MacOS.' 50 | }, 51 | 52 | height = { 53 | type = 'value', 54 | description = 'The height of the button in pixels.' 55 | }, 56 | 57 | text = { 58 | type = 'value', 59 | description = 'The text on the button.' 60 | }, 61 | 62 | text_color = { 63 | type = 'value', 64 | description = 'The color of the text on the button. You can specify the color as a list of numbers in the order red, green, blue, or as a table with the fields red, green and blue. So, the color orange would be either { 1.0, 0.4, 0 } or { red = 1.0, green = 0.4, blue = 0 }. When you read the text color, it is always specified in the second format. This property is not supported under MacOS.' 65 | }, 66 | 67 | width = { 68 | type = 'value', 69 | description = 'The width of the button in pixels.' 70 | }, 71 | 72 | word_wrap = { 73 | type = 'value', 74 | description = 'true if words that don\'t fit on the button automatically wrap to the next line, false if long lines will be cut off.' 75 | }, 76 | 77 | x = { 78 | type = 'value', 79 | description = 'The x-position of the button in pixels.' 80 | }, 81 | 82 | y = { 83 | type = 'value', 84 | description = 'The y-position of the button in pixels.' 85 | }, 86 | 87 | click = { 88 | type = 'method', 89 | description = 'Simulates a click on the button.', 90 | args = '()', 91 | returns = '()' 92 | }, 93 | 94 | destroy = { 95 | type = 'method', 96 | description = 'Removes the button from its window. Once a button is destroyed, you can\'t access its properties, call its functions, or listen to its events anymore.', 97 | args = '()', 98 | returns = '()' 99 | }, 100 | 101 | focus = { 102 | type = 'method', 103 | description = 'Gives the keyboard focus to the button.', 104 | args = '()', 105 | returns = '()' 106 | }, 107 | 108 | on_click = { 109 | type = 'method', 110 | description = 'The event handler that is called when the user clicks the button with the left mouse button.', 111 | args = '()', 112 | returns = '()' 113 | }, 114 | 115 | on_mouse_down = { 116 | type = 'method', 117 | description = 'The event handler that is called when the user releases a mouse button while the mouse cursor is over the button.', 118 | args = '(button, x, y)', 119 | returns = '()' 120 | }, 121 | 122 | on_mouse_move = { 123 | type = 'method', 124 | description = 'The event handler that is called when the user moves the mouse cursor over the button.', 125 | args = '(x, y)', 126 | returns = '()' 127 | }, 128 | 129 | on_mouse_up = { 130 | type = 'method', 131 | description = 'The event handler that is called when the user presses a mouse button while the mouse cursor is over the button.', 132 | args = '(button, x, y)', 133 | returns = '()' 134 | } 135 | } 136 | }, 137 | 138 | dialog = { 139 | type = 'class', 140 | description = 'A dialog box.', 141 | 142 | childs = { 143 | background_color = { 144 | type = 'value', 145 | description = 'The background color of the dialog. You can specify the color as a list of numbers in the order red, green, blue, or as a table with the fields red, green and blue. So, the color orange would be either { 1.0, 0.4, 0 } or { red = 1.0, green = 0.4, blue = 0 }. When you read the background color, it is always specified in the second format. This property is not supported under MacOS.' 146 | }, 147 | 148 | height = { 149 | type = 'value', 150 | description = 'The height of the dialog in pixels. The height does not include borders or the title bar.' 151 | }, 152 | 153 | resizable = { 154 | type = 'value', 155 | description = 'true if the user can resize the dialog, false if the size of the dialog is fixed.' 156 | }, 157 | 158 | title = { 159 | type = 'value', 160 | description = 'The text that is displayed in the title bar of the dialog.' 161 | }, 162 | 163 | width = { 164 | type = 'value', 165 | description = 'The width of the dialog in pixels. The width does not include borders.' 166 | }, 167 | 168 | x = { 169 | type = 'value', 170 | description = 'The x-coordinate of the dialog in pixels.' 171 | }, 172 | 173 | y = { 174 | type = 'value', 175 | description = 'The y-coordinate of the dialog in pixels.' 176 | }, 177 | 178 | add_button = { 179 | type = 'method', 180 | description = 'Creates a button and adds it to the dialog.', 181 | args = '()', 182 | returns = '(button)', 183 | valuetype = 'button' 184 | }, 185 | 186 | add_image = { 187 | type = 'method', 188 | description = 'Creates an image and adds it to the dialog.', 189 | args = '()', 190 | returns = '(image)', 191 | valuetype = 'image' 192 | }, 193 | 194 | add_label = { 195 | type = 'method', 196 | description = 'Creates a label and adds it to the dialog.', 197 | args = '()', 198 | returns = '(label)', 199 | valuetype = 'label' 200 | }, 201 | 202 | add_text_box = { 203 | type = 'method', 204 | description = 'Creates a text box and adds it to the dialog.', 205 | args = '()', 206 | returns = '(text_box)', 207 | value_type = 'text_box' 208 | }, 209 | 210 | close = { 211 | type = 'method', 212 | description = 'Closes the dialog.' 213 | }, 214 | 215 | show_modal = { 216 | type = 'method', 217 | description = "Shows the dialog. While the dialog is visible, the user can't activate the parent window.", 218 | args = '()', 219 | returns = '()' 220 | }, 221 | 222 | show_modeless = { 223 | type = 'method', 224 | description = 'Shows the dialog. The user can still activate the parent window while the dialog is visible.', 225 | args = '()', 226 | returns = '()' 227 | }, 228 | 229 | on_closing = { 230 | type = 'method', 231 | description = 'The event handler that is called when the dialog is about to close. Return false from the event handler to prevent the dialog from closing.', 232 | args = '()', 233 | returns = '()' 234 | }, 235 | 236 | on_key_down = { 237 | type = 'method', 238 | description = 'The event handler that is called when the user presses a key while the dialog has focus.', 239 | args = '(key, modifiers)', 240 | returns = '()' 241 | }, 242 | 243 | on_key_up = { 244 | type = 'method', 245 | description = 'The event handler that is called when the user releases a key while the dialog has focus.', 246 | args = '(key, modifiers)', 247 | returns = '()' 248 | }, 249 | 250 | on_mouse_down = { 251 | type = 'method', 252 | description = 'The event handler that is called when the user releases a mouse button while the mouse cursor is over the dialog.', 253 | args = '(button, x, y)', 254 | returns = '()' 255 | }, 256 | 257 | on_mouse_move = { 258 | type = 'method', 259 | description = 'The event handler that is called when the user moves the mouse cursor over the dialog.', 260 | args = '(x, y)', 261 | returns = '()' 262 | }, 263 | 264 | on_mouse_up = { 265 | type = 'method', 266 | description = 'The event handler that is called when the user presses a mouse button while the mouse cursor is over the dialog.', 267 | args = '(button, x, y)', 268 | returns = '()' 269 | }, 270 | 271 | on_move = { 272 | type = 'method', 273 | description = 'The event handler that is called when the user moves the dialog.', 274 | args = '()', 275 | returns = '()' 276 | }, 277 | 278 | on_resize = { 279 | type = 'method', 280 | description = 'The event handler that is called when the user resizes the dialog.', 281 | args = '()', 282 | returns = '()' 283 | } 284 | } 285 | }, 286 | 287 | file_dialog = { 288 | type = 'class', 289 | description = 'A file dialog with which the user can select a file to open or save.', 290 | 291 | childs = { 292 | default_folder = { 293 | type = 'value', 294 | description = 'The path of the folder the user starts in when the file dialog opens.' 295 | }, 296 | 297 | default_file = { 298 | type = 'value', 299 | description = 'The name of the file that is selected when the file dialog opens.' 300 | }, 301 | 302 | file_name = { 303 | type = 'value', 304 | description = 'The name of the file the user has selected. The file name includes the full path of the file. If multiselect is true, you should use file_names instead of file_name.' 305 | }, 306 | 307 | file_names = { 308 | type = 'value', 309 | description = 'A list of names of the files the user has selected. The file names include the full path of the file. If multiselect is false, you should use file_name instead of file_names.' 310 | }, 311 | 312 | filters = { 313 | type = 'value', 314 | description = "A list of file filters that are available in the file dialog. Every filter in the list is of itself a list with two values: the description and the wildcard filter itself. For example: { { 'Text files', '*.txt' }, { 'All files', '*.*' } }" 315 | }, 316 | 317 | multiselect = { 318 | type = 'value', 319 | description = 'true if the user can select multiple files in the file dialog, or false when the user can select only a single file.' 320 | }, 321 | 322 | title = { 323 | type = 'value', 324 | description = 'The text that is displayed in the title bar of the file dialog.' 325 | }, 326 | 327 | open = { 328 | type = 'method', 329 | description = 'Shows a dialog for opening a file. Returns true if the user has selected a file, or false when the user has cancelled the dialog.', 330 | args = '()', 331 | returns = '(bool)' 332 | }, 333 | 334 | save = { 335 | type = 'method', 336 | description = 'Shows a dialog for saving a file. Returns true if the user has selected a file, or false when the user has cancelled the dialog.', 337 | args = '()', 338 | returns = '(bool)' 339 | } 340 | } 341 | }, 342 | 343 | image = { 344 | type = 'class', 345 | description = 'An image, displayed in a window.', 346 | 347 | childs = { 348 | anchor = { 349 | type = 'value', 350 | description = "The sides of the image's parent to which the image is anchored. When the image is anchored to a side, the distance between the image and the parent's side don't change when you resize the parent. Valid values are 'left', 'right', 'top', 'bottom', or combinations thereof separated by a space. 'all' is a shortcut for 'top left bottom right'." 351 | }, 352 | 353 | file_name = { 354 | type = 'value', 355 | description = 'The path of the file that contains the image.' 356 | }, 357 | 358 | height = { 359 | type = 'value', 360 | description = "The image's height in pixels." 361 | }, 362 | 363 | width = { 364 | type = 'value', 365 | description = "The image's width in pixels." 366 | }, 367 | 368 | x = { 369 | type = 'value', 370 | description = "The image's x-coordinate in pixels relative to the window." 371 | }, 372 | 373 | y = { 374 | type = 'value', 375 | description = "The image's y-coordinate in pixels relative to the window." 376 | }, 377 | 378 | destroy = { 379 | type = 'method', 380 | description = 'Removes the image from its window. Once an image is destroyed, you can\'t access its properties, call its functions, or listen to its events anymore.', 381 | args = '()', 382 | returns = '()' 383 | }, 384 | 385 | on_mouse_down = { 386 | type = 'method', 387 | description = 'The event handler that is called when the user releases a mouse button while the mouse cursor is over the image.', 388 | args = '(button, x, y)', 389 | returns = '()' 390 | }, 391 | 392 | on_mouse_over = { 393 | type = 'method', 394 | description = 'The event handler that is called when the user moves the mouse cursor over the image.', 395 | args = '(x, y)', 396 | returns = '()' 397 | }, 398 | 399 | on_mouse_up = { 400 | type = 'method', 401 | description = 'The event handler that is called when the user presses a mouse button while the mouse cursor is over the image.', 402 | args = '(button, x, y)', 403 | returns = '()' 404 | } 405 | } 406 | }, 407 | 408 | keyboard = { 409 | type = 'class', 410 | description = 'The current state of the keyboard.', 411 | 412 | childs = { 413 | key_down = { 414 | type = 'value', 415 | description = 'A list of all keyboard keys with an associated value of true if the key is down and false if the key is up.' 416 | }, 417 | 418 | key_up = { 419 | type = 'value', 420 | description = 'A list of all keyboard keys with an associated value of true if the key is up and false if the key is down.' 421 | } 422 | } 423 | }, 424 | 425 | label = { 426 | type = 'class', 427 | description = 'A label with static text.', 428 | 429 | childs = { 430 | anchor = { 431 | type = 'value', 432 | description = "The sides of the label's parent to which the label is anchored. When the label is anchored to a side, the distance between the label and the parent's side don't change when you resize the parent. Valid values are 'left', 'right', 'top', 'bottom', or combinations thereof separated by a space. 'all' is a shortcut for 'top left bottom right'." 433 | }, 434 | 435 | background_color = { 436 | type = 'value', 437 | description = 'The background color of the label. You can specify the color as a list of numbers in the order red, green, blue, or as a table with the fields red, green and blue. So, the color orange would be either { 1.0, 0.4, 0 } or { red = 1.0, green = 0.4, blue = 0 }. When you read the background color, it is always specified in the second format. This property is not supported under MacOS.' 438 | }, 439 | 440 | height = { 441 | type = 'value', 442 | description = "The label's height in pixels." 443 | }, 444 | 445 | text = { 446 | type = 'value', 447 | description = 'The text that is displayed on the label.' 448 | }, 449 | 450 | text_color = { 451 | type = 'value', 452 | description = 'The color of the text on the label. You can specify the color as a list of numbers in the order red, green, blue, or as a table with the fields red, green and blue. So, the color orange would be either { 1.0, 0.4, 0 } or { red = 1.0, green = 0.4, blue = 0 }. When you read the text color, it is always specified in the second format. This property is not supported under MacOS.' 453 | }, 454 | 455 | width = { 456 | type = 'value', 457 | description = "The label's width in pixels." 458 | }, 459 | 460 | word_wrap = { 461 | type = 'value', 462 | description = 'true if words that don\'t fit in the label automatically wrap to the next line, false if long lines will be cut off. Note that you must set the label\'s width in order for word wrapping to work.' 463 | }, 464 | 465 | x = { 466 | type = 'value', 467 | description = "The label's x-coordinate in pixels relative to the window." 468 | }, 469 | 470 | y = { 471 | type = 'value', 472 | description = "The label's y-coordinate in pixels relative to the window." 473 | }, 474 | 475 | destroy = { 476 | type = 'method', 477 | description = 'Removes the label from its window. Once a label is destroyed, you can\'t access its properties, call its functions, or listen to its events anymore.', 478 | args = '()', 479 | returns = '()' 480 | }, 481 | 482 | on_mouse_down = { 483 | type = 'method', 484 | description = 'The event handler that is called when the user releases a mouse button while the mouse cursor is over the label.', 485 | args = '(button, x, y)', 486 | returns = '()' 487 | }, 488 | 489 | on_mouse_move = { 490 | type = 'method', 491 | description = 'The event handler that is called when the user moves the mouse cursor over the label.', 492 | args = '(x, y)', 493 | returns = '()' 494 | }, 495 | 496 | on_mouse_up = { 497 | type = 'method', 498 | description = 'The event handler that is called when the user presses a mouse button while the mouse cursor is over the label.', 499 | args = '(button, x, y)', 500 | returns = '()' 501 | } 502 | } 503 | }, 504 | 505 | menu = { 506 | type = 'class', 507 | description = 'A menu on the menu bar', 508 | 509 | childs = { 510 | text = { 511 | type = 'value', 512 | description = "The menu's text." 513 | }, 514 | 515 | add_item = { 516 | type = 'method', 517 | description = 'Creates a menu item with the specified text and adds it to the menu.', 518 | args = '(text)', 519 | returns = '(menu_item)', 520 | valuetype = 'menu_item' 521 | }, 522 | 523 | add_separator = { 524 | type = 'method', 525 | description = 'Adds a horizontal line to the menu.', 526 | args = '()', 527 | returns = '()' 528 | } 529 | } 530 | }, 531 | 532 | menu_bar = { 533 | type = 'class', 534 | description = 'The menu bar of a window.', 535 | 536 | childs = { 537 | add_menu = { 538 | type = 'method', 539 | description = 'Creates a menu with the specified text and adds it to the menu bar.', 540 | args = '(text)', 541 | returns = '(menu)', 542 | valuetype = 'menu' 543 | } 544 | } 545 | }, 546 | 547 | menu_item = { 548 | type = 'class', 549 | description = 'A menu item in a menu.', 550 | 551 | childs = { 552 | checked = { 553 | type = 'value', 554 | description = "true if the menu item has a checkmark in front of it, false if it doesn't." 555 | }, 556 | 557 | text = { 558 | type = 'value', 559 | description = "The menu item's text." 560 | }, 561 | 562 | select = { 563 | type = 'method', 564 | description = 'Select the menu item, just as if the user had clicked it.', 565 | args = '()', 566 | returns = '()' 567 | }, 568 | 569 | on_select = { 570 | type = 'method', 571 | description = 'The event handler that is called when the user selects the menu item.', 572 | args = '()', 573 | returns = '()' 574 | } 575 | } 576 | }, 577 | 578 | mouse = { 579 | type = 'class', 580 | description = 'The current state of the mouse.', 581 | 582 | childs = { 583 | button_down = { 584 | type = 'value', 585 | description = 'A list of mouse buttons with an associated value of true if the button is down and false if the button is up.' 586 | }, 587 | 588 | button_up = { 589 | type = 'value', 590 | description = 'A list of mouse buttons with an associated value of true if the button is up and false if the button is down.' 591 | }, 592 | 593 | screen_x = { 594 | type = 'value', 595 | description = 'The x-coordinate of the mouse cursor in pixels relative to the screen.' 596 | }, 597 | 598 | screen_y = { 599 | type = 'value', 600 | description = 'The y-coordinate of the mouse cursor in pixels relative to the screen.' 601 | }, 602 | 603 | x = { 604 | type = 'value', 605 | description = 'The x-coordinate of the mouse cursor in pixels relative to the window that has focus.' 606 | }, 607 | 608 | y = { 609 | type = 'value', 610 | description = 'The y-coordinate of the mouse cursor in pixels relative to the window that has focus.' 611 | } 612 | } 613 | }, 614 | 615 | text_box = { 616 | type = 'class', 617 | description = 'A box in which the user can enter text.', 618 | 619 | childs = { 620 | anchor = { 621 | type = 'value', 622 | description = "The sides of the text box's parent to which the text box is anchored. When the text box is anchored to a side, the distance between the text box and the parent's side don't change when you resize the parent. Valid values are 'left', 'right', 'top', 'bottom', or combinations thereof separated by a space. 'all' is a shortcut for 'top left bottom right'." 623 | }, 624 | 625 | background_color = { 626 | type = 'value', 627 | description = 'The background color of the text box. You can specify the color as a list of numbers in the order red, green, blue, or as a table with the fields red, green and blue. So, the color orange would be either { 1.0, 0.4, 0 } or { red = 1.0, green = 0.4, blue = 0 }. When you read the background color, it is always specified in the second format. This property is not supported under MacOS.' 628 | }, 629 | 630 | destroy = { 631 | type = 'method', 632 | description = 'Removes the text box from its window. Once a text box is destroyed, you can\'t access its properties, call its functions, or listen to its events anymore.', 633 | args = '()', 634 | returns = '()' 635 | }, 636 | 637 | focus = { 638 | type = 'method', 639 | description = 'Gives the keyboard focus to the text box.', 640 | args = '()', 641 | returns = '()' 642 | }, 643 | 644 | height = { 645 | type = 'value', 646 | description = "The text box's height in pixels.", 647 | }, 648 | 649 | multiline = { 650 | type = 'value', 651 | description = 'true if the text box can show multiple lines of text, false if it only shows one.' 652 | }, 653 | 654 | text = { 655 | type = 'value', 656 | description = 'The text in the text box.' 657 | }, 658 | 659 | text_color = { 660 | type = 'value', 661 | description = 'The color of the text in the text box. You can specify the color as a list of numbers in the order red, green, blue, or as a table with the fields red, green and blue. So, the color orange would be either { 1.0, 0.4, 0 } or { red = 1.0, green = 0.4, blue = 0 }. When you read the text color, it is always specified in the second format. This property is not supported under MacOS.' 662 | }, 663 | 664 | width = { 665 | type = 'value', 666 | description = "The text box's width in pixels." 667 | }, 668 | 669 | word_wrap = { 670 | type = 'value', 671 | description = "true if words that don't fit in the text box automatically wrap to the next line, false if long lines require vertical scrolling." 672 | }, 673 | 674 | x = { 675 | type = 'value', 676 | description = "The text box's x-coordinate in pixels relative to the window." 677 | }, 678 | 679 | y = { 680 | type = 'value', 681 | description = "The text box's y-coordinate in pixels relative to the window." 682 | }, 683 | 684 | on_mouse_down = { 685 | type = 'method', 686 | description = 'The event handler that is called when the user releases a mouse button while the mouse cursor is over the text box.', 687 | args = '(button, x, y)', 688 | returns = '()' 689 | }, 690 | 691 | on_mouse_move = { 692 | type = 'method', 693 | description = 'The event handler that is called when the user moves the mouse cursor over the text box.', 694 | args = '(x, y)', 695 | returns = '()' 696 | }, 697 | 698 | on_mouse_up = { 699 | type = 'method', 700 | description = 'The event handler that is called when the user presses a mouse button while the mouse cursor is over the text box.', 701 | args = '(button, x, y)', 702 | returns = '()' 703 | }, 704 | 705 | on_text_changed = { 706 | type = 'method', 707 | description = 'The event handler that is called when the text in the text box has changed.', 708 | args = '()', 709 | returns = '()' 710 | } 711 | } 712 | }, 713 | 714 | timer = { 715 | type = 'class', 716 | description = 'A timer that raises an event after a set interval has expired.', 717 | 718 | childs = { 719 | interval = { 720 | type = 'value', 721 | description = 'The interval in seconds between on_tick-events.' 722 | }, 723 | 724 | start = { 725 | type = 'method', 726 | description = 'Starts the timer. The timer will start to raise on_tick-events.', 727 | args = '()', 728 | returns = '()' 729 | }, 730 | 731 | stop = { 732 | type = 'method', 733 | description = 'Stops the timer. The timer will stop raising on_tick-events.', 734 | args = '()', 735 | returns = '()' 736 | }, 737 | 738 | on_tick = { 739 | type = 'method', 740 | description = "The event handler that is called each time when the timer's interval expires.", 741 | args = '(delta_time)', 742 | returns = '()' 743 | } 744 | } 745 | }, 746 | 747 | window = { 748 | type = 'class', 749 | description = 'A window with a title bar.', 750 | 751 | childs = { 752 | background_color = { 753 | type = 'value', 754 | description = 'The background color of the window. You can specify the color as a list of numbers in the order red, green, blue, or as a table with the fields red, green and blue. So, the color orange would be either { 1.0, 0.4, 0 } or { red = 1.0, green = 0.4, blue = 0 }. When you read the background color, it is always specified in the second format. This property is not supported under MacOS.' 755 | }, 756 | 757 | height = { 758 | type = 'value', 759 | description = 'The height of the window in pixels. The height does not include borders, scroll bars, the title bar, the menu bar, or the status bar.' 760 | }, 761 | 762 | menu_bar = { 763 | type = 'value', 764 | description = "The window's menu bar.", 765 | valuetype = 'menu_bar' 766 | }, 767 | 768 | resizable = { 769 | type = 'value', 770 | description = 'true if the user can resize the window, false if the size of the window is fixed.' 771 | }, 772 | 773 | title = { 774 | type = 'value', 775 | description = 'The text that is displayed in the title bar of the window.' 776 | }, 777 | 778 | width = { 779 | type = 'value', 780 | description = 'The width of the window in pixels. The width does not include borders and scroll bars.' 781 | }, 782 | 783 | x = { 784 | type = 'value', 785 | description = 'The x-coordinate of the window in pixels.' 786 | }, 787 | 788 | y = { 789 | type = 'value', 790 | description = 'The y-coordinate of the window in pixels.' 791 | }, 792 | 793 | add_button = { 794 | type = 'method', 795 | description = 'Creates a button and adds it to the window.', 796 | args = '()', 797 | returns = '(button)', 798 | valuetype = 'button' 799 | }, 800 | 801 | add_image = { 802 | type = 'method', 803 | description = 'Creates an image and adds it to the window.', 804 | args = '()', 805 | returns = '(image)', 806 | valuetype = 'image' 807 | }, 808 | 809 | add_label = { 810 | type = 'method', 811 | description = 'Creates a label and adds it to the window.', 812 | args = '()', 813 | returns = '(label)', 814 | valuetype = 'label' 815 | }, 816 | 817 | add_text_box = { 818 | type = 'method', 819 | description = 'Creates a text box and adds it to the window.', 820 | args = '()', 821 | returns = '(text_box)', 822 | value_type = 'text_box' 823 | }, 824 | 825 | close = { 826 | type = 'method', 827 | description = 'Closes the window. If there are no other open windows, the application will close.' 828 | }, 829 | 830 | create_dialog = { 831 | type = 'method', 832 | description = "Creates a dialog box. A dialog box is a separate window with its own controls. Unlike a window, a dialog box doesn't show up in the task bar.", 833 | args = '()', 834 | returns = '(dialog)', 835 | value_type = 'dialog' 836 | }, 837 | 838 | create_file_dialog = { 839 | type = 'method', 840 | description = 'Creates a file dialog that you can use to specify a file to open or save.', 841 | args = '()', 842 | returns = '(file_dialog)', 843 | value_type = 'file_dialog' 844 | }, 845 | 846 | on_closing = { 847 | type = 'method', 848 | description = 'The event handler that is called when the window is about to close. Return false from the event handler to prevent the window from closing.', 849 | args = '()', 850 | returns = '()' 851 | }, 852 | 853 | on_key_down = { 854 | type = 'method', 855 | description = 'The event handler that is called when the user presses a key while the window has focus.', 856 | args = '(key, modifiers)', 857 | returns = '()' 858 | }, 859 | 860 | on_key_up = { 861 | type = 'method', 862 | description = 'The event handler that is called when the user releases a key while the window has focus.', 863 | args = '(key, modifiers)', 864 | returns = '()' 865 | }, 866 | 867 | on_mouse_down = { 868 | type = 'method', 869 | description = 'The event handler that is called when the user releases a mouse button while the mouse cursor is over the window.', 870 | args = '(button, x, y)', 871 | returns = '()' 872 | }, 873 | 874 | on_mouse_move = { 875 | type = 'method', 876 | description = 'The event handler that is called when the user moves the mouse cursor over the window.', 877 | args = '(x, y)', 878 | returns = '()' 879 | }, 880 | 881 | on_mouse_up = { 882 | type = 'method', 883 | description = 'The event handler that is called when the user presses a mouse button while the mouse cursor is over the window.', 884 | args = '(button, x, y)', 885 | returns = '()' 886 | }, 887 | 888 | on_move = { 889 | type = 'method', 890 | description = 'The event handler that is called when the user moves the window.', 891 | args = '()', 892 | returns = '()' 893 | }, 894 | 895 | on_resize = { 896 | type = 'method', 897 | description = 'The event handler that is called when the user resizes the window.', 898 | args = '()', 899 | returns = '()' 900 | } 901 | } 902 | } 903 | } 904 | 905 | return { 906 | name = "Lua GUI", 907 | description = "An easy-to-use library for creating GUIs with Lua.", 908 | author = "William Willing", 909 | version = 4, 910 | 911 | onRegister = function() 912 | ide:AddAPI("lua", "gui", api) 913 | table.insert(ide.interpreters.luadeb.api, "gui") 914 | ReloadLuaAPI() 915 | end, 916 | 917 | onUnRegister = function() 918 | ide:RemoveAPI("lua", "gui") 919 | 920 | for i, v in ipairs(ide.interpreters.luadeb.api) do 921 | if v == "gui" then 922 | table.remove(ide.interpreters.luadeb.api, i) 923 | break 924 | end 925 | end 926 | end 927 | } -------------------------------------------------------------------------------- /src/test/button.lua: -------------------------------------------------------------------------------- 1 | local unit_test = require 'test.unit_test' 2 | local assert = unit_test.assert 3 | 4 | require 'gui' 5 | 6 | local window, button 7 | 8 | local suite = { 9 | set_up = function() 10 | window = gui.create_window() 11 | button = window:add_button() 12 | end, 13 | 14 | ['a button has a size'] = function() 15 | button.width = 50 16 | button.height = 25 17 | 18 | assert.are_equal(50, button.width) 19 | assert.are_equal(25, button.height) 20 | end, 21 | 22 | ['a button has a position'] = function() 23 | button.x = 95 24 | button.y = 66 25 | 26 | assert.are_equal(95, button.x) 27 | assert.are_equal(66, button.y) 28 | end, 29 | 30 | ['a button has text'] = function() 31 | button.text = 'click me' 32 | 33 | assert.are_equal('click me', button.text) 34 | end, 35 | 36 | ['a button raises a click event'] = function() 37 | button.on_click = unit_test.count_calls_to() 38 | button:click() 39 | 40 | assert.was_called(button.on_click) 41 | end, 42 | 43 | ['a button can be anchored'] = function() 44 | window.width = 200 45 | window.height = 200 46 | 47 | button.x = 50 48 | button.y = 50 49 | button.width = 100 50 | button.height = 100 51 | button.anchor = 'all' 52 | 53 | window.width = 500 54 | window.height = 300 55 | 56 | assert.are_equal(400, button.width) 57 | assert.are_equal(200, button.height) 58 | end 59 | } 60 | 61 | unit_test.run(suite) -------------------------------------------------------------------------------- /src/test/main.lua: -------------------------------------------------------------------------------- 1 | local unit_test = require 'test.unit_test' 2 | 3 | unit_test.gather() 4 | 5 | require 'test.window' 6 | require 'test.button' 7 | require 'test.menu' 8 | require 'test.text_box' 9 | 10 | unit_test.run_all() -------------------------------------------------------------------------------- /src/test/menu.lua: -------------------------------------------------------------------------------- 1 | local unit_test = require 'test.unit_test' 2 | local assert = unit_test.assert 3 | 4 | require 'gui' 5 | 6 | local window 7 | 8 | local suite = { 9 | set_up = function() 10 | window = gui.create_window() 11 | end, 12 | 13 | ['a menu has text'] = function() 14 | local menu = window.menu_bar:add_menu('File') 15 | 16 | assert.are_equal('File', menu.text) 17 | end, 18 | 19 | ["a menu's text does not include the ampersand"] = function() 20 | local menu = window.menu_bar:add_menu('&File') 21 | 22 | assert.are_equal('File', menu.text) 23 | end, 24 | 25 | ['a menu can have a menu item'] = function() 26 | local menu = window.menu_bar:add_menu('File') 27 | local menu_item = menu:add_item('&New') 28 | 29 | assert.are_equal('New', menu_item.text) 30 | end, 31 | 32 | ['a menu item raises a select event'] = function() 33 | local menu = window.menu_bar:add_menu('Help') 34 | local menu_item = menu:add_item('About') 35 | menu_item.on_select = unit_test.count_calls_to() 36 | menu_item:select() 37 | 38 | assert.was_called(menu_item.on_select) 39 | end 40 | } 41 | 42 | unit_test.run(suite) -------------------------------------------------------------------------------- /src/test/text_box.lua: -------------------------------------------------------------------------------- 1 | local unit_test = require 'test.unit_test' 2 | local assert = unit_test.assert 3 | 4 | require 'gui' 5 | 6 | local window, text_box 7 | 8 | local suite = { 9 | set_up = function() 10 | window = gui.create_window() 11 | text_box = window:add_text_box() 12 | end, 13 | 14 | ['a text box raises a text changed event'] = function() 15 | text_box.on_text_changed = unit_test.count_calls_to() 16 | text_box.text = 'hello' 17 | 18 | assert.was_called(text_box.on_text_changed) 19 | end, 20 | 21 | ['a text box can be anchored to all sides of a window'] = function() 22 | text_box.x = 0 23 | text_box.y = 0 24 | text_box.width = window.width 25 | text_box.height = window.height 26 | text_box.anchor = 'all' 27 | 28 | window.width = 400 29 | window.height = 345 30 | 31 | assert.are_equal(400, text_box.width) 32 | assert.are_equal(345, text_box.height) 33 | end, 34 | 35 | ['a text box can be anchored to the top left of a window'] = function() 36 | text_box.x = 39 37 | text_box.y = 46 38 | text_box.width = 100 39 | text_box.height = 25 40 | text_box.anchor = 'top left' 41 | 42 | window.x = 0 43 | window.y = 0 44 | window.width = 160 45 | window.height = 583 46 | 47 | assert.are_equal(39, text_box.x) 48 | assert.are_equal(46, text_box.y) 49 | assert.are_equal(100, text_box.width) 50 | assert.are_equal(25, text_box.height) 51 | end, 52 | 53 | ['a text box can be anchored to the bottom right of a window'] = function() 54 | window.width = 400 55 | window.height = 300 56 | text_box.x = 200 57 | text_box.width = 200 58 | text_box.y = 100 59 | text_box.height = 100 60 | text_box.anchor = 'bottom right' 61 | 62 | window.width = 200 63 | window.height = 450 64 | 65 | assert.are_equal(0, text_box.x) 66 | assert.are_equal(200, text_box.width) 67 | assert.are_equal(250, text_box.y) 68 | assert.are_equal(100, text_box.height) 69 | end, 70 | 71 | ['a text box can be unanchored from all sides of a window'] = function() 72 | window.width = 400 73 | window.height = 300 74 | text_box.x = 200 75 | text_box.width = 200 76 | text_box.y = 100 77 | text_box.height = 100 78 | text_box.anchor = 'none' 79 | 80 | window.width = 600 81 | window.height = 200 82 | 83 | assert.are_equal(300, text_box.x) 84 | assert.are_equal(200, text_box.width) 85 | assert.are_equal(50, text_box.y) 86 | assert.are_equal(100, text_box.height) 87 | end, 88 | 89 | ['enabling word wrap does not change any of the other properties'] = function() 90 | text_box.x = 20 91 | text_box.y = 30 92 | text_box.width = 40 93 | text_box.height = 50 94 | text_box.anchor = 'bottom right' 95 | text_box.multiline = true 96 | text_box.text = 'foo bar' 97 | text_box.word_wrap = true 98 | 99 | assert.are_equal(20, text_box.x) 100 | assert.are_equal(30, text_box.y) 101 | assert.are_equal(40, text_box.width) 102 | assert.are_equal(50, text_box.height) 103 | assert.are_equal('bottom right', text_box.anchor) 104 | assert.are_equal(true, text_box.multiline) 105 | assert.are_equal('foo bar', text_box.text) 106 | end 107 | } 108 | 109 | unit_test.run(suite) -------------------------------------------------------------------------------- /src/test/unit_test.lua: -------------------------------------------------------------------------------- 1 | local unit_test = { 2 | tests = {}, 3 | assert = {}, 4 | deferred = false, 5 | suites = {} 6 | } 7 | 8 | local current_test_name 9 | local failed_test_count = 0 10 | 11 | local function format_value(value) 12 | if type(value) == 'string' then 13 | return string.format("'%s'", value) 14 | else 15 | return tostring(value) 16 | end 17 | end 18 | 19 | local function report_assertion_failure(message) 20 | failed_test_count = failed_test_count + 1 21 | 22 | local caller_info = debug.getinfo(3) 23 | local file_name = string.sub(caller_info.source, 2) 24 | local line_number = caller_info.currentline 25 | 26 | io.write(string.format( 27 | '%s:%i: [%s] %s\n', 28 | file_name, 29 | line_number, 30 | current_test_name, 31 | message)) 32 | end 33 | 34 | local function report_result() 35 | if failed_test_count == 0 then 36 | io.write('All tests passed.\n') 37 | elseif failed_test_count == 1 then 38 | io.write('1 test failed.\n') 39 | else 40 | io.write(string.format('%i tests failed.\n', failed_test_count)) 41 | end 42 | 43 | io.write('\n') 44 | end 45 | 46 | local function add_test_suite(suite) 47 | table.insert(unit_test.suites, suite) 48 | end 49 | 50 | function unit_test.assert.are_equal(expected, actual) 51 | if expected ~= actual then 52 | local message = string.format('expected %s but got %s.', format_value(expected), format_value(actual)) 53 | report_assertion_failure(message) 54 | end 55 | end 56 | 57 | function unit_test.assert.is_true(expression) 58 | if not expression then 59 | report_assertion_failure('expected expression to be true, but was false.') 60 | end 61 | end 62 | 63 | function unit_test.assert.was_called(func) 64 | local name, value = debug.getupvalue(func, 1) 65 | 66 | if name ~= 'counter' or type(value) ~= 'number' then 67 | report_assertion_failure('function was not wrapped by unit_test.call_counter.') 68 | elseif value == 0 then 69 | report_assertion_failure("expected function to have been called, but it wasn't.") 70 | end 71 | end 72 | 73 | function unit_test.count_calls_to(func) 74 | local counter = 0 75 | local f = func or function() end 76 | 77 | return function(...) 78 | counter = counter + 1 79 | return f(...) 80 | end 81 | end 82 | 83 | function unit_test.gather() 84 | unit_test.run = add_test_suite 85 | end 86 | 87 | function unit_test.run(suite) 88 | add_test_suite(suite) 89 | unit_test.run_all() 90 | end 91 | 92 | function unit_test.run_all() 93 | io.write('\nRunning unit tests...\n') 94 | 95 | for _, suite in ipairs(unit_test.suites) do 96 | local set_up = suite.set_up or function() end 97 | local tear_down = suite.tear_down or function() end 98 | 99 | for test_name, run_test in pairs(suite) do 100 | if test_name ~= 'set_up' and test_name ~= 'tear_down' then 101 | current_test_name = test_name 102 | 103 | set_up() 104 | run_test() 105 | tear_down() 106 | end 107 | end 108 | end 109 | 110 | report_result() 111 | end 112 | 113 | return unit_test -------------------------------------------------------------------------------- /src/test/window.lua: -------------------------------------------------------------------------------- 1 | local unit_test = require 'test.unit_test' 2 | local assert = unit_test.assert 3 | 4 | require 'gui' 5 | 6 | local window 7 | 8 | local suite = { 9 | set_up = function() 10 | window = gui.create_window() 11 | end, 12 | 13 | ['a window has a size'] = function() 14 | window.width = 500 15 | window.height = 200 16 | 17 | assert.are_equal(500, window.width) 18 | assert.are_equal(200, window.height) 19 | end, 20 | 21 | ['a window has a position'] = function() 22 | window.x = 20 23 | window.y = 120 24 | 25 | assert.are_equal(20, window.x) 26 | assert.are_equal(120, window.y) 27 | end, 28 | 29 | ['a window has a title'] = function() 30 | window.title = 'my fine gui window' 31 | 32 | assert.are_equal(window.title, 'my fine gui window') 33 | end 34 | } 35 | 36 | unit_test.run(suite) -------------------------------------------------------------------------------- /zbplugin.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name = "Lua GUI", 3 | description = "An easy-to-use library for creating GUIs with Lua.", 4 | author = "William Willing", 5 | version = 4, 6 | 7 | install = function() 8 | local remotePath = "http://zerobranestore.blob.core.windows.net/luagui/" 9 | 10 | download(remotePath .. "button.lua", idePath.. "lualibs/gui/button.lua") 11 | download(remotePath .. "check.lua", idePath.. "lualibs/gui/check.lua") 12 | download(remotePath .. "common.anchor.lua", idePath.. "lualibs/gui/common/anchor.lua") 13 | download(remotePath .. "common.client_size.lua", idePath.. "lualibs/gui/common/client_size.lua") 14 | download(remotePath .. "common.color.lua", idePath.. "lualibs/gui/common/color.lua") 15 | download(remotePath .. "common.destroyable.lua", idePath .. "lualibs/gui/common/destroyable.lua") 16 | download(remotePath .. "common.focusable.lua", idePath .. "lualibs/gui/common/focusable.lua") 17 | download(remotePath .. "common.label.lua", idePath.. "lualibs/gui/common/label.lua") 18 | download(remotePath .. "common.lua", idePath.. "lualibs/gui/common.lua") 19 | download(remotePath .. "common.position.lua", idePath.. "lualibs/gui/common/position.lua") 20 | download(remotePath .. "common.resizable.lua", idePath.. "lualibs/gui/common/resizable.lua") 21 | download(remotePath .. "common.size.lua", idePath.. "lualibs/gui/common/size.lua") 22 | download(remotePath .. "common.text_color.lua", idePath.. "lualibs/gui/common/text_color.lua") 23 | download(remotePath .. "common.value.lua", idePath.. "lualibs/gui/common/value.lua") 24 | download(remotePath .. "dialog.lua", idePath.. "lualibs/gui/dialog.lua") 25 | download(remotePath .. "file_dialog.lua", idePath.. "lualibs/gui/file_dialog.lua") 26 | download(remotePath .. "gui.lua", idePath.. "lualibs/gui.lua") 27 | download(remotePath .. "image.lua", idePath.. "lualibs/gui/image.lua") 28 | download(remotePath .. "keyboard.lua", idePath.. "lualibs/gui/keyboard.lua") 29 | download(remotePath .. "label.lua", idePath.. "lualibs/gui/label.lua") 30 | download(remotePath .. "LICENSE", idePath .. "lualibs/gui/LICENSE") 31 | download(remotePath .. "menu.lua", idePath.. "lualibs/gui/menu.lua") 32 | download(remotePath .. "menu_bar.lua", idePath.. "lualibs/gui/menu_bar.lua") 33 | download(remotePath .. "menu_item.lua", idePath.. "lualibs/gui/menu_item.lua") 34 | download(remotePath .. "mouse.lua", idePath.. "lualibs/gui/mouse.lua") 35 | download(remotePath .. "plugin.lua", idePath.. "packages/luagui.lua") 36 | download(remotePath .. "text_box.lua", idePath.. "lualibs/gui/text_box.lua") 37 | download(remotePath .. "timer.lua", idePath.. "lualibs/gui/timer.lua") 38 | download(remotePath .. "window.lua", idePath.. "lualibs/gui/window.lua") 39 | end 40 | } --------------------------------------------------------------------------------