├── imgs
├── logo.png
├── beGUI1.png
├── beGUI2.png
├── grids.png
├── docking_absolutely.png
└── docking_relatively.png
├── src
├── disk.png
├── imgs
│ ├── slide.png
│ ├── button.png
│ ├── checkbox.png
│ ├── radiobox.png
│ ├── window.png
│ ├── button_up.png
│ ├── panel_gray.png
│ ├── button_close.png
│ ├── button_down.png
│ ├── button_left.png
│ ├── button_right.png
│ ├── panel_white.png
│ └── progressbar.png
├── fonts
│ └── ascii 8x8.png
├── info.json
├── config.json
├── libs
│ └── beGUI
│ │ ├── LICENSE_beGUI.txt
│ │ ├── beGUI_Structures.lua
│ │ ├── beClass.lua
│ │ ├── beGUI_Custom.lua
│ │ ├── beStack.lua
│ │ ├── beGUI.lua
│ │ ├── beTheme.lua
│ │ ├── beGUI_Popups.lua
│ │ ├── beTween.lua
│ │ ├── beGUI_Utils.lua
│ │ ├── beGUI_Widget.lua
│ │ └── beGUI_Containers.lua
├── keycode.lua
└── main.lua
├── docs
├── bitty.data
├── bitty.wasm
└── index.html
├── LICENSE
└── README.md
/imgs/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paladin-t/begui/HEAD/imgs/logo.png
--------------------------------------------------------------------------------
/src/disk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paladin-t/begui/HEAD/src/disk.png
--------------------------------------------------------------------------------
/docs/bitty.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paladin-t/begui/HEAD/docs/bitty.data
--------------------------------------------------------------------------------
/docs/bitty.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paladin-t/begui/HEAD/docs/bitty.wasm
--------------------------------------------------------------------------------
/imgs/beGUI1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paladin-t/begui/HEAD/imgs/beGUI1.png
--------------------------------------------------------------------------------
/imgs/beGUI2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paladin-t/begui/HEAD/imgs/beGUI2.png
--------------------------------------------------------------------------------
/imgs/grids.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paladin-t/begui/HEAD/imgs/grids.png
--------------------------------------------------------------------------------
/src/imgs/slide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paladin-t/begui/HEAD/src/imgs/slide.png
--------------------------------------------------------------------------------
/src/imgs/button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paladin-t/begui/HEAD/src/imgs/button.png
--------------------------------------------------------------------------------
/src/imgs/checkbox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paladin-t/begui/HEAD/src/imgs/checkbox.png
--------------------------------------------------------------------------------
/src/imgs/radiobox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paladin-t/begui/HEAD/src/imgs/radiobox.png
--------------------------------------------------------------------------------
/src/imgs/window.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paladin-t/begui/HEAD/src/imgs/window.png
--------------------------------------------------------------------------------
/src/fonts/ascii 8x8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paladin-t/begui/HEAD/src/fonts/ascii 8x8.png
--------------------------------------------------------------------------------
/src/imgs/button_up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paladin-t/begui/HEAD/src/imgs/button_up.png
--------------------------------------------------------------------------------
/src/imgs/panel_gray.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paladin-t/begui/HEAD/src/imgs/panel_gray.png
--------------------------------------------------------------------------------
/src/imgs/button_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paladin-t/begui/HEAD/src/imgs/button_close.png
--------------------------------------------------------------------------------
/src/imgs/button_down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paladin-t/begui/HEAD/src/imgs/button_down.png
--------------------------------------------------------------------------------
/src/imgs/button_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paladin-t/begui/HEAD/src/imgs/button_left.png
--------------------------------------------------------------------------------
/src/imgs/button_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paladin-t/begui/HEAD/src/imgs/button_right.png
--------------------------------------------------------------------------------
/src/imgs/panel_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paladin-t/begui/HEAD/src/imgs/panel_white.png
--------------------------------------------------------------------------------
/src/imgs/progressbar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paladin-t/begui/HEAD/src/imgs/progressbar.png
--------------------------------------------------------------------------------
/imgs/docking_absolutely.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paladin-t/begui/HEAD/imgs/docking_absolutely.png
--------------------------------------------------------------------------------
/imgs/docking_relatively.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paladin-t/begui/HEAD/imgs/docking_relatively.png
--------------------------------------------------------------------------------
/src/info.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 0,
3 | "title": "beGUI | Bitty Engine",
4 | "description": "Minimal customizable GUI system for Bitty Engine",
5 | "author": "Tony",
6 | "version": "1.5.9",
7 | "genre": "LIB",
8 | "url": "https://github.com/paladin-t/begui"
9 | }
--------------------------------------------------------------------------------
/src/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "application": {
3 | "_window": {
4 | "display_index": 0,
5 | "fullscreen": false,
6 | "maximized": false,
7 | "size": [
8 | 480,
9 | 320
10 | ]
11 | },
12 | "pause_on_focus_lost": false
13 | },
14 | "canvas": {
15 | "state": 0,
16 | "fix_ratio": true
17 | },
18 | "input": {
19 | "onscreen_gamepad": {
20 | "enabled": false,
21 | "swap_ab": false,
22 | "scale": 1.0,
23 | "padding": [
24 | 8.0,
25 | 12.0
26 | ]
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (C) 2021 - 2022 Tony Wang
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/libs/beGUI/LICENSE_beGUI.txt:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (C) 2021 - 2022 Tony Wang
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/src/libs/beGUI/beGUI_Structures.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | The MIT License
3 |
4 | Copyright (C) 2021 - 2022 Tony Wang
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal in
8 | the Software without restriction, including without limitation the rights to
9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10 | the Software, and to permit persons to whom the Software is furnished to do so,
11 | subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | ]]
23 |
24 | local beClass = require 'libs/beGUI/beClass'
25 |
26 | --[[
27 | Helper structures.
28 | ]]
29 |
30 | local Percent = beClass.class({
31 | amount = 0,
32 |
33 | -- Constructs a Percent structure.
34 | -- `amount`: typically [0, 100]
35 | ctor = function (self, amount)
36 | self.amount = amount / 100
37 | end,
38 |
39 | __mul = function (self, num)
40 | return self.amount * num
41 | end
42 | })
43 |
44 | local function percent(amount)
45 | return Percent.new(amount)
46 | end
47 |
48 | --[[
49 | Exporting.
50 | ]]
51 |
52 | return {
53 | Percent = Percent,
54 | percent = percent
55 | }
56 |
--------------------------------------------------------------------------------
/src/libs/beGUI/beClass.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | The MIT License
3 |
4 | Copyright (C) 2021 - 2022 Tony Wang
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal in
8 | the Software without restriction, including without limitation the rights to
9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10 | the Software, and to permit persons to whom the Software is furnished to do so,
11 | subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | ]]
23 |
24 | local function class(kls, base)
25 | if not base then
26 | base = { }
27 | end
28 |
29 | kls.new = function (...)
30 | local obj = { }
31 | setmetatable(obj, kls)
32 | if obj.ctor then
33 | obj:ctor(...)
34 | end
35 |
36 | return obj
37 | end
38 |
39 | kls.__index = kls
40 |
41 | setmetatable(kls, base)
42 |
43 | return kls
44 | end
45 |
46 | local function is(obj, kls)
47 | if kls == nil then
48 | error('Invalid class.')
49 | end
50 |
51 | repeat
52 | if obj == kls then
53 | return true
54 | end
55 | obj = getmetatable(obj)
56 | until not obj
57 |
58 | return false
59 | end
60 |
61 | local beClass = {
62 | class = class,
63 | is = is
64 | }
65 |
66 | return beClass
67 |
--------------------------------------------------------------------------------
/src/libs/beGUI/beGUI_Custom.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | The MIT License
3 |
4 | Copyright (C) 2021 - 2022 Tony Wang
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal in
8 | the Software without restriction, including without limitation the rights to
9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10 | the Software, and to permit persons to whom the Software is furnished to do so,
11 | subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | ]]
23 |
24 | local beClass = require 'libs/beGUI/beClass'
25 | local beWidget = require 'libs/beGUI/beGUI_Widget'
26 |
27 | --[[
28 | Widgets.
29 | ]]
30 |
31 | local Custom = beClass.class({
32 | _name = 'Custom',
33 |
34 | -- Constructs a Custom Widget.
35 | ctor = function (self, name)
36 | beWidget.Widget.ctor(self)
37 |
38 | if name then
39 | self._name = name
40 | end
41 | end,
42 |
43 | __tostring = function (self)
44 | return self.name
45 | end,
46 |
47 | navigatable = function (self)
48 | return 'children'
49 | end,
50 |
51 | name = function (self)
52 | return self._name
53 | end,
54 | setName = function (self, val)
55 | self._name = val
56 |
57 | return self
58 | end,
59 |
60 | _update = function (self, theme, delta, dx, dy, event)
61 | if not self.visibility then
62 | return
63 | end
64 |
65 | local ox, oy = self:offset()
66 | local px, py = self:position()
67 | local x, y = dx + px + ox, dy + py + oy
68 | local w, h = self:size()
69 | self:_trigger('updated', self, x, y, w, h, delta, event)
70 |
71 | beWidget.Widget._update(self, theme, delta, dx, dy, event)
72 | end
73 | }, beWidget.Widget)
74 |
75 | --[[
76 | Exporting.
77 | ]]
78 |
79 | return {
80 | Custom = Custom
81 | }
82 |
--------------------------------------------------------------------------------
/src/keycode.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | Example for the Bitty Engine
3 |
4 | Copyright (C) 2020 - 2022 Tony Wang, all rights reserved
5 |
6 | Homepage: https://paladin-t.github.io/bitty/
7 | ]]
8 |
9 | local function asc(s)
10 | return string.byte(s, 1)
11 | end
12 |
13 | local function toKeycode(k)
14 | return (1 << 30) | k
15 | end
16 |
17 | KeyCode = {
18 | Return = 13,
19 | Esc = 27,
20 | Backspace = 8,
21 | Tab = 9,
22 | Space = asc(' '),
23 |
24 | Exclaim = asc('!'),
25 | DoubleQuote = 34,
26 | Hash = asc('#'),
27 | Percent = asc('%'),
28 | Dollar = asc('$'),
29 | Ampersand = asc('&'),
30 | Quote = asc('\''),
31 | LeftParenthesis = asc('('),
32 | RightParenthesis = asc(')'),
33 | Asterisk = asc('*'),
34 | Plus = asc('+'),
35 | Comma = asc(','),
36 | Minus = asc('-'),
37 | Period = asc('.'),
38 | Slash = asc('/'),
39 |
40 | Num0 = asc('0'),
41 | Num1 = asc('1'),
42 | Num2 = asc('2'),
43 | Num3 = asc('3'),
44 | Num4 = asc('4'),
45 | Num5 = asc('5'),
46 | Num6 = asc('6'),
47 | Num7 = asc('7'),
48 | Num8 = asc('8'),
49 | Num9 = asc('9'),
50 |
51 | Colon = asc(':'),
52 | Semicolon = asc(';'),
53 | Less = asc('<'),
54 | Equals = asc('='),
55 | Greater = asc('>'),
56 | Question = asc('?'),
57 | At = asc('@'),
58 | LeftBracket = asc('['),
59 | Backslash = asc('\\'),
60 | RightBracket = asc(']'),
61 | Caret = asc('^'),
62 | Underscore = asc('_'),
63 | Backquote = asc('`'),
64 |
65 | A = asc('a'),
66 | B = asc('b'),
67 | C = asc('c'),
68 | D = asc('d'),
69 | E = asc('e'),
70 | F = asc('f'),
71 | G = asc('g'),
72 | H = asc('h'),
73 | I = asc('i'),
74 | J = asc('j'),
75 | K = asc('k'),
76 | L = asc('l'),
77 | M = asc('m'),
78 | N = asc('n'),
79 | O = asc('o'),
80 | P = asc('p'),
81 | Q = asc('q'),
82 | R = asc('r'),
83 | S = asc('s'),
84 | T = asc('t'),
85 | U = asc('u'),
86 | V = asc('v'),
87 | W = asc('w'),
88 | X = asc('x'),
89 | Y = asc('y'),
90 | Z = asc('z'),
91 |
92 | F1 = toKeycode(58),
93 | F2 = toKeycode(59),
94 | F3 = toKeycode(60),
95 | F4 = toKeycode(61),
96 | F5 = toKeycode(62),
97 | F6 = toKeycode(63),
98 | F7 = toKeycode(64),
99 | F8 = toKeycode(65),
100 | F9 = toKeycode(66),
101 | F10 = toKeycode(67),
102 | F11 = toKeycode(68),
103 | F12 = toKeycode(69),
104 |
105 | Insert = toKeycode(73),
106 | Home = toKeycode(74),
107 | PageUp = toKeycode(75),
108 | Delete = 127,
109 | End = toKeycode(77),
110 | PageDown = toKeycode(78),
111 | Right = toKeycode(79),
112 | Left = toKeycode(80),
113 | Down = toKeycode(81),
114 | Up = toKeycode(82),
115 |
116 | NumLockClear = toKeycode(83),
117 | KeypadDivide = toKeycode(84),
118 | KeypadMultiply = toKeycode(85),
119 | KeypadMinus = toKeycode(86),
120 | KeypadPlus = toKeycode(87),
121 | KeypadEnter = toKeycode(88),
122 | Keypad1 = toKeycode(89),
123 | Keypad2 = toKeycode(90),
124 | Keypad3 = toKeycode(91),
125 | Keypad4 = toKeycode(92),
126 | Keypad5 = toKeycode(93),
127 | Keypad6 = toKeycode(94),
128 | Keypad7 = toKeycode(95),
129 | Keypad8 = toKeycode(96),
130 | Keypad9 = toKeycode(97),
131 | Keypad0 = toKeycode(98),
132 | KeypadPeriod = toKeycode(99),
133 |
134 | LCtrl = toKeycode(224),
135 | LShift = toKeycode(225),
136 | LAlt = toKeycode(226),
137 | LGui = toKeycode(227),
138 | RCtrl = toKeycode(228),
139 | RShift = toKeycode(229),
140 | RAlt = toKeycode(230),
141 | RGui = toKeycode(231)
142 | }
143 |
--------------------------------------------------------------------------------
/src/libs/beGUI/beStack.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | The MIT License
3 |
4 | Copyright (C) 2021 - 2022 Tony Wang
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal in
8 | the Software without restriction, including without limitation the rights to
9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10 | the Software, and to permit persons to whom the Software is furnished to do so,
11 | subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | ]]
23 |
24 | local beClass = require 'libs/beGUI/beClass'
25 |
26 | --[[
27 | Stack.
28 | ]]
29 |
30 | local NonShrinkStack = beClass.class({
31 | _threshold = nil,
32 | _stack = nil,
33 | _count = 0,
34 |
35 | ctor = function (self, threshold)
36 | self._threshold = threshold or 5
37 | self._stack = { }
38 | self._count = 0
39 | end,
40 |
41 | __tostring = function (self)
42 | return 'NonShrinkStack'
43 | end,
44 |
45 | __len = function (self)
46 | return self._count
47 | end,
48 |
49 | push = function (self, arg1, arg2)
50 | if self._count >= self._threshold then
51 | error('Stack overflow.')
52 | end
53 | if self._count >= #self._stack then
54 | table.insert(self._stack, { arg1, arg2 })
55 | self._count = self._count + 1
56 | else
57 | self._count = self._count + 1
58 | local obj = self._stack[self._count]
59 | obj[1], obj[2] = arg1, arg2
60 | end
61 |
62 | return self
63 | end,
64 | pop = function (self)
65 | if self._count == 0 then
66 | error('Pop from empty stack.')
67 | end
68 | local result = self._stack[self._count]
69 | self._count = self._count - 1
70 | local ret1, ret2 = result[1], result[2]
71 | result[1], result[2] = false, false
72 |
73 | return ret1, ret2
74 | end,
75 | top = function (self)
76 | if self._count == 0 then
77 | return nil
78 | end
79 | local result = self._stack[self._count]
80 |
81 | return result[1], result[2]
82 | end,
83 | count = function (self)
84 | return self._count
85 | end,
86 | empty = function (self)
87 | return self._count == 0
88 | end,
89 | clear = function (self)
90 | while not self:empty() do
91 | self:pop()
92 | end
93 |
94 | return self
95 | end,
96 | get = function (self, index)
97 | if index < 1 or index > self._count then
98 | return nil
99 | end
100 |
101 | return self._stack[index]
102 | end,
103 | set = function (self, index, data)
104 | if index < 1 or index > self._count then
105 | return self
106 | end
107 |
108 | self._stack[index] = data
109 |
110 | return self
111 | end
112 | })
113 |
114 | --[[
115 | Exporting.
116 | ]]
117 |
118 | return {
119 | NonShrinkStack = NonShrinkStack
120 | }
121 |
--------------------------------------------------------------------------------
/src/libs/beGUI/beGUI.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | The MIT License
3 |
4 | Copyright (C) 2021 - 2022 Tony Wang
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal in
8 | the Software without restriction, including without limitation the rights to
9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10 | the Software, and to permit persons to whom the Software is furnished to do so,
11 | subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | ]]
23 |
24 | local beUtils = require 'libs/beGUI/beGUI_Utils'
25 | local beStructures = require 'libs/beGUI/beGUI_Structures'
26 | local beWidget = require 'libs/beGUI/beGUI_Widget'
27 | local beBasics = require 'libs/beGUI/beGUI_Basics'
28 | local beContainers = require 'libs/beGUI/beGUI_Containers'
29 | local bePopups = require 'libs/beGUI/beGUI_Popups'
30 | local beCustom = require 'libs/beGUI/beGUI_Custom'
31 | local beTween = require 'libs/beGUI/beTween'
32 |
33 | --[[
34 | Exporting.
35 | ]]
36 |
37 | if not beGUI then
38 | beGUI = { }
39 | end
40 | beGUI = beUtils.merge(
41 | beGUI,
42 | {
43 | version = '1.5.9',
44 |
45 | -- Data structure to represent relative number.
46 | percent = beStructures.percent,
47 |
48 | -- Widget class, base for other widgets, also could be used as container of other widgets.
49 | Widget = beWidget.Widget,
50 | -- Label widget.
51 | Label = beBasics.Label,
52 | -- MultilineLabel widget.
53 | MultilineLabel = beBasics.MultilineLabel,
54 | -- Url widget.
55 | -- Events:
56 | -- 'clicked': function (sender) end
57 | Url = beBasics.Url,
58 | -- InputBox widget.
59 | -- Events:
60 | -- 'changed': function (sender, value) end
61 | InputBox = beBasics.InputBox,
62 | -- Picture widget.
63 | Picture = beBasics.Picture,
64 | -- Button widget.
65 | -- Events:
66 | -- 'clicked': function (sender) end
67 | Button = beBasics.Button,
68 | -- PictureButton widget.
69 | -- Events:
70 | -- 'clicked': function (sender) end
71 | PictureButton = beBasics.PictureButton,
72 | -- CheckBox widget.
73 | -- Events:
74 | -- 'changed': function (sender, value) end
75 | CheckBox = beBasics.CheckBox,
76 | -- RadioBox widget.
77 | -- Events:
78 | -- 'changed': function (sender, value) end
79 | RadioBox = beBasics.RadioBox,
80 | -- ComboBox widget.
81 | -- Events:
82 | -- 'changed': function (sender, value) end
83 | ComboBox = beBasics.ComboBox,
84 | -- NumberBox widget.
85 | -- Events:
86 | -- 'changed': function (sender, value) end
87 | NumberBox = beBasics.NumberBox,
88 | -- ProgressBar widget.
89 | -- Events:
90 | -- 'changed': function (sender, value, maxValue, shadowValue) end
91 | ProgressBar = beBasics.ProgressBar,
92 | -- Slide widget.
93 | -- Events:
94 | -- 'changed': function (sender, value) end
95 | Slide = beBasics.Slide,
96 | -- Group widget.
97 | Group = beContainers.Group,
98 | -- List widget.
99 | List = beContainers.List,
100 | -- Draggable widget.
101 | Draggable = beContainers.Draggable,
102 | -- Droppable widget.
103 | -- Events:
104 | -- 'entered': function (sender, draggable) end
105 | -- 'left': function (sender, draggable) end
106 | -- 'dropping': function (sender, draggable) return droppable end
107 | -- 'dropped': function (sender, draggable) end
108 | -- 'clicked': function (sender) end
109 | Droppable = beContainers.Droppable,
110 | -- Tab widget.
111 | -- Events:
112 | -- 'changed': function (sender, value) end
113 | Tab = beContainers.Tab,
114 | -- Popup widget.
115 | Popup = bePopups.Popup,
116 | -- MessageBox widget.
117 | -- Events:
118 | -- 'canceled': function (sender) end
119 | -- 'confirmed': function (sender) end
120 | MessageBox = bePopups.MessageBox,
121 | -- QuestionBox widget.
122 | -- Events:
123 | -- 'canceled': function (sender) end
124 | -- 'confirmed': function (sender) end
125 | -- 'denied': function (sender) end
126 | QuestionBox = bePopups.QuestionBox,
127 | -- Custom widget.
128 | -- Events:
129 | -- 'updated': function (sender, x, y, w, h, delta, event) end
130 | Custom = beCustom.Custom,
131 | -- Tweening helper.
132 | Tween = beTween.Tween
133 | }
134 | )
135 |
--------------------------------------------------------------------------------
/src/libs/beGUI/beTheme.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | The MIT License
3 |
4 | Copyright (C) 2021 - 2022 Tony Wang
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal in
8 | the Software without restriction, including without limitation the rights to
9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10 | the Software, and to permit persons to whom the Software is furnished to do so,
11 | subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | ]]
23 |
24 | local function default()
25 | local font_ = Font.new('fonts/ascii 8x8.png', Vec2.new(8, 8))
26 |
27 | return {
28 | ['font'] = {
29 | resource = font_,
30 | color = Color.new(0, 0, 0)
31 | },
32 | ['font_white'] = {
33 | resource = font_,
34 | color = Color.new(255, 255, 255)
35 | },
36 | ['font_placeholder'] = {
37 | resource = font_,
38 | color = Color.new(138, 138, 138)
39 | },
40 | ['font_title'] = {
41 | resource = font_,
42 | color = Color.new(255, 255, 255)
43 | },
44 | ['font_url'] = {
45 | resource = font_,
46 | color = Color.new(0, 102, 255)
47 | },
48 | ['font_url_hover'] = {
49 | resource = font_,
50 | color = Color.new(0, 162, 255)
51 | },
52 |
53 | ['label'] = {
54 | content_offset = nil
55 | },
56 | ['label_shadow'] = {
57 | content_offset = { 1, 1 }
58 | },
59 | ['multilinelabel'] = {
60 | content_offset = nil
61 | },
62 |
63 | ['url'] = {
64 | content_offset = nil
65 | },
66 | ['url_down'] = {
67 | content_offset = nil
68 | },
69 |
70 | ['inputbox'] = {
71 | resource = Resources.load('imgs/panel_white.png'),
72 | area = { 0, 0, 17, 17 },
73 | content_offset = { 2, 0, 2 }
74 | },
75 |
76 | ['button'] = {
77 | resource = Resources.load('imgs/button.png'),
78 | area = { 0, 0, 23, 23 },
79 | content_offset = nil
80 | },
81 | ['button_down'] = {
82 | resource = Resources.load('imgs/button.png'),
83 | area = { 0, 23, 23, 23 },
84 | content_offset = { 0, 1 }
85 | },
86 | ['button_disabled'] = {
87 | resource = Resources.load('imgs/button.png'),
88 | area = { 0, 46, 23, 23 },
89 | content_offset = nil
90 | },
91 |
92 | ['button_close'] = {
93 | resource = Resources.load('imgs/button_close.png'),
94 | area = { 0, 0, 19, 19 },
95 | content_offset = nil
96 | },
97 | ['button_close_down'] = {
98 | resource = Resources.load('imgs/button_close.png'),
99 | area = { 0, 19, 19, 19 },
100 | content_offset = nil
101 | },
102 |
103 | ['checkbox'] = {
104 | resource = Resources.load('imgs/checkbox.png'),
105 | area = { 0, 0, 13, 13 },
106 | content_offset = { 16, 1 }
107 | },
108 | ['checkbox_selected'] = {
109 | resource = Resources.load('imgs/checkbox.png'),
110 | area = { 0, 13, 13, 13 },
111 | content_offset = { 16, 1 }
112 | },
113 | ['checkbox_disabled'] = {
114 | resource = Resources.load('imgs/checkbox.png'),
115 | area = { 13, 0, 13, 13 },
116 | content_offset = { 16, 1 }
117 | },
118 | ['checkbox_selected_disabled'] = {
119 | resource = Resources.load('imgs/checkbox.png'),
120 | area = { 13, 13, 13, 13 },
121 | content_offset = { 16, 1 }
122 | },
123 |
124 | ['radiobox'] = {
125 | resource = Resources.load('imgs/radiobox.png'),
126 | area = { 0, 0, 13, 13 },
127 | content_offset = { 16, 1 }
128 | },
129 | ['radiobox_selected'] = {
130 | resource = Resources.load('imgs/radiobox.png'),
131 | area = { 0, 13, 13, 13 },
132 | content_offset = { 16, 1 }
133 | },
134 |
135 | ['combobox'] = {
136 | resource = Resources.load('imgs/panel_gray.png'),
137 | area = { 0, 0, 17, 17 },
138 | content_offset = { 1, 1 }
139 | },
140 | ['combobox_button_left'] = {
141 | resource = Resources.load('imgs/button_left.png'),
142 | area = { 0, 0, 17, 17 },
143 | content_offset = nil
144 | },
145 | ['combobox_button_left_down'] = {
146 | resource = Resources.load('imgs/button_left.png'),
147 | area = { 0, 17, 17, 17 },
148 | content_offset = nil
149 | },
150 | ['combobox_button_right'] = {
151 | resource = Resources.load('imgs/button_right.png'),
152 | area = { 0, 0, 17, 17 },
153 | content_offset = nil
154 | },
155 | ['combobox_button_right_down'] = {
156 | resource = Resources.load('imgs/button_right.png'),
157 | area = { 0, 17, 17, 17 },
158 | content_offset = nil
159 | },
160 |
161 | ['numberbox'] = {
162 | resource = Resources.load('imgs/panel_gray.png'),
163 | area = { 0, 0, 17, 17 },
164 | content_offset = { 1, 1 }
165 | },
166 | ['numberbox_button_up'] = {
167 | resource = Resources.load('imgs/button_up.png'),
168 | area = { 0, 0, 17, 17 },
169 | content_offset = nil
170 | },
171 | ['numberbox_button_up_down'] = {
172 | resource = Resources.load('imgs/button_up.png'),
173 | area = { 0, 17, 17, 17 },
174 | content_offset = nil
175 | },
176 | ['numberbox_button_down'] = {
177 | resource = Resources.load('imgs/button_down.png'),
178 | area = { 0, 0, 17, 17 },
179 | content_offset = nil
180 | },
181 | ['numberbox_button_down_down'] = {
182 | resource = Resources.load('imgs/button_down.png'),
183 | area = { 0, 17, 17, 17 },
184 | content_offset = nil
185 | },
186 |
187 | ['progressbar'] = {
188 | resource = Resources.load('imgs/progressbar.png'),
189 | area = { 0, 0, 17, 17 },
190 | content_offset = { 2, 2 }
191 | },
192 |
193 | ['slide'] = {
194 | resource = Resources.load('imgs/slide.png'),
195 | color = Color.new(0, 0, 0),
196 | area = { 0, 0, 13, 17 },
197 | content_offset = nil
198 | },
199 |
200 | ['group'] = {
201 | resource = nil,
202 | color = Color.new(0, 0, 0),
203 | area = nil,
204 | content_offset = { 8, 0 }
205 | },
206 | ['group_title'] = {
207 | content_offset = nil
208 | },
209 |
210 | ['list'] = {
211 | resource = Resources.load('imgs/panel_white.png'),
212 | color = Color.new(127, 127, 127, 128),
213 | area = { 0, 0, 17, 17 },
214 | content_offset = nil
215 | },
216 |
217 | ['tab'] = {
218 | resource = nil,
219 | color = Color.new(0, 0, 0),
220 | area = nil,
221 | content_offset = { 3, 3 }
222 | },
223 | ['tab_title'] = {
224 | content_offset = nil
225 | },
226 |
227 | ['window'] = {
228 | resource = Resources.load('imgs/window.png')
229 | }
230 | }
231 | end
232 |
233 | beTheme = {
234 | default = default
235 | }
236 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
135 |
136 | beGUI live demo, source code
137 |
138 |
139 | Powered by Bitty Engine
140 |
141 |
142 |
143 |
144 |
145 |
146 |
218 |
233 |
234 |
235 |
236 |
--------------------------------------------------------------------------------
/src/libs/beGUI/beGUI_Popups.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | The MIT License
3 |
4 | Copyright (C) 2021 - 2022 Tony Wang
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal in
8 | the Software without restriction, including without limitation the rights to
9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10 | the Software, and to permit persons to whom the Software is furnished to do so,
11 | subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | ]]
23 |
24 | local beClass = require 'libs/beGUI/beClass'
25 | local beStructures = require 'libs/beGUI/beGUI_Structures'
26 | local beWidget = require 'libs/beGUI/beGUI_Widget'
27 |
28 | --[[
29 | Widgets.
30 | ]]
31 |
32 | local Popup = beClass.class({
33 | _scheduled = nil,
34 |
35 | -- Constructs a Popup.
36 | ctor = function (self)
37 | beWidget.Widget.ctor(self)
38 | end,
39 |
40 | __tostring = function (self)
41 | return 'Popup'
42 | end,
43 |
44 | navigatable = function (self)
45 | return 'children'
46 | end,
47 |
48 | schedule = function (self, func)
49 | self._scheduled = func
50 | end,
51 |
52 | _update = function (self, theme, delta, dx, dy, event)
53 | if not self.visibility then
54 | return
55 | end
56 |
57 | local ox, oy = self:offset()
58 | local px, py = self:position()
59 | local x, y = dx + px + ox, dy + py + oy
60 | local w, h = self:size()
61 |
62 | if self.transparency then
63 | local col = Color.new(80, 80, 80, 120 * (self.transparency / 255))
64 | rect(x, y, x + w, y + h, true, col)
65 | else
66 | rect(x, y, x + w, y + h, true, Color.new(80, 80, 80, 120))
67 | end
68 |
69 | beWidget.Widget._update(self, theme, delta, dx, dy, event)
70 |
71 | if self._scheduled then
72 | self._scheduled()
73 | self._scheduled = nil
74 | end
75 | end
76 | }, beWidget.Widget)
77 |
78 | local MessageBox = beClass.class({
79 | _closable = true,
80 | _title = nil,
81 | _message = nil,
82 | _confirm = 'OK',
83 | _initialized = false,
84 |
85 | -- Constructs a MessageBox.
86 | -- `closable`: `true` to enable the close button, `false` to disable
87 | -- `title`: the title text
88 | -- `message`: the message text
89 | -- `confirm`: the text for the confirm button
90 | ctor = function (self, closable, title, message, confirm)
91 | Popup.ctor(self)
92 |
93 | self._closable = closable
94 | self._title = title or 'Bitty Engine'
95 | self._message = message
96 | if confirm then
97 | self._confirm = confirm
98 | end
99 |
100 | local P = beStructures.percent
101 | self
102 | :setId('popup')
103 | :anchor(0, 0)
104 | :put(0, 0)
105 | :resize(P(100), P(100))
106 | end,
107 |
108 | __tostring = function (self)
109 | return 'MessageBox'
110 | end,
111 |
112 | _update = function (self, theme, delta, dx, dy, event)
113 | if not self._initialized then
114 | self._initialized = true
115 |
116 | local width = 256
117 | local P = beStructures.percent
118 | local child = beGUI.Picture.new(theme['window'], true)
119 | :setId('picture')
120 | :anchor(0.5, 0.5)
121 | :put(P(50), P(50))
122 | :resize(width, 128)
123 | :addChild(
124 | beGUI.Label.new(self._title, 'center', true, 'font_title')
125 | :setId('label_title')
126 | :anchor(0.5, 0)
127 | :put(P(50), 3)
128 | :resize(P(100), 23)
129 | )
130 | :addChild(
131 | beGUI.Label.new(self._message, 'center', true)
132 | :setId('label_message')
133 | :anchor(0.5, 1)
134 | :put(P(50), P(50))
135 | :resize(P(100), 23)
136 | )
137 | :addChild(
138 | beGUI.Button.new(self._confirm)
139 | :setId('button_confirm')
140 | :anchor(0.5, 1)
141 | :put(P(50), P(90))
142 | :resize(P(24), 23)
143 | :on('clicked', function (sender)
144 | self:_trigger('confirmed', self)
145 | end)
146 | )
147 | if self._closable then
148 | child
149 | :addChild(
150 | beGUI.PictureButton.new('', false, { normal = 'button_close', pressed = 'button_close_down' })
151 | :setId('button_close')
152 | :anchor(1, 0)
153 | :put(width - 6, 5)
154 | :resize(19, 19)
155 | :on('clicked', function (sender)
156 | self:_trigger('canceled', self)
157 | end)
158 | )
159 | end
160 | self:addChild(child)
161 |
162 | local w, h = self:size()
163 | child:_updateLayout(w, h)
164 | end
165 |
166 | Popup._update(self, theme, delta, dx, dy, event)
167 | end,
168 |
169 | _cancelNavigation = function (self)
170 | if self._closable then
171 | self:_trigger('canceled', self)
172 | end
173 |
174 | self.context.focus = nil
175 | self.context.navigated = false
176 | end
177 | }, Popup)
178 |
179 | local QuestionBox = beClass.class({
180 | _closable = true,
181 | _title = nil,
182 | _message = nil,
183 | _confirm = 'Yes',
184 | _deny = 'No',
185 | _initialized = false,
186 |
187 | -- Constructs a QuestionBox.
188 | -- `closable`: `true` to enable the close button, `false` to disable
189 | -- `title`: the title text
190 | -- `message`: the message text
191 | -- `confirm`: the text for the confirm button
192 | -- `deny`: the text for the deny button
193 | ctor = function (self, closable, title, message, confirm, deny)
194 | Popup.ctor(self)
195 |
196 | self._closable = closable
197 | self._title = title or 'Bitty Engine'
198 | self._message = message
199 | if confirm then
200 | self._confirm = confirm
201 | end
202 | if deny then
203 | self._deny = deny
204 | end
205 |
206 | local P = beStructures.percent
207 | self
208 | :setId('popup')
209 | :anchor(0, 0)
210 | :put(0, 0)
211 | :resize(P(100), P(100))
212 | end,
213 |
214 | __tostring = function (self)
215 | return 'QuestionBox'
216 | end,
217 |
218 | _update = function (self, theme, delta, dx, dy, event)
219 | if not self._initialized then
220 | self._initialized = true
221 |
222 | local width = 256
223 | local P = beStructures.percent
224 | local child = beGUI.Picture.new(theme['window'], true)
225 | :setId('picture')
226 | :anchor(0.5, 0.5)
227 | :put(P(50), P(50))
228 | :resize(width, 128)
229 | :addChild(
230 | beGUI.Label.new(self._title, 'center', true, 'font_title')
231 | :setId('label_title')
232 | :anchor(0.5, 0)
233 | :put(P(50), 3)
234 | :resize(P(100), 23)
235 | )
236 | :addChild(
237 | beGUI.Label.new(self._message, 'center', true)
238 | :setId('label_message')
239 | :anchor(0.5, 1)
240 | :put(P(50), P(50))
241 | :resize(P(100), 23)
242 | )
243 | :addChild(
244 | beGUI.Button.new(self._confirm)
245 | :setId('button_confirm')
246 | :anchor(1.1, 1)
247 | :put(P(50), P(90))
248 | :resize(P(24), 23)
249 | :on('clicked', function (sender)
250 | self:_trigger('confirmed', self)
251 | end)
252 | )
253 | :addChild(
254 | beGUI.Button.new(self._deny)
255 | :setId('button_deny')
256 | :anchor(-0.1, 1)
257 | :put(P(50), P(90))
258 | :resize(P(24), 23)
259 | :on('clicked', function (sender)
260 | self:_trigger('denied', self)
261 | end)
262 | )
263 | if self._closable then
264 | child
265 | :addChild(
266 | beGUI.PictureButton.new('', false, { normal = 'button_close', pressed = 'button_close_down' })
267 | :setId('button_close')
268 | :anchor(1, 0)
269 | :put(width - 6, 5)
270 | :resize(19, 19)
271 | :on('clicked', function (sender)
272 | self:_trigger('canceled', self)
273 | end)
274 | )
275 | end
276 | self:addChild(child)
277 |
278 | local w, h = self:size()
279 | child:_updateLayout(w, h)
280 | end
281 |
282 | Popup._update(self, theme, delta, dx, dy, event)
283 | end,
284 |
285 | _cancelNavigation = function (self)
286 | if self._closable then
287 | self:_trigger('canceled', self)
288 | end
289 |
290 | self.context.focus = nil
291 | self.context.navigated = false
292 | end
293 | }, Popup)
294 |
295 | --[[
296 | Exporting.
297 | ]]
298 |
299 | return {
300 | Popup = Popup,
301 | MessageBox = MessageBox,
302 | QuestionBox = QuestionBox
303 | }
304 |
--------------------------------------------------------------------------------
/src/libs/beGUI/beTween.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | MIT LICENSE
3 |
4 | Copyright (c) 2014 Enrique García Cota, Yuichi Tateno, Emmanuel Oga
5 | Adapted by Tony for Bitty Engine
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a
8 | copy of this software and associated documentation files (the
9 | "Software"), to deal in the Software without restriction, including
10 | without limitation the rights to use, copy, modify, merge, publish,
11 | distribute, sublicense, and/or sell copies of the Software, and to
12 | permit persons to whom the Software is furnished to do so, subject to
13 | the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be included
16 | in all copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 | ]]
26 |
27 | local tween = {
28 | _VERSION = 'Tween 2.1.1',
29 | _DESCRIPTION = 'Tweening for Lua',
30 | _URL = 'https://github.com/kikito/tween.lua'
31 | }
32 |
33 | -- Easing.
34 |
35 | -- Adapted from https://github.com/EmmanuelOga/easing.
36 | -- For all easing functions:
37 | -- t = time == how much time has to pass for the tweening to complete
38 | -- b = begin == starting property value
39 | -- c = change == ending - beginning
40 | -- d = duration == running time. How much time has passed *right now*
41 |
42 | local pow, sin, cos, pi, sqrt, abs, asin = function (a, b) return a ^ b end, math.sin, math.cos, math.pi, math.sqrt, math.abs, math.asin
43 |
44 | -- Linear.
45 | local function linear(t, b, c, d)
46 | return c * t / d + b
47 | end
48 |
49 | -- Quad.
50 | local function inQuad(t, b, c, d)
51 | return c * pow(t / d, 2) + b
52 | end
53 | local function outQuad(t, b, c, d)
54 | t = t / d
55 |
56 | return -c * t * (t - 2) + b
57 | end
58 | local function inOutQuad(t, b, c, d)
59 | t = t / d * 2
60 | if t < 1 then
61 | return c / 2 * pow(t, 2) + b
62 | end
63 |
64 | return -c / 2 * ((t - 1) * (t - 3) - 1) + b
65 | end
66 | local function outInQuad(t, b, c, d)
67 | if t < d / 2 then
68 | return outQuad(t * 2, b, c / 2, d)
69 | end
70 |
71 | return inQuad((t * 2) - d, b + c / 2, c / 2, d)
72 | end
73 |
74 | -- Cubic.
75 | local function inCubic (t, b, c, d)
76 | return c * pow(t / d, 3) + b
77 | end
78 | local function outCubic(t, b, c, d)
79 | return c * (pow(t / d - 1, 3) + 1) + b
80 | end
81 | local function inOutCubic(t, b, c, d)
82 | t = t / d * 2
83 | if t < 1 then
84 | return c / 2 * t * t * t + b
85 | end
86 | t = t - 2
87 |
88 | return c / 2 * (t * t * t + 2) + b
89 | end
90 | local function outInCubic(t, b, c, d)
91 | if t < d / 2 then
92 | return outCubic(t * 2, b, c / 2, d)
93 | end
94 |
95 | return inCubic((t * 2) - d, b + c / 2, c / 2, d)
96 | end
97 |
98 | -- Quart.
99 | local function inQuart(t, b, c, d)
100 | return c * pow(t / d, 4) + b
101 | end
102 | local function outQuart(t, b, c, d)
103 | return -c * (pow(t / d - 1, 4) - 1) + b
104 | end
105 | local function inOutQuart(t, b, c, d)
106 | t = t / d * 2
107 | if t < 1 then
108 | return c / 2 * pow(t, 4) + b
109 | end
110 |
111 | return -c / 2 * (pow(t - 2, 4) - 2) + b
112 | end
113 | local function outInQuart(t, b, c, d)
114 | if t < d / 2 then
115 | return outQuart(t * 2, b, c / 2, d)
116 | end
117 |
118 | return inQuart((t * 2) - d, b + c / 2, c / 2, d)
119 | end
120 |
121 | -- Quint.
122 | local function inQuint(t, b, c, d)
123 | return c * pow(t / d, 5) + b
124 | end
125 | local function outQuint(t, b, c, d)
126 | return c * (pow(t / d - 1, 5) + 1) + b
127 | end
128 | local function inOutQuint(t, b, c, d)
129 | t = t / d * 2
130 | if t < 1 then
131 | return c / 2 * pow(t, 5) + b
132 | end
133 |
134 | return c / 2 * (pow(t - 2, 5) + 2) + b
135 | end
136 | local function outInQuint(t, b, c, d)
137 | if t < d / 2 then
138 | return outQuint(t * 2, b, c / 2, d)
139 | end
140 |
141 | return inQuint((t * 2) - d, b + c / 2, c / 2, d)
142 | end
143 |
144 | -- Sine.
145 | local function inSine(t, b, c, d)
146 | return -c * cos(t / d * (pi / 2)) + c + b
147 | end
148 | local function outSine(t, b, c, d)
149 | return c * sin(t / d * (pi / 2)) + b
150 | end
151 | local function inOutSine(t, b, c, d)
152 | return -c / 2 * (cos(pi * t / d) - 1) + b
153 | end
154 | local function outInSine(t, b, c, d)
155 | if t < d / 2 then
156 | return outSine(t * 2, b, c / 2, d)
157 | end
158 |
159 | return inSine((t * 2) -d, b + c / 2, c / 2, d)
160 | end
161 |
162 | -- Expo.
163 | local function inExpo(t, b, c, d)
164 | if t == 0 then
165 | return b
166 | end
167 |
168 | return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001
169 | end
170 | local function outExpo(t, b, c, d)
171 | if t == d then
172 | return b + c
173 | end
174 |
175 | return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b
176 | end
177 | local function inOutExpo(t, b, c, d)
178 | if t == 0 then
179 | return b
180 | end
181 | if t == d then
182 | return b + c
183 | end
184 | t = t / d * 2
185 | if t < 1 then
186 | return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005
187 | end
188 |
189 | return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b
190 | end
191 | local function outInExpo(t, b, c, d)
192 | if t < d / 2 then
193 | return outExpo(t * 2, b, c / 2, d)
194 | end
195 |
196 | return inExpo((t * 2) - d, b + c / 2, c / 2, d)
197 | end
198 |
199 | -- Circ.
200 | local function inCirc(t, b, c, d)
201 | return (-c * (sqrt(1 - pow(t / d, 2)) - 1) + b)
202 | end
203 | local function outCirc(t, b, c, d)
204 | return (c * sqrt(1 - pow(t / d - 1, 2)) + b)
205 | end
206 | local function inOutCirc(t, b, c, d)
207 | t = t / d * 2
208 | if t < 1 then
209 | return -c / 2 * (sqrt(1 - t * t) - 1) + b
210 | end
211 | t = t - 2
212 |
213 | return c / 2 * (sqrt(1 - t * t) + 1) + b
214 | end
215 | local function outInCirc(t, b, c, d)
216 | if t < d / 2 then
217 | return outCirc(t * 2, b, c / 2, d)
218 | end
219 |
220 | return inCirc((t * 2) - d, b + c / 2, c / 2, d)
221 | end
222 |
223 | -- Elastic.
224 | local function calculatePAS(p, a, c, d)
225 | p, a = p or d * 0.3, a or 0
226 | if a < abs(c) then
227 | return p, c, p / 4
228 | end -- p, a, s.
229 |
230 | return p, a, p / (2 * pi) * asin(c / a) -- p, a, s.
231 | end
232 | local function inElastic(t, b, c, d, a, p)
233 | local s
234 | if t == 0 then
235 | return b
236 | end
237 | t = t / d
238 | if t == 1 then
239 | return b + c
240 | end
241 | p, a, s = calculatePAS(p, a, c, d)
242 | t = t - 1
243 |
244 | return -(a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b
245 | end
246 | local function outElastic(t, b, c, d, a, p)
247 | local s
248 | if t == 0 then
249 | return b
250 | end
251 | t = t / d
252 | if t == 1 then
253 | return b + c
254 | end
255 | p, a, s = calculatePAS(p, a, c, d)
256 |
257 | return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) + c + b
258 | end
259 | local function inOutElastic(t, b, c, d, a, p)
260 | local s
261 | if t == 0 then
262 | return b
263 | end
264 | t = t / d * 2
265 | if t == 2 then
266 | return b + c
267 | end
268 | p, a, s = calculatePAS(p, a, c, d)
269 | t = t - 1
270 | if t < 0 then
271 | return -0.5 * (a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b
272 | end
273 |
274 | return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) * 0.5 + c + b
275 | end
276 | local function outInElastic(t, b, c, d, a, p)
277 | if t < d / 2 then
278 | return outElastic(t * 2, b, c / 2, d, a, p)
279 | end
280 |
281 | return inElastic((t * 2) - d, b + c / 2, c / 2, d, a, p)
282 | end
283 |
284 | -- Back.
285 | local function inBack(t, b, c, d, s)
286 | s = s or 1.70158
287 | t = t / d
288 |
289 | return c * t * t * ((s + 1) * t - s) + b
290 | end
291 | local function outBack(t, b, c, d, s)
292 | s = s or 1.70158
293 | t = t / d - 1
294 |
295 | return c * (t * t * ((s + 1) * t + s) + 1) + b
296 | end
297 | local function inOutBack(t, b, c, d, s)
298 | s = (s or 1.70158) * 1.525
299 | t = t / d * 2
300 | if t < 1 then
301 | return c / 2 * (t * t * ((s + 1) * t - s)) + b
302 | end
303 | t = t - 2
304 |
305 | return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b
306 | end
307 | local function outInBack(t, b, c, d, s)
308 | if t < d / 2 then
309 | return outBack(t * 2, b, c / 2, d, s)
310 | end
311 |
312 | return inBack((t * 2) - d, b + c / 2, c / 2, d, s)
313 | end
314 |
315 | -- Bounce.
316 | local function outBounce(t, b, c, d)
317 | t = t / d
318 | if t < 1 / 2.75 then
319 | return c * (7.5625 * t * t) + b
320 | end
321 | if t < 2 / 2.75 then
322 | t = t - (1.5 / 2.75)
323 |
324 | return c * (7.5625 * t * t + 0.75) + b
325 | elseif t < 2.5 / 2.75 then
326 | t = t - (2.25 / 2.75)
327 |
328 | return c * (7.5625 * t * t + 0.9375) + b
329 | end
330 | t = t - (2.625 / 2.75)
331 |
332 | return c * (7.5625 * t * t + 0.984375) + b
333 | end
334 | local function inBounce(t, b, c, d)
335 | return c - outBounce(d - t, 0, c, d) + b
336 | end
337 | local function inOutBounce(t, b, c, d)
338 | if t < d / 2 then
339 | return inBounce(t * 2, 0, c, d) * 0.5 + b
340 | end
341 |
342 | return outBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b
343 | end
344 | local function outInBounce(t, b, c, d)
345 | if t < d / 2 then
346 | return outBounce(t * 2, b, c / 2, d)
347 | end
348 |
349 | return inBounce((t * 2) - d, b + c / 2, c / 2, d)
350 | end
351 |
352 | tween.easing = {
353 | linear = linear,
354 | inQuad = inQuad, outQuad = outQuad, inOutQuad = inOutQuad, outInQuad = outInQuad,
355 | inCubic = inCubic, outCubic = outCubic, inOutCubic = inOutCubic, outInCubic = outInCubic,
356 | inQuart = inQuart, outQuart = outQuart, inOutQuart = inOutQuart, outInQuart = outInQuart,
357 | inQuint = inQuint, outQuint = outQuint, inOutQuint = inOutQuint, outInQuint = outInQuint,
358 | inSine = inSine, outSine = outSine, inOutSine = inOutSine, outInSine = outInSine,
359 | inExpo = inExpo, outExpo = outExpo, inOutExpo = inOutExpo, outInExpo = outInExpo,
360 | inCirc = inCirc, outCirc = outCirc, inOutCirc = inOutCirc, outInCirc = outInCirc,
361 | inElastic = inElastic, outElastic = outElastic, inOutElastic = inOutElastic, outInElastic = outInElastic,
362 | inBack = inBack, outBack = outBack, inOutBack = inOutBack, outInBack = outInBack,
363 | inBounce = inBounce, outBounce = outBounce, inOutBounce = inOutBounce, outInBounce = outInBounce
364 | }
365 |
366 | -- Private stuff.
367 |
368 | local function copyTables(destination, keysTable, valuesTable)
369 | valuesTable = valuesTable or keysTable
370 | local mt = getmetatable(keysTable)
371 | if mt and getmetatable(destination) == nil then
372 | setmetatable(destination, mt)
373 | end
374 | for k, v in pairs(keysTable) do
375 | if type(v) == 'table' then
376 | destination[k] = copyTables({ }, v, valuesTable[k])
377 | else
378 | destination[k] = valuesTable[k]
379 | end
380 | end
381 |
382 | return destination
383 | end
384 |
385 | local function checkSubjectAndTargetRecursively(subject, target, path)
386 | path = path or { }
387 | local targetType, newPath
388 | for k, targetValue in pairs(target) do
389 | targetType, newPath = type(targetValue), copyTables({ }, path)
390 | table.insert(newPath, tostring(k))
391 | if targetType == 'number' then
392 | assert(type(subject[k]) == 'number', "Parameter '" .. table.concat(newPath,'/') .. "' is missing from subject or isn't a number")
393 | elseif targetType == 'table' then
394 | checkSubjectAndTargetRecursively(subject[k], targetValue, newPath)
395 | else
396 | assert(targetType == 'number', "Parameter '" .. table.concat(newPath,'/') .. "' must be a number or table of numbers")
397 | end
398 | end
399 | end
400 |
401 | local function checkNewParams(duration, subject, target, easing)
402 | assert(type(duration) == 'number' and duration > 0, "duration must be a positive number. Was " .. tostring(duration))
403 | local tsubject = type(subject)
404 | assert(tsubject == 'table' or tsubject == 'userdata', "subject must be a table or userdata. Was " .. tostring(subject))
405 | assert(type(target) == 'table', "target must be a table. Was " .. tostring(target))
406 | assert(type(easing) =='function', "easing must be a function. Was " .. tostring(easing))
407 | checkSubjectAndTargetRecursively(subject, target)
408 | end
409 |
410 | local function getEasingFunction(easing)
411 | easing = easing or "linear"
412 | if type(easing) == 'string' then
413 | local name = easing
414 | easing = tween.easing[name]
415 | if type(easing) ~= 'function' then
416 | error("The easing function name '" .. name .. "' is invalid")
417 | end
418 | end
419 |
420 | return easing
421 | end
422 |
423 | local function performEasingOnSubject(subject, target, initial, clock, duration, easing)
424 | local t, b, c, d
425 | for k, v in pairs(target) do
426 | if type(v) == 'table' then
427 | performEasingOnSubject(subject[k], v, initial[k], clock, duration, easing)
428 | else
429 | t, b, c, d = clock, initial[k], v - initial[k], duration
430 | subject[k] = easing(t, b, c, d)
431 | end
432 | end
433 | end
434 |
435 | -- Tween methods.
436 |
437 | local Tween_idx = { }
438 | local Tween_mt = { __index = Tween_idx }
439 |
440 | function Tween_idx:reset()
441 | self.initial = self.initial or copyTables({ }, self.target, self.subject)
442 | self.clock = 0
443 | copyTables(self.subject, self.initial)
444 | self.finished = false
445 |
446 | return self
447 | end
448 |
449 | function Tween_idx:set(clock)
450 | assert(type(clock) == 'number', "clock must be a positive number or 0")
451 |
452 | if self.clock == clock then
453 | return self.clock >= self.duration
454 | end
455 |
456 | self.initial = self.initial or copyTables({ }, self.target, self.subject)
457 | self.clock = clock
458 |
459 | if self.clock <= 0 then
460 | self.clock = 0
461 | copyTables(self.subject, self.initial)
462 | elseif self.clock >= self.duration then -- The tween has expired.
463 | self.clock = self.duration
464 | copyTables(self.subject, self.target)
465 | else
466 | performEasingOnSubject(self.subject, self.target, self.initial, self.clock, self.duration, self.easing)
467 | end
468 |
469 | self:trigger('changed', self.subject)
470 |
471 | if self.clock < self.duration then
472 | return false
473 | end
474 | if self.loop then
475 | self:trigger('completed', self.subject)
476 | self.clock = 0
477 |
478 | return false
479 | end
480 | if not self.finished then
481 | self:trigger('completed', self.subject)
482 | self.finished = true
483 | end
484 |
485 | return true
486 | end
487 |
488 | function Tween_idx:update(delta)
489 | assert(type(delta) == 'number', "delta must be a number")
490 |
491 | return self:set(self.clock + delta)
492 | end
493 |
494 | function Tween_idx:on(event, handler)
495 | if not event then
496 | return self
497 | end
498 | if self.events == nil then
499 | self.events = { }
500 | end
501 | self.events[event] = handler
502 |
503 | return self
504 | end
505 |
506 | function Tween_idx:off(event)
507 | if not event then
508 | return self
509 | end
510 | if self.events == nil then
511 | return self
512 | end
513 | if self.events[event] == nil then
514 | return self
515 | end
516 | self.events[event] = nil
517 |
518 | return self
519 | end
520 |
521 | function Tween_idx:trigger(event, ...)
522 | if not event then
523 | return nil
524 | end
525 | if self.events == nil then
526 | return nil
527 | end
528 | if not self.events[event] then
529 | return nil
530 | end
531 | local ret = self.events[event](self, ...)
532 |
533 | return ret
534 | end
535 |
536 | -- Public interface.
537 |
538 | function tween.new(duration, subject, target, easing, loop)
539 | easing = getEasingFunction(easing)
540 | checkNewParams(duration, subject, target, easing)
541 |
542 | return setmetatable({
543 | duration = duration,
544 | subject = subject,
545 | target = target,
546 | easing = easing,
547 | clock = 0,
548 | finished = false,
549 | loop = loop
550 | }, Tween_mt)
551 | end
552 |
553 | --[[
554 | Exporting.
555 | ]]
556 |
557 | return {
558 | Tween = tween
559 | }
560 |
--------------------------------------------------------------------------------
/src/libs/beGUI/beGUI_Utils.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | The MIT License
3 |
4 | Copyright (C) 2021 - 2022 Tony Wang
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal in
8 | the Software without restriction, including without limitation the rights to
9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10 | the Software, and to permit persons to whom the Software is furnished to do so,
11 | subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | ]]
23 |
24 | --[[
25 | Constants.
26 | ]]
27 |
28 | local function toKeycode(k)
29 | return (1 << 30) | k
30 | end
31 |
32 | local KeyCodeLShift = toKeycode(225)
33 | local KeyCodeRShift = toKeycode(229)
34 |
35 | --[[
36 | Helper functions.
37 | ]]
38 |
39 | --[[ Number. ]]
40 |
41 | local function NaN(val)
42 | return 0 / 0
43 | end
44 |
45 | local function isNaN(val)
46 | return val ~= val
47 | end
48 |
49 | local function round(val)
50 | if val >= 0 then
51 | return math.floor(val + 0.5)
52 | else
53 | return math.ceil(val - 0.5)
54 | end
55 | end
56 |
57 | local function clamp(x, min, max)
58 | return math.max(math.min(x, max), min)
59 | end
60 |
61 | --[[ Math. ]]
62 |
63 | local function intersected(rect1, rect2)
64 | if not rect1 then
65 | return nil
66 | end
67 | if not rect2 then
68 | return rect1
69 | end
70 |
71 | local x1, y1, x2, y2 =
72 | math.max(rect1:xMin(), rect2:xMin()),
73 | math.max(rect1:yMin(), rect2:yMin()),
74 | math.min(rect1:xMax(), rect2:xMax()),
75 | math.min(rect1:yMax(), rect2:yMax())
76 | if x1 >= x2 or y1 >= y2 then
77 | return nil
78 | end
79 |
80 | return Rect.new(x1, y1, x2, y2)
81 | end
82 |
83 | --[[ String. ]]
84 |
85 | local function startsWith(txt, part)
86 | return part == '' or string.sub(txt, 1, #part) == part
87 | end
88 |
89 | local function endsWith(str, part)
90 | return part == '' or string.sub(str, -#part) == part
91 | end
92 |
93 | local function split(txt, sep, pattern, translate)
94 | local result = { }
95 | for line in string.gmatch(txt, '[^\n]*') do
96 | if sep == nil then
97 | local i = 1
98 | for str in string.gmatch(line, pattern or '[\33-\127\192-\255]+[\128-\191]*') do
99 | if translate then
100 | str = translate(str, i)
101 | end
102 | local codes = { }
103 | for _, v in utf8.codes(str) do
104 | table.insert(codes, v)
105 | end
106 | local j = nil
107 | for i = #codes, 1, -1 do
108 | if codes[i] <= 255 then
109 | break
110 | end
111 | j = i
112 | end
113 | if j == nil then
114 | table.insert(result, str)
115 | else
116 | local first, second = '', ''
117 | for k = 1, j - 1, 1 do
118 | first = first .. utf8.char(codes[k])
119 | end
120 | for k = j, #codes, 1 do
121 | second = second .. utf8.char(codes[k])
122 | end
123 | if first ~= '' then
124 | table.insert(result, first)
125 | end
126 | if second ~= '' then
127 | table.insert(result, second)
128 | end
129 | end
130 | i = i + 1
131 | end
132 | else
133 | local i = 1
134 | for str in string.gmatch(line, '([^' .. sep .. ']+)') do
135 | if translate then
136 | str = translate(str, i)
137 | end
138 | table.insert(result, str)
139 | i = i + 1
140 | end
141 | end
142 | table.insert(result, '\n')
143 | end
144 | table.remove(result)
145 |
146 | return result
147 | end
148 |
149 | local function escape(txt, defaultColor, tokenize, pattern, translate)
150 | local fill = function (result, txt, col)
151 | if tokenize then
152 | local chars = split(txt, nil, pattern, translate)
153 | for i, c in ipairs(chars) do
154 | local code = utf8.codepoint(c)
155 | if code <= 255 then
156 | if c ~= '\n' then
157 | c = c .. ' '
158 | end
159 | else
160 | local nextCode = i < #chars and utf8.codepoint(chars[i + 1]) or 256
161 | if nextCode <= 255 then
162 | if c ~= '\n' then
163 | c = c .. ' '
164 | end
165 | end
166 | end
167 | table.insert(
168 | result,
169 | {
170 | text = c,
171 | color = col
172 | }
173 | )
174 | end
175 | else
176 | table.insert(
177 | result,
178 | {
179 | text = txt,
180 | color = col
181 | }
182 | )
183 | end
184 | end
185 |
186 | local result = { }
187 | while #txt > 0 do
188 | local i = string.find(txt, '%[col=')
189 | if i == nil then
190 | fill(result, txt, defaultColor)
191 | txt = ''
192 | else
193 | local head = string.sub(txt, 1, i - 1)
194 | fill(result, head, defaultColor)
195 | txt = string.sub(txt, i + 5)
196 | local j = string.find(txt, '%]')
197 | if j == nil then
198 | error('Invalid escape.')
199 | end
200 | local col = string.sub(txt, 1, j - 1)
201 | local rgba = tonumber(col)
202 | rgba = ((rgba >> 24) & 0xff) | ((rgba << 8) & 0xff0000) | ((rgba >> 8) & 0xff00) | ((rgba << 24) & 0xff000000)
203 | col = Color.new()
204 | col:fromRGBA(rgba)
205 | txt = string.sub(txt, j + 1)
206 | local k = string.find(txt, '%[/col%]')
207 | if k == nil then
208 | error('Invalid escape.')
209 | end
210 | local text = string.sub(txt, 1, k - 1)
211 | fill(result, text, col)
212 | txt = string.sub(txt, k + 6)
213 | end
214 | end
215 |
216 | return result
217 | end
218 |
219 | --[[ List. ]]
220 |
221 | local function car(lst)
222 | if not lst or #lst == 0 then
223 | return nil
224 | end
225 |
226 | return lst[1]
227 | end
228 |
229 | local function cdr(lst)
230 | if not lst or #lst == 0 then
231 | return { }
232 | end
233 | lst = table.pack(table.unpack(lst))
234 | table.remove(lst, 1)
235 |
236 | return lst
237 | end
238 |
239 | local function exists(lst, elem)
240 | if not lst then
241 | return false
242 | end
243 |
244 | for _, v in pairs(lst) do
245 | if v == elem then
246 | return true
247 | end
248 | end
249 |
250 | return false
251 | end
252 |
253 | local function filter(lst, pred)
254 | if not lst then
255 | return nil
256 | end
257 |
258 | local result = { }
259 | for _, v in ipairs(lst) do
260 | if pred and pred(v) then
261 | table.insert(result, v)
262 | elseif not pred and not v then
263 | return { }
264 | end
265 | end
266 |
267 | return result
268 | end
269 |
270 | --[[ Dictionary. ]]
271 |
272 | local function merge(first, second)
273 | if first == nil and second == nil then
274 | return nil
275 | end
276 | local result = { }
277 | if first then
278 | for k, v in pairs(first) do
279 | result[k] = v
280 | end
281 | end
282 | if second then
283 | for k, v in pairs(second) do
284 | result[k] = v
285 | end
286 | end
287 |
288 | return result
289 | end
290 |
291 | --[[ Text drawing. ]]
292 |
293 | local function tex3Grid(elem, x, y, w, h, permeation, alpha, color_)
294 | local img = elem.resource
295 | local area = elem.area
296 | local srcx, srcy = 0, 0
297 | local srcw, srch = img.width, img.height
298 | if area then
299 | srcx, srcy = area[1], area[2]
300 | srcw, srch = area[3], area[4]
301 | end
302 | local col = nil
303 | if color_ or alpha then
304 | if color_ then
305 | if alpha then
306 | col = Color.new(color_.r, color_.g, color_.b, color_.a * (alpha / 255))
307 | else
308 | col = color_
309 | end
310 | else
311 | if alpha then
312 | col = Color.new(255, 255, 255, alpha)
313 | end
314 | end
315 | end
316 | if srcw == w and srch == h then
317 | if area then
318 | if col then
319 | tex(img, x, y, w, h, srcx, srcy, srcw, srch, 0, Vec2.new(0.5, 0.5), false, false, col)
320 | else
321 | tex(img, x, y, w, h, srcx, srcy, srcw, srch)
322 | end
323 | else
324 | if col then
325 | tex(img, x, y, w, h, x, y, w, h, 0, Vec2.new(0.5, 0.5), false, false, col)
326 | else
327 | tex(img, x, y, w, h)
328 | end
329 | end
330 | else
331 | if col then
332 | permeation = 0
333 | elseif not permeation then
334 | permeation = 1
335 | end
336 | x, y, w, h = math.floor(x), math.floor(y), math.floor(w), math.floor(h)
337 | local w_1_3 = math.floor(srcw * 1 / 3)
338 | local srcx1, srcx2, srcx3, srcx4 = srcx, srcx + w_1_3, srcx + srcw - w_1_3 - 1, srcx + srcw - 1
339 | local srcwBorder, srcwMiddle = srcx2 - srcx1 + 1, srcx3 - srcx2 + 1
340 | local dstx1, dstx2, dstx3, dstx4 = x, x + srcwBorder, x + w - srcwBorder, x + w - 1
341 | if col then
342 | tex(img, dstx2 - permeation, y, dstx3 - dstx2 + permeation * 2, h, srcx2, srcy, srcwMiddle, srch, 0, Vec2.new(0.5, 0.5), false, false, col) -- Middle.
343 | tex(img, dstx1, y, srcwBorder, h, srcx1, srcy, srcwBorder, srch, 0, Vec2.new(0.5, 0.5), false, false, col) -- Left.
344 | tex(img, dstx3, y, srcwBorder, h, srcx3, srcy, srcwBorder, srch, 0, Vec2.new(0.5, 0.5), false, false, col) -- Right.
345 | else
346 | tex(img, dstx2 - permeation, y, dstx3 - dstx2 + permeation * 2, h, srcx2, srcy, srcwMiddle, srch) -- Middle.
347 | tex(img, dstx1, y, srcwBorder, h, srcx1, srcy, srcwBorder, srch) -- Left.
348 | tex(img, dstx3, y, srcwBorder, h, srcx3, srcy, srcwBorder, srch) -- Right.
349 | end
350 | end
351 | end
352 |
353 | local function tex9Grid(elem, x, y, w, h, permeation, alpha, color_)
354 | local img = elem.resource
355 | local area = elem.area
356 | local srcx, srcy = 0, 0
357 | local srcw, srch = img.width, img.height
358 | if area then
359 | srcx, srcy = area[1], area[2]
360 | srcw, srch = area[3], area[4]
361 | end
362 | local col = nil
363 | if color_ or alpha then
364 | if color_ then
365 | if alpha then
366 | col = Color.new(color_.r, color_.g, color_.b, color_.a * (alpha / 255))
367 | else
368 | col = color_
369 | end
370 | else
371 | if alpha then
372 | col = Color.new(255, 255, 255, alpha)
373 | end
374 | end
375 | end
376 | if srcw == w and srch == h then
377 | if area then
378 | if col then
379 | tex(img, x, y, w, h, srcx, srcy, srcw, srch, 0, Vec2.new(0.5, 0.5), false, false, col)
380 | else
381 | tex(img, x, y, w, h, srcx, srcy, srcw, srch)
382 | end
383 | else
384 | if col then
385 | tex(img, x, y, w, h, x, y, w, h, 0, Vec2.new(0.5, 0.5), false, false, col)
386 | else
387 | tex(img, x, y, w, h)
388 | end
389 | end
390 | else
391 | if col then
392 | permeation = 0
393 | elseif not permeation then
394 | permeation = 1
395 | end
396 | x, y, w, h = math.floor(x), math.floor(y), math.floor(w), math.floor(h)
397 | local w_1_3, h_1_3 = math.floor(srcw * 1 / 3), math.floor(srch * 1 / 3)
398 | local srcx1, srcx2, srcx3, srcx4 = srcx, srcx + w_1_3, srcx + srcw - w_1_3 - 1, srcx + srcw - 1
399 | local srcy1, srcy2, srcy3, srcy4 = srcy, srcy + h_1_3, srcy + srch - h_1_3 - 1, srcy + srch - 1
400 | local srcwBorder, srcwMiddle = srcx2 - srcx1 + 1, srcx3 - srcx2 + 1
401 | local srchBorder, srchMiddle = srcy2 - srcy1 + 1, srcy3 - srcy2 + 1
402 | local dstx1, dstx2, dstx3, dstx4 = x, x + srcwBorder, x + w - srcwBorder, x + w - 1
403 | local dsty1, dsty2, dsty3, dsty4 = y, y + srchBorder, y + h - srchBorder, y + h - 1
404 | if col then
405 | tex(img, dstx2 - permeation, dsty2 - permeation, dstx3 - dstx2 + permeation * 2, dsty3 - dsty2 + permeation * 2, srcx2, srcy2, srcwMiddle, srchMiddle, 0, Vec2.new(0.5, 0.5), false, false, col) -- Center.
406 | tex(img, dstx2 - permeation, dsty1, dstx3 - dstx2 + permeation * 2, srchBorder, srcx2, srcy1, srcwMiddle, srchBorder, 0, Vec2.new(0.5, 0.5), false, false, col) -- Middle top.
407 | tex(img, dstx1, dsty2 - permeation, srcwBorder, dsty3 - dsty2 + permeation * 2, srcx1, srcy2, srcwBorder, srchMiddle, 0, Vec2.new(0.5, 0.5), false, false, col) -- Left middle.
408 | tex(img, dstx3, dsty2 - permeation, srcwBorder, dsty3 - dsty2 + permeation * 2, srcx3, srcy2, srcwBorder, srchMiddle, 0, Vec2.new(0.5, 0.5), false, false, col) -- Right middle.
409 | tex(img, dstx2 - permeation, dsty3, dstx3 - dstx2 + permeation * 2, srchBorder, srcx2, srcy3, srcwMiddle, srchBorder, 0, Vec2.new(0.5, 0.5), false, false, col) -- Middle bottom.
410 | tex(img, dstx1, dsty1, srcwBorder, srchBorder, srcx1, srcy1, srcwBorder, srchBorder, 0, Vec2.new(0.5, 0.5), false, false, col) -- Left top.
411 | tex(img, dstx3, dsty1, srcwBorder, srchBorder, srcx3, srcy1, srcwBorder, srchBorder, 0, Vec2.new(0.5, 0.5), false, false, col) -- Right top.
412 | tex(img, dstx1, dsty3, srcwBorder, srchBorder, srcx1, srcy3, srcwBorder, srchBorder, 0, Vec2.new(0.5, 0.5), false, false, col) -- Left bottom.
413 | tex(img, dstx3, dsty3, srcwBorder, srchBorder, srcx3, srcy3, srcwBorder, srchBorder, 0, Vec2.new(0.5, 0.5), false, false, col) -- Right bottom.
414 | else
415 | tex(img, dstx2 - permeation, dsty2 - permeation, dstx3 - dstx2 + permeation * 2, dsty3 - dsty2 + permeation * 2, srcx2, srcy2, srcwMiddle, srchMiddle) -- Center.
416 | tex(img, dstx2 - permeation, dsty1, dstx3 - dstx2 + permeation * 2, srchBorder, srcx2, srcy1, srcwMiddle, srchBorder) -- Middle top.
417 | tex(img, dstx1, dsty2 - permeation, srcwBorder, dsty3 - dsty2 + permeation * 2, srcx1, srcy2, srcwBorder, srchMiddle) -- Left middle.
418 | tex(img, dstx3, dsty2 - permeation, srcwBorder, dsty3 - dsty2 + permeation * 2, srcx3, srcy2, srcwBorder, srchMiddle) -- Right middle.
419 | tex(img, dstx2 - permeation, dsty3, dstx3 - dstx2 + permeation * 2, srchBorder, srcx2, srcy3, srcwMiddle, srchBorder) -- Middle bottom.
420 | tex(img, dstx1, dsty1, srcwBorder, srchBorder, srcx1, srcy1, srcwBorder, srchBorder) -- Left top.
421 | tex(img, dstx3, dsty1, srcwBorder, srchBorder, srcx3, srcy1, srcwBorder, srchBorder) -- Right top.
422 | tex(img, dstx1, dsty3, srcwBorder, srchBorder, srcx1, srcy3, srcwBorder, srchBorder) -- Left bottom.
423 | tex(img, dstx3, dsty3, srcwBorder, srchBorder, srcx3, srcy3, srcwBorder, srchBorder) -- Right bottom.
424 | end
425 | end
426 | end
427 |
428 | local function textLeftSameLine(txt, font_, x, y, w, h, offset_, alpha)
429 | local dx = offset_ and offset_[1] or 0
430 | local dy = offset_ and offset_[2] or 0
431 | local margin, scale = font_.margin or 1, font_.scale or 1
432 | local textWidth, textHeight = measure(txt, font_.resource, margin, scale)
433 | local fx, fy = x + dx, y + dy
434 | local col = alpha and Color.new(font_.color.r, font_.color.g, font_.color.b, alpha) or font_.color
435 | text(txt, fx, fy, col, margin, scale)
436 |
437 | return fx, fy, textWidth, textHeight
438 | end
439 |
440 | local function textLeft(txt, font_, x, y, w, h, offset_, alpha)
441 | local dx = offset_ and offset_[1] or 0
442 | local dy = offset_ and offset_[2] or 0
443 | local margin, scale = font_.margin or 1, font_.scale or 1
444 | local textWidth, textHeight = measure(txt, font_.resource, margin, scale)
445 | local fx, fy = x + dx, y + (h - textHeight) * 0.5 + dy
446 | local col = alpha and Color.new(font_.color.r, font_.color.g, font_.color.b, alpha) or font_.color
447 | text(txt, fx, fy, col, margin, scale)
448 |
449 | return fx, fy, textWidth, textHeight
450 | end
451 |
452 | local function textCenter(txt, font_, x, y, w, h, offset_, alpha)
453 | local dx = offset_ and offset_[1] or 0
454 | local dy = offset_ and offset_[2] or 0
455 | local margin, scale = font_.margin or 1, font_.scale or 1
456 | local textWidth, textHeight = measure(txt, font_.resource, margin, scale)
457 | local fx, fy = x + (w - textWidth) * 0.5 + dx, y + (h - textHeight) * 0.5 + dy
458 | local col = alpha and Color.new(font_.color.r, font_.color.g, font_.color.b, alpha) or font_.color
459 | text(txt, fx, fy, col, margin, scale)
460 |
461 | return fx, fy, textWidth, textHeight
462 | end
463 |
464 | local function textRight(txt, font_, x, y, w, h, offset_, alpha)
465 | local dx = offset_ and offset_[1] or 0
466 | local dy = offset_ and offset_[2] or 0
467 | local margin, scale = font_.margin or 1, font_.scale or 1
468 | local textWidth, textHeight = measure(txt, font_.resource, margin, scale)
469 | local fx, fy = x + (w - textWidth) + dx, y + (h - textHeight) * 0.5 + dy
470 | local col = alpha and Color.new(font_.color.r, font_.color.g, font_.color.b, alpha) or font_.color
471 | text(txt, fx, fy, col, margin, scale)
472 |
473 | return fx, fy, textWidth, textHeight
474 | end
475 |
476 | --[[
477 | Exporting.
478 | ]]
479 |
480 | return {
481 | KeyCodeLShift = KeyCodeLShift,
482 | KeyCodeRShift = KeyCodeRShift,
483 | NaN = NaN,
484 | isNaN = isNaN,
485 | round = round,
486 | clamp = clamp,
487 | intersected = intersected,
488 | startsWith = startsWith,
489 | endsWith = endsWith,
490 | split = split,
491 | escape = escape,
492 | car = car,
493 | cdr = cdr,
494 | exists = exists,
495 | filter = filter,
496 | merge = merge,
497 | tex3Grid = tex3Grid,
498 | tex9Grid = tex9Grid,
499 | textLeftSameLine = textLeftSameLine,
500 | textLeft = textLeft,
501 | textCenter = textCenter,
502 | textRight = textRight
503 | }
504 |
--------------------------------------------------------------------------------
/src/main.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | The MIT License
3 |
4 | Copyright (C) 2021 - 2022 Tony Wang
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal in
8 | the Software without restriction, including without limitation the rights to
9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10 | the Software, and to permit persons to whom the Software is furnished to do so,
11 | subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | ]]
23 |
24 | require 'libs/beGUI/beGUI'
25 | require 'libs/beGUI/beTheme'
26 | require 'keycode'
27 |
28 | local DEBUG = false
29 |
30 | local widgets = nil
31 | local theme = nil
32 |
33 | function setup()
34 | print('beGUI v' .. beGUI.version)
35 |
36 | Canvas.main:resize(0, 320)
37 |
38 | local P = beGUI.percent -- Alias of percent.
39 | local left = beGUI.Widget.new()
40 | :put(P(27), 15) -- X: 27%, Y: 15.
41 | :resize(P(45), 290) -- W: 45%, H: 290.
42 | :addChild(
43 | beGUI.Button.new('Button')
44 | :setId('button')
45 | :anchor(0, 0) -- X: left, Y: top.
46 | :put(P(-50), 0) -- X: -50%, Y: 0.
47 | :resize(P(48), 23) -- W: 48%, H: 23.
48 | :on('clicked', function (sender)
49 | local lbl = widgets:get(1, 'label')
50 | lbl:setValue('Clicked ' .. tostring(sender))
51 | end)
52 | )
53 | :addChild(
54 | beGUI.Button.new('Popup')
55 | :setId('button')
56 | :anchor(1, 0) -- X: right, Y: top.
57 | :put(P(50), 0) -- X: 50%, Y: 0.
58 | :resize(P(48), 23) -- W: 48%, H: 23.
59 | :on('clicked', function (sender)
60 | widgets:openPopup( -- Open popup.
61 | beGUI.QuestionBox.new(true, 'Message', 'Hi there!')
62 | :on('canceled', function (sender)
63 | widgets:closePopup()
64 |
65 | local lbl = widgets:get(1, 'label')
66 | lbl:setValue('Popup canceled')
67 | end)
68 | :on('confirmed', function (sender)
69 | widgets:closePopup()
70 |
71 | local lbl = widgets:get(1, 'label')
72 | lbl:setValue('Popup confirmed')
73 | end)
74 | :on('denied', function (sender)
75 | widgets:closePopup()
76 |
77 | local lbl = widgets:get(1, 'label')
78 | lbl:setValue('Popup denied')
79 | end)
80 | )
81 |
82 | local lbl = widgets:get(1, 'label')
83 | lbl:setValue('Popup opened')
84 | end)
85 | )
86 | :addChild(
87 | beGUI.Label.new('beGUI demo', 'left', true)
88 | :setId('label')
89 | :anchor(0.5, 0)
90 | :put(0, 20)
91 | :resize(P(100), 23)
92 | )
93 | :addChild(
94 | beGUI.CheckBox.new('CheckBox')
95 | :setId('checkbox')
96 | :anchor(0.5, 0)
97 | :put(0, 40)
98 | :resize(P(100), 23)
99 | :on('changed', function (sender, value)
100 | local lbl = widgets:get(1, 'label')
101 | lbl:setValue('Changed ' .. tostring(sender) .. ', ' .. tostring(value))
102 | end)
103 | )
104 | :addChild(
105 | beGUI.RadioBox.new('RadioBox 1', true)
106 | :setId('radiobox1')
107 | :anchor(0.5, 0)
108 | :put(0, 60)
109 | :resize(P(100), 23)
110 | :on('changed', function (sender, value)
111 | if value then
112 | local lbl = widgets:get(1, 'label')
113 | lbl:setValue('Changed ' .. tostring(sender) .. '1, ' .. tostring(value))
114 |
115 | local multilinelabel = widgets:find('multilinelabel')
116 | multilinelabel:setAlignment('left')
117 | end
118 | end)
119 | )
120 | :addChild(
121 | beGUI.RadioBox.new('RadioBox 2')
122 | :setId('radiobox2')
123 | :anchor(0.5, 0)
124 | :put(0, 80)
125 | :resize(P(100), 23)
126 | :on('changed', function (sender, value)
127 | if value then
128 | local lbl = widgets:get(1, 'label')
129 | lbl:setValue('Changed ' .. tostring(sender) .. '2, ' .. tostring(value))
130 |
131 | local multilinelabel = widgets:find('multilinelabel')
132 | multilinelabel:setAlignment('center')
133 | end
134 | end)
135 | )
136 | :addChild(
137 | beGUI.RadioBox.new('RadioBox 3')
138 | :setId('radiobox3')
139 | :anchor(0.5, 0)
140 | :put(0, 100)
141 | :resize(P(100), 23)
142 | :on('changed', function (sender, value)
143 | if value then
144 | local lbl = widgets:get(1, 'label')
145 | lbl:setValue('Changed ' .. tostring(sender) .. '3, ' .. tostring(value))
146 |
147 | local multilinelabel = widgets:find('multilinelabel')
148 | multilinelabel:setAlignment('right')
149 | end
150 | end)
151 | )
152 | :addChild(
153 | beGUI.ComboBox.new({ 'Item 1', 'Item 2', 'Item 3', 'More Items...' })
154 | :setId('combobox')
155 | :anchor(0.5, 0)
156 | :put(0, 120)
157 | :resize(P(100), 23)
158 | :on('changed', function (sender, value)
159 | local lbl = widgets:get(1, 'label')
160 | lbl:setValue('Changed ' .. tostring(sender) .. ', ' .. tostring(value))
161 | end)
162 | )
163 | :addChild(
164 | beGUI.List.new(true)
165 | :setId('list')
166 | :anchor(0.5, 0)
167 | :put(0, 150)
168 | :resize(P(100), 69)
169 | :addChild( -- List items are added as children.
170 | beGUI.Button.new('Increase')
171 | :setId('button')
172 | :put(0, 0)
173 | :resize(P(100), 23)
174 | :on('clicked', function (_)
175 | local lbl = widgets:get(1, 'list', 'label1') -- Get the following one label.
176 | local num = tonumber(lbl:getValue())
177 | lbl:setValue(tostring(num + 1))
178 | end)
179 | )
180 | :addChild(
181 | beGUI.Label.new('1')
182 | :setId('label1')
183 | :put(P(1), 20)
184 | :resize(P(98), 23)
185 | )
186 | :addChild(
187 | beGUI.Label.new('Hold and slide...')
188 | :setId('label2')
189 | :put(P(1), 40)
190 | :resize(P(98), 23)
191 | )
192 | :addChild(
193 | beGUI.Label.new('Label 3')
194 | :setId('label3')
195 | :put(P(1), 60)
196 | :resize(P(98), 23)
197 | )
198 | :addChild(
199 | beGUI.Label.new('Item in a List')
200 | :setId('label4')
201 | :put(P(1), 80)
202 | :resize(P(98), 23)
203 | )
204 | )
205 | :addChild(
206 | beGUI.Group.new('Group')
207 | :setId('group')
208 | :anchor(0, 0)
209 | :put(P(-50), 226)
210 | :resize(P(100), 60)
211 | :addChild(
212 | beGUI.Picture.new({ resource = Resources.load('disk.png') })
213 | :setId('picture')
214 | :anchor(0, 0)
215 | :put(P(1), 10)
216 | :resize(48, 48)
217 | )
218 | :addChild(
219 | beGUI.NumberBox.new(10, 1, 0, 100)
220 | :setId('numberbox1')
221 | :anchor(1, 0)
222 | :put(P(99), 10)
223 | :resize(P(50), 23)
224 | :on('changed', function (sender, value)
225 | local lbl = widgets:get(1, 'label')
226 | lbl:setValue('Changed ' .. tostring(sender) .. ', ' .. tostring(value))
227 | end)
228 | )
229 | :addChild(
230 | beGUI.NumberBox.new(5, 0.1, 0, 10, function (val)
231 | return math.floor(val * 10 + 0.5) / 10
232 | end)
233 | :setId('numberbox2')
234 | :anchor(1, 1)
235 | :put(P(99), 55)
236 | :resize(P(50), 23)
237 | :on('changed', function (sender, value)
238 | local lbl = widgets:get(1, 'label')
239 | lbl:setValue('Changed ' .. tostring(sender) .. ', ' .. tostring(value))
240 | end)
241 | )
242 | )
243 | local right = beGUI.Widget.new()
244 | :put(P(73), 15) -- X: 73%, Y: 15.
245 | :resize(P(45), 290) -- W: 45%, H: 290.
246 | :addChild(
247 | beGUI.InputBox.new('', 'Input something...')
248 | :setId('inputbox')
249 | :anchor(0.5, 0) -- X: middle, Y: top.
250 | :put(0, 0) -- X: 0, Y: 0.
251 | :resize(P(100), 23) -- W: 100%, H: 23.
252 | :on('changed', function (sender, value)
253 | local lbl = widgets:get(2, 'label')
254 | lbl:setValue('Changed ' .. tostring(sender) .. ', ' .. tostring(value))
255 | end)
256 | )
257 | :addChild(
258 | beGUI.Label.new('Click above')
259 | :setId('label')
260 | :anchor(0.5, 0)
261 | :put(0, 20)
262 | :resize(P(100), 23)
263 | )
264 | :addChild(
265 | beGUI.Droppable.new()
266 | :setId('droppable1')
267 | :anchor(0, 0)
268 | :put(P(-50), 40)
269 | :resize(P(40), 17)
270 | :addChild(
271 | beGUI.Custom.new()
272 | :setId('custom')
273 | :anchor(0, 0)
274 | :put(0, 0)
275 | :resize(P(100), P(100))
276 | :on('updated', function (sender, x, y, w, h, delta, event)
277 | rect(x, y, x + w - 1, y + h - 1, false, sender.color or Color.new(0, 0, 0))
278 | end)
279 | )
280 | :on('entered', function (sender, draggable)
281 | local custom = widgets:get(2, 'droppable1', 'custom')
282 | custom.color = Color.new(0, 255, 0)
283 |
284 | local lbl = widgets:get(2, 'label')
285 | lbl:setValue('Entered ' .. sender.id .. ', ' .. draggable.id)
286 | end)
287 | :on('left', function (sender, draggable)
288 | local custom = widgets:get(2, 'droppable1', 'custom')
289 | custom.color = nil
290 |
291 | local lbl = widgets:get(2, 'label')
292 | lbl:setValue('Left ' .. sender.id .. ', ' .. draggable.id)
293 | end)
294 | :on('dropping', function (sender, draggable)
295 | return true
296 | end)
297 | :on('dropped', function (sender, draggable)
298 | local custom = widgets:get(2, 'droppable1', 'custom')
299 | custom.color = nil
300 |
301 | local lbl = widgets:get(2, 'label')
302 | lbl:setValue('Dropped ' .. sender.id .. ', ' .. draggable.id)
303 | end)
304 | :addChild(
305 | beGUI.Draggable.new()
306 | :setId('draggable1')
307 | :anchor(0, 0)
308 | :put(0, 0)
309 | :resize(P(100), P(100))
310 | :addChild(
311 | beGUI.Button.new('Drag me')
312 | :setId('button_draggable')
313 | :anchor(0, 0)
314 | :put(P(1), P(6))
315 | :resize(P(99), P(94))
316 | :on('clicked', function (sender)
317 | local lbl = widgets:get(2, 'label')
318 | lbl:setValue('Clicked ' .. sender.id)
319 | end)
320 | )
321 | )
322 | )
323 | :addChild(
324 | beGUI.Droppable.new()
325 | :setId('droppable2')
326 | :anchor(1, 0)
327 | :put(P(50), 40)
328 | :resize(P(40), 17)
329 | :addChild(
330 | beGUI.Custom.new()
331 | :setId('custom')
332 | :anchor(0, 0)
333 | :put(0, 0)
334 | :resize(P(100), P(100))
335 | :on('updated', function (sender, x, y, w, h, delta, event)
336 | rect(x, y, x + w - 1, y + h - 1, false, sender.color or Color.new(0, 0, 0))
337 | end)
338 | )
339 | :on('entered', function (sender, draggable)
340 | local custom = widgets:get(2, 'droppable2', 'custom')
341 | custom.color = Color.new(0, 255, 0)
342 |
343 | local lbl = widgets:get(2, 'label')
344 | lbl:setValue('Entered ' .. sender.id .. ', ' .. draggable.id)
345 | end)
346 | :on('left', function (sender, draggable)
347 | local custom = widgets:get(2, 'droppable2', 'custom')
348 | custom.color = nil
349 |
350 | local lbl = widgets:get(2, 'label')
351 | lbl:setValue('Left ' .. sender.id .. ', ' .. draggable.id)
352 | end)
353 | :on('dropping', function (sender, draggable)
354 | return true
355 | end)
356 | :on('dropped', function (sender, draggable)
357 | local custom = widgets:get(2, 'droppable2', 'custom')
358 | custom.color = nil
359 |
360 | local lbl = widgets:get(2, 'label')
361 | lbl:setValue('Dropped ' .. sender.id .. ', ' .. draggable.id)
362 | end)
363 | )
364 | :addChild(
365 | beGUI.Droppable.new()
366 | :setId('droppable3')
367 | :anchor(0, 0)
368 | :put(P(-50), 63)
369 | :resize(P(40), 17)
370 | :addChild(
371 | beGUI.Custom.new()
372 | :setId('custom')
373 | :anchor(0, 0)
374 | :put(0, 0)
375 | :resize(P(100), P(100))
376 | :on('updated', function (sender, x, y, w, h, delta, event)
377 | rect(x, y, x + w - 1, y + h - 1, false, sender.color or Color.new(0, 0, 0))
378 | end)
379 | )
380 | :on('entered', function (sender, draggable)
381 | local custom = widgets:get(2, 'droppable3', 'custom')
382 | custom.color = Color.new(0, 255, 0)
383 |
384 | local lbl = widgets:get(2, 'label')
385 | lbl:setValue('Entered ' .. sender.id .. ', ' .. draggable.id)
386 | end)
387 | :on('left', function (sender, draggable)
388 | local custom = widgets:get(2, 'droppable3', 'custom')
389 | custom.color = nil
390 |
391 | local lbl = widgets:get(2, 'label')
392 | lbl:setValue('Left ' .. sender.id .. ', ' .. draggable.id)
393 | end)
394 | :on('dropping', function (sender, draggable)
395 | return true
396 | end)
397 | :on('dropped', function (sender, draggable)
398 | local custom = widgets:get(2, 'droppable3', 'custom')
399 | custom.color = nil
400 |
401 | local lbl = widgets:get(2, 'label')
402 | lbl:setValue('Dropped ' .. sender.id .. ', ' .. draggable.id)
403 | end)
404 | :addChild(
405 | beGUI.Draggable.new()
406 | :setId('draggable2')
407 | :anchor(0, 0)
408 | :put(0, 0)
409 | :resize(P(100), P(100))
410 | :addChild(
411 | beGUI.Label.new('Drag me 2')
412 | :setId('label')
413 | :anchor(0, 0)
414 | :put(2, 0)
415 | :resize(P(100), P(100))
416 | )
417 | )
418 | )
419 | :addChild(
420 | beGUI.Droppable.new()
421 | :setId('droppable4')
422 | :anchor(1, 0)
423 | :put(P(50), 63)
424 | :resize(P(40), 17)
425 | :addChild(
426 | beGUI.Custom.new()
427 | :setId('custom')
428 | :anchor(0, 0)
429 | :put(0, 0)
430 | :resize(P(100), P(100))
431 | :on('updated', function (sender, x, y, w, h, delta, event)
432 | rect(x, y, x + w - 1, y + h - 1, false, sender.color or Color.new(0, 0, 0))
433 | end)
434 | )
435 | :on('entered', function (sender, draggable)
436 | local custom = widgets:get(2, 'droppable4', 'custom')
437 | if draggable.id == 'draggable2' then
438 | custom.color = Color.new(0, 255, 0)
439 | else
440 | custom.color = Color.new(255, 0, 0)
441 | end
442 |
443 | local lbl = widgets:get(2, 'label')
444 | lbl:setValue('Entered ' .. sender.id .. ', ' .. draggable.id)
445 | end)
446 | :on('left', function (sender, draggable)
447 | local custom = widgets:get(2, 'droppable4', 'custom')
448 | custom.color = nil
449 |
450 | local lbl = widgets:get(2, 'label')
451 | lbl:setValue('Left ' .. sender.id .. ', ' .. draggable.id)
452 | end)
453 | :on('dropping', function (sender, draggable)
454 | return draggable.id == 'draggable2'
455 | end)
456 | :on('dropped', function (sender, draggable)
457 | local custom = widgets:get(2, 'droppable4', 'custom')
458 | custom.color = nil
459 |
460 | local lbl = widgets:get(2, 'label')
461 | lbl:setValue('Dropped ' .. sender.id .. ', ' .. draggable.id)
462 | end)
463 | )
464 | :addChild(
465 | beGUI.List.new(true)
466 | :setId('list')
467 | :anchor(0.5, 0)
468 | :put(0, 90)
469 | :resize(P(100), 60)
470 | :addChild(
471 | beGUI.MultilineLabel.new('The Quick Brown Fox Jumps Over the Lazy Dog.\n[col=0xff0000ff]ABCDEFG[/col] [col=0x00ff00ff]HIJKLMN[/col] [col=0x0000ffff]OPQRST[/col] UVWXYZ abcdefg hijklmn opqrst uvwxyz 1234567890 ! @ # $ % ^ & * ( ) ` - = [ ] \\ ; \' , . / ~ _ + { } | : " < > ?\n')
472 | :setId('multilinelabel')
473 | :put(P(1), 2)
474 | :resize(P(98), 0) -- Height is automatically calculated.
475 | )
476 | )
477 | :addChild(
478 | beGUI.ProgressBar.new(0, Color.new(0, 0, 128))
479 | :setId('progressbar')
480 | :anchor(0.5, 0)
481 | :put(0, 160)
482 | :resize(P(100), 17)
483 | :addChild(
484 | beGUI.Label.new('3/5', 'center', false, 'font_white')
485 | :setId('label_numbers')
486 | :anchor(0.5, 1)
487 | :put(P(50), P(100))
488 | :resize(P(100), P(100))
489 | )
490 | :on('changed', function (sender, value, maxValue, shadowValue)
491 | local lblNums = sender:find('label_numbers')
492 | lblNums:setValue(tostring(value) .. '/' .. tostring(maxValue))
493 | end)
494 | :setMaxValue(5)
495 | :setValue(3)
496 | )
497 | :addChild(
498 | beGUI.Slide.new(3, 0, 5)
499 | :setId('slide')
500 | :anchor(0.5, 0)
501 | :put(0, 184)
502 | :resize(P(100), 17)
503 | :on('changed', function (sender, value)
504 | local prog = widgets:get(2, 'progressbar')
505 | prog:setValue(value)
506 | end)
507 | )
508 | :addChild(
509 | beGUI.Url.new('Bitty Engine | beGUI')
510 | :setId('url')
511 | :anchor(0.5, 0)
512 | :put(0, 210)
513 | :resize(-1, 12)
514 | :on('clicked', function (sender)
515 | Platform.surf('https://github.com/paladin-t/begui')
516 | end)
517 | )
518 | :addChild(
519 | beGUI.Tab.new()
520 | :setId('tab')
521 | :anchor(0.5, 0)
522 | :put(0, 230)
523 | :resize(P(100), 57)
524 | :add('Tab 1')
525 | :add('Tab 2')
526 | :add('Tab 3')
527 | :setValue(1)
528 | :addChild(
529 | beGUI.Button.new('Button')
530 | :setId('button')
531 | :anchor(1, 0)
532 | :put(P(98), 23)
533 | :resize(60, 23)
534 | :on('clicked', function (sender)
535 | local lbl = sender.parent:get('label')
536 | lbl:setValue('Clicked ' .. tostring(sender))
537 | end)
538 | )
539 | :addChild(
540 | beGUI.Label.new('Hello world!', 'left', true)
541 | :setId('label')
542 | :anchor(0, 0)
543 | :put(4, 23)
544 | :resize(P(100), 23)
545 | )
546 | :setValue(2)
547 | :addChild(
548 | beGUI.Label.new('Greetings from tab 2.', 'left', true)
549 | :setId('label')
550 | :anchor(0, 0)
551 | :put(4, 23)
552 | :resize(P(100), 23)
553 | )
554 | :setValue(3)
555 | :addChild(
556 | beGUI.Label.new('Greetings from tab 3.', 'left', true)
557 | :setId('label')
558 | :anchor(0, 0)
559 | :put(4, 23)
560 | :resize(P(100), 23)
561 | )
562 | :setValue(1)
563 | :on('changed', function (sender, value)
564 | local lbl = widgets:get(2, 'label')
565 | lbl:setValue('Changed ' .. tostring(sender) .. ', ' .. tostring(value))
566 | end)
567 | )
568 | widgets = beGUI.Widget.new()
569 | :put(0, 0)
570 | :resize(P(100), P(100))
571 | :addChild(
572 | left
573 | )
574 | :addChild(
575 | right
576 | )
577 | theme = beTheme.default()
578 | end
579 |
580 | function update(delta)
581 | cls(Color.new(255, 255, 255))
582 |
583 | if keyp(KeyCode.Up) then
584 | widgets:navigate('prev')
585 | elseif keyp(KeyCode.Down) then
586 | widgets:navigate('next')
587 | elseif keyp(KeyCode.Left) then
588 | widgets:navigate('dec')
589 | elseif keyp(KeyCode.Right) then
590 | widgets:navigate('inc')
591 | elseif keyp(KeyCode.Return) then
592 | widgets:navigate('press')
593 | elseif keyp(KeyCode.Esc) then
594 | widgets:navigate('cancel')
595 | end
596 |
597 | font(theme['font'].resource)
598 | widgets:update(theme, delta)
599 | font(nil)
600 |
601 | if DEBUG then
602 | local x, y, lmb = mouse()
603 | circ(x, y, 3, lmb, Color.new(255, 0, 0))
604 | end
605 | end
606 |
--------------------------------------------------------------------------------
/src/libs/beGUI/beGUI_Widget.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | The MIT License
3 |
4 | Copyright (C) 2021 - 2022 Tony Wang
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal in
8 | the Software without restriction, including without limitation the rights to
9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10 | the Software, and to permit persons to whom the Software is furnished to do so,
11 | subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | ]]
23 |
24 | local beClass = require 'libs/beGUI/beClass'
25 | local beUtils = require 'libs/beGUI/beGUI_Utils'
26 | local beStack = require 'libs/beGUI/beStack'
27 | local beStructures = require 'libs/beGUI/beGUI_Structures'
28 |
29 | --[[
30 | Widgets.
31 | ]]
32 |
33 | local Widget = nil
34 | Widget = beClass.class({
35 | DEBUG = false,
36 |
37 | id = nil, -- String.
38 | visibility = true, -- Boolean.
39 | capturability = false,
40 | queriablility = false,
41 | anchorX = 0, anchorY = 0, -- Number, typically [0.0, 1.0].
42 | x = 0, y = 0, -- Number or Percent.
43 | worldX = 0, worldY = 0, -- Auto calculated.
44 | width = 0, height = 0, -- Number or Percent.
45 | parentWidth = nil, parentHeight = nil, -- Auto calculated.
46 | content = nil,
47 | transparency = nil,
48 | parent = nil,
49 | children = nil,
50 |
51 | popup = nil,
52 | hovering = false,
53 | focused = nil, -- Used to reserve focused Widget before popup.
54 | focusIfHovering = false,
55 | focusTicks = 0,
56 | clippingStack = nil,
57 | context = nil,
58 | tweens = nil,
59 | events = nil,
60 |
61 | ctor = function (self)
62 | -- Do nothing.
63 | end,
64 |
65 | __tostring = function (self)
66 | return 'Widget'
67 | end,
68 |
69 | -- Sets the ID of the Widget; an ID is used to identify a Widget from others for accessing.
70 | -- `id`: ID string
71 | setId = function (self, id)
72 | if id ~= nil and type(id) ~= 'string' then
73 | error('String or nil expected.')
74 | end
75 |
76 | self.id = id
77 |
78 | return self
79 | end,
80 | -- Gets a sub Widget with the specific ID sequence.
81 | -- `...`: ID sequence of the full hierarchy path
82 | get = function (self, ...)
83 | local full = { ... }
84 | if #full == 0 then
85 | return self
86 | end
87 | local car_ = beUtils.car(full)
88 | local cdr_ = beUtils.cdr(full)
89 | local c = self:getChild(car_)
90 | if not c then
91 | return nil
92 | end
93 |
94 | return c:get(table.unpack(cdr_))
95 | end,
96 | -- Finds the first matched Widget with the specific ID.
97 | -- `id`: ID string at any level of the hierarchy path
98 | find = function (self, id)
99 | local find_ = nil
100 | find_ = function (widget, id)
101 | if widget.id == id then
102 | return widget
103 | end
104 | if widget.children then
105 | for _, c in ipairs(widget.children) do
106 | local ret = find_(c, id)
107 | if ret then
108 | return ret
109 | end
110 | end
111 | end
112 |
113 | return nil
114 | end
115 |
116 | return find_(self, id)
117 | end,
118 |
119 | -- Gets the visibility of the Widget.
120 | visible = function (self)
121 | return self.visibility
122 | end,
123 | -- Sets the visibility of the Widget.
124 | -- `val`: boolean
125 | setVisible = function (self, val)
126 | self.visibility = val
127 |
128 | return self
129 | end,
130 |
131 | -- Gets the capturability of the Widget.
132 | capturable = function (self)
133 | return self.capturability
134 | end,
135 | -- Sets the capturability of the Widget.
136 | -- `val`: `true`, `false` or 'children'
137 | setCapturable = function (self, val)
138 | self.capturability = val
139 |
140 | return self
141 | end,
142 |
143 | -- Sets the anchor of the Widget; anchor is used to calculate the offset when placing Widget.
144 | -- `x`: x position of the anchor in local space as number, typically [0.0, 1.0] for [left, right]
145 | -- `y`: y position of the anchor in local space as number, typically [0.0, 1.0] for [top, bottom]
146 | anchor = function (self, x, y)
147 | self.anchorX = x
148 | self.anchorY = y
149 |
150 | return self
151 | end,
152 | -- Gets the offset of the Widget.
153 | -- returns offset x, y in world space
154 | offset = function (self)
155 | local w, h = self:size()
156 |
157 | return -w * self.anchorX, -h * self.anchorY
158 | end,
159 | -- Sets the position of the Widget.
160 | -- `x`: number for absolute position, or Percent for relative position
161 | -- `y`: number for absolute position, or Percent for relative position
162 | put = function (self, x, y)
163 | self.x = x
164 | self.y = y
165 |
166 | return self
167 | end,
168 | -- Gets the position of the Widget.
169 | -- returns position x, y in local space
170 | position = function (self)
171 | if self.parentWidth == nil --[[ or self.parentHeight == nil ]] then
172 | self:_updateHierarchy()
173 | end
174 | local canvasWidth, canvasHeight = Canvas.main:size()
175 | local x, y = self.x, self.y
176 | if beClass.is(x, beStructures.Percent) then
177 | if self.parent then
178 | x = x * self.parentWidth
179 | else
180 | x = x * canvasWidth
181 | end
182 | end
183 | if beClass.is(y, beStructures.Percent) then
184 | if self.parent then
185 | y = y * self.parentHeight
186 | else
187 | y = y * canvasHeight
188 | end
189 | end
190 |
191 | return x, y
192 | end,
193 | -- Gets the position of the Widget in world space.
194 | -- returns position x, y in world space
195 | worldPosition = function (self)
196 | return self.worldX, self.worldY
197 | end,
198 | -- Sets the size of the Widget.
199 | -- `width`: number for absolute size, or Percent for relative size
200 | -- `height`: number for absolute size, or Percent for relative size
201 | resize = function (self, width, height)
202 | self.width = width
203 | self.height = height
204 |
205 | return self
206 | end,
207 | -- Gets the size of the Widget.
208 | size = function (self)
209 | if self.parentWidth == nil --[[ or self.parentHeight == nil ]] then
210 | self:_updateHierarchy()
211 | end
212 | local w, h = self.width, self.height
213 | if beClass.is(w, beStructures.Percent) then
214 | w = w * self.parentWidth
215 | end
216 | if beClass.is(h, beStructures.Percent) then
217 | h = h * self.parentHeight
218 | end
219 |
220 | return w, h
221 | end,
222 |
223 | -- Gets the alpha value of the Widget.
224 | alpha = function (self)
225 | if not self.transparency then
226 | return 255
227 | end
228 |
229 | return self.transparency
230 | end,
231 | -- Sets the alpha value of the Widget.
232 | -- `val`: number with range of value from 0 to 255, or nil for default (255)
233 | setAlpha = function (self, val)
234 | if val == 255 then
235 | val = nil
236 | end
237 |
238 | self.transparency = val
239 |
240 | if self.children ~= nil then
241 | for _, c in ipairs(self.children) do
242 | c:setAlpha(val)
243 | end
244 | end
245 |
246 | return self
247 | end,
248 |
249 | -- Gets the child with the specific ID or index.
250 | -- `idOrIndex`: ID string, or index number
251 | -- returns the found child or nil
252 | getChild = function (self, idOrIndex)
253 | if self.children == nil then
254 | return nil
255 | end
256 | if not idOrIndex then
257 | return nil
258 | end
259 | if type(idOrIndex) == 'string' then
260 | for _, v in ipairs(self.children) do
261 | if v.id == idOrIndex then
262 | return v
263 | end
264 | end
265 | elseif type(idOrIndex) == 'number' then
266 | if idOrIndex >= 1 and idOrIndex <= #self.children then
267 | return self.children[idOrIndex]
268 | end
269 | end
270 |
271 | return nil
272 | end,
273 | -- Inserts a child before the specific index.
274 | -- `child`: the child Widget to insert
275 | insertChild = function (self, child, index)
276 | if not child then
277 | return self
278 | end
279 | if child.parent then
280 | error('This widget is already a child of another one.')
281 | end
282 | if self.children == nil then
283 | self.children = { }
284 | end
285 | for _, v in ipairs(self.children) do
286 | if v == child then
287 | return self
288 | end
289 | end
290 | table.insert(self.children, index, child)
291 | child.parent = self
292 |
293 | return self
294 | end,
295 | -- Adds a child to the end of the children list.
296 | -- `child`: the child Widget to add
297 | addChild = function (self, child)
298 | return self:insertChild(child, self.children and #self.children + 1 or 1)
299 | end,
300 | -- Removes a child with the specific child, or its ID or index.
301 | -- `child`: child Widget, or its ID string, or index number
302 | removeChild = function (self, childOrIdOrIndex)
303 | if self.children == nil then
304 | return self
305 | end
306 | if not childOrIdOrIndex then
307 | return self
308 | end
309 | if type(childOrIdOrIndex) == 'string' then
310 | for i, v in ipairs(self.children) do
311 | if v.id == childOrIdOrIndex then
312 | local c = self.children[i]
313 | table.remove(self.children, i)
314 | c.parent = nil
315 |
316 | return self
317 | end
318 | end
319 | error('Child doesn\'t exist.')
320 | elseif type(childOrIdOrIndex) == 'number' then
321 | if childOrIdOrIndex >= 1 and childOrIdOrIndex <= #self.children then
322 | local c = self.children[childOrIdOrIndex]
323 | table.remove(self.children, childOrIdOrIndex)
324 | c.parent = nil
325 | else
326 | error('Index out of bounds.')
327 | end
328 | else
329 | if not childOrIdOrIndex.parent then
330 | error('This widget is not a child of any other one.')
331 | end
332 | for i, v in ipairs(self.children) do
333 | if v == childOrIdOrIndex then
334 | local c = self.children[i]
335 | table.remove(self.children, i)
336 | c.parent = nil
337 |
338 | return self
339 | end
340 | end
341 | end
342 |
343 | return self
344 | end,
345 | -- Iterates all children, and calls the specific handler.
346 | -- `handler`: the children handler in form of `function (child, index) end`
347 | foreachChild = function (self, handler)
348 | if self.children == nil then
349 | return self
350 | end
351 | for i, c in ipairs(self.children) do
352 | handler(c, i)
353 | end
354 |
355 | return self
356 | end,
357 | -- Sorts all children with the specific comparer.
358 | -- `comp`: the comparer in form of `function (left, right) end`
359 | sortChildren = function (self, comp)
360 | table.sort(self.children, comp)
361 |
362 | return self
363 | end,
364 | -- Gets the count of all children.
365 | getChildrenCount = function (self)
366 | if self.children == nil then
367 | return 0
368 | end
369 |
370 | return #self.children
371 | end,
372 | -- Clears all children.
373 | clearChildren = function (self)
374 | if self.children == nil then
375 | return self
376 | end
377 | for i, c in ipairs(self.children) do
378 | c.parent = nil
379 | end
380 | self.children = nil
381 |
382 | return self
383 | end,
384 |
385 | -- Opens a popup.
386 | -- `content`: the popup to open
387 | openPopup = function (self, content)
388 | if self.popup then
389 | return self
390 | end
391 | if not content then
392 | error('Widget expected.')
393 | end
394 |
395 | local focused = self.context.navigated ~= nil
396 | self.focused = self.context.focus
397 | self.context.focus = nil
398 |
399 | self.popup = content
400 | self:addChild(self.popup)
401 |
402 | local w, h = self:size()
403 | self.popup:_updateLayout(w, h)
404 |
405 | if focused then
406 | self.popup:schedule(function ()
407 | if self.popup.context == nil then
408 | self.popup.context = self.context
409 | end
410 | self.popup:navigate('next')
411 | end)
412 | end
413 |
414 | return self
415 | end,
416 | -- Closes any popup.
417 | closePopup = function (self)
418 | if not self.popup then
419 | return self
420 | end
421 | self:removeChild(self.popup)
422 | self.popup = nil
423 |
424 | self.context.focus = self.focused
425 | self.focused = nil
426 |
427 | return self
428 | end,
429 |
430 | -- Updates the Widget and its children recursively.
431 | -- `theme`: the theme to draw with
432 | -- `delta`: elapsed time since previous update
433 | update = function (self, theme, delta, event)
434 | return self:_update(theme, delta, 0, 0, event)
435 | end,
436 |
437 | -- Registers the handler of the specific event.
438 | -- `event`: event name string
439 | -- `handler`: callback function
440 | on = function (self, event, handler)
441 | if not event then
442 | return self
443 | end
444 | if self.events == nil then
445 | self.events = { }
446 | end
447 | if self.events[event] == nil then
448 | self.events[event] = { }
449 | end
450 | if beUtils.exists(self.events[event], handler) then
451 | error('Event handler already exists.')
452 | end
453 | table.insert(self.events[event], handler)
454 |
455 | return self
456 | end,
457 | -- Unregisters the handlers of the specific event.
458 | -- `event`: event name string
459 | off = function (self, event)
460 | if not event then
461 | return self
462 | end
463 | if self.events == nil then
464 | return self
465 | end
466 | if self.events[event] == nil then
467 | return self
468 | end
469 | self.events[event] = nil
470 | if not next(self.events) then
471 | self.events = nil
472 | end
473 |
474 | return self
475 | end,
476 |
477 | -- Gets whether this Widget is navigatable.
478 | -- returns 'all' for fully navigatable,
479 | -- `nil` for non-navigatable,
480 | -- 'children' for children only,
481 | -- 'content' for content only
482 | navigatable = function (self)
483 | return 'children'
484 | end,
485 | -- Navigates through widgets, call this to perform key navigation, etc.
486 | -- `dir`: can be one in 'prev', 'next', 'press', 'cancel'
487 | navigate = function (self, dir)
488 | if dir ~= 'prev' and dir ~= 'next' and dir ~= 'dec' and dir ~= 'inc' and dir ~= 'press' and dir ~= 'cancel' then
489 | error('Unknown navigation: ' .. dir .. '.')
490 | end
491 |
492 | if self.context then
493 | self.context.navigated = dir
494 | end
495 | end,
496 |
497 | -- Gets whether this Widget is queriable.
498 | -- returns `true` for queriable, otherwise `false`
499 | queriable = function (self)
500 | return self.queriablility
501 | end,
502 | -- Sets whether this Widget is queriable.
503 | -- `val`: whether this Widget is queriable
504 | setQueriable = function (self, val)
505 | self.queriablility = val
506 |
507 | return self
508 | end,
509 | -- Queries a Widget at the specific position.
510 | -- `x`: the x position to query
511 | -- `y`: the y position to query
512 | query = function (self, x, y)
513 | if not x or not y or beUtils.isNaN(x) or beUtils.isNaN(y) then
514 | return nil
515 | end
516 | if not self:visible() then
517 | return nil
518 | end
519 | if not self:queriable() then
520 | return nil
521 | end
522 | if self.children ~= nil then
523 | for i = #self.children, 1, -1 do
524 | local c = self.children[i]
525 | local ret = c:query(x, y)
526 | if ret ~= nil then
527 | return ret
528 | end
529 | end
530 | end
531 | local x_, y_ = self.worldX, self.worldY
532 | local w_, h_ = self:size()
533 | if Math.intersects(Vec2.new(x, y), Rect.byXYWH(x_, y_, w_, h_)) then
534 | return self
535 | end
536 |
537 | return nil
538 | end,
539 |
540 | -- Gets whether this Widget has captured mouse event.
541 | captured = function (self)
542 | if not self.visibility then
543 | return false
544 | end
545 | if not self.capturability then
546 | return false
547 | end
548 | if self.popup then
549 | return true
550 | end
551 | if self.hovering then
552 | return true
553 | end
554 | if self.children ~= nil then
555 | for _, c in ipairs(self.children) do
556 | if c:captured() then
557 | return true
558 | end
559 | end
560 | end
561 |
562 | return false
563 | end,
564 |
565 | -- Schedules a tweening procedure.
566 | -- `t`: the tweening object
567 | tween = function (self, t)
568 | if self.tweens == nil then
569 | self.tweens = { }
570 | end
571 | table.insert(self.tweens, t)
572 |
573 | return self
574 | end,
575 | -- Clears all tweening procedures.
576 | clearTweenings = function (self)
577 | self.tweens = nil
578 |
579 | return self
580 | end,
581 |
582 | _updateHierarchy = function (self)
583 | if self.parent then
584 | local w, h = self.parent:size()
585 | self:_updateLayout(w, h)
586 | else
587 | local canvasWidth, canvasHeight = Canvas.main:size()
588 | self:_updateLayout(canvasWidth, canvasHeight)
589 | end
590 | end,
591 | _updateLayout = function (self, parentWidth, parentHeight)
592 | if self.parentWidth == parentWidth and self.parentHeight == parentHeight then
593 | return
594 | end
595 | self.parentWidth, self.parentHeight = parentWidth, parentHeight
596 | local w, h = self:size()
597 |
598 | if self.children == nil then
599 | return
600 | end
601 | for _, c in ipairs(self.children) do
602 | c:_updateLayout(w, h)
603 | end
604 | end,
605 | _update = function (self, theme, delta, dx, dy, event)
606 | if not self.visibility then
607 | return
608 | end
609 |
610 | local once = not event
611 | if once then
612 | self:_touchClip()
613 | if self.context == nil then
614 | self.context = {
615 | navigated = nil,
616 | focus = nil,
617 | active = nil,
618 | dragging = nil,
619 | popup = nil,
620 | clippingStack = self.clippingStack
621 | }
622 | end
623 |
624 | local canvasWidth, canvasHeight = Canvas.main:size()
625 | self:_updateLayout(canvasWidth, canvasHeight)
626 |
627 | local x, y, lmb, _4, _5, wheel = mouse()
628 | event = {
629 | mousePosition = Vec2.new(x, y),
630 | mouseDown = lmb,
631 | mouseWheel = wheel,
632 | canceled = false,
633 | context = self.context
634 | }
635 |
636 | if lmb or wheel ~= 0 then
637 | self.context.navigated = nil
638 | end
639 |
640 | if self.popup ~= nil then
641 | if self.popup.context == nil then
642 | self.popup.context = self.context
643 | end
644 | self.popup:_navigate()
645 | else
646 | self:_navigate()
647 | end
648 | else
649 | self.context = nil
650 | end
651 |
652 | if self.tweens then
653 | local dead = nil
654 | for _, t in ipairs(self.tweens) do
655 | if t:update(delta) then
656 | if dead == nil then
657 | dead = { }
658 | end
659 | table.insert(dead, t)
660 | end
661 | end
662 | if dead then
663 | self.tweens = beUtils.filter(self.tweens, function (t)
664 | return not beUtils.exists(dead, t)
665 | end)
666 | if #self.tweens == 0 then
667 | self.tweens = nil
668 | end
669 | end
670 | end
671 |
672 | local ox, oy = self:offset()
673 | local px, py = self:position()
674 | local x, y = dx + px + ox, dy + py + oy
675 | if beGUI.Widget.DEBUG then
676 | local w, h = self:size()
677 | rect(x, y, x + w - 1, y + h - 1, false, Color.new(255, 0, 0, 128))
678 | end
679 | if self.capturability == true then
680 | local w, h = self:size()
681 | self.hovering = Math.intersects(event.mousePosition, Rect.byXYWH(x, y, w, h))
682 | end
683 | self.worldX, self.worldY = x, y
684 | self:_updateChildren(theme, delta, x, y, event)
685 |
686 | if self.focusIfHovering then
687 | local w, h = self:size()
688 | if self.hovering or Math.intersects(event.mousePosition, Rect.byXYWH(x, y, w, h)) then
689 | if event.context then
690 | event.context.focus = self
691 | end
692 | end
693 | end
694 | if event.context and event.context.focus == self and (event.context.navigated ~= nil or self.focusIfHovering) then
695 | self:_updateFocus(delta, x, y)
696 | end
697 |
698 | if once then
699 | local dragging = self.context and self.context.dragging
700 | if dragging then
701 | dragging:updateDragging(theme, delta, event)
702 | end
703 | end
704 | end,
705 | _updateChildren = function (self, theme, delta, dx, dy, event)
706 | if self.children == nil then
707 | return
708 | end
709 | for _, c in ipairs(self.children) do
710 | if not self.popup or self.popup == c then
711 | c:_update(theme, delta, dx, dy, event)
712 | else
713 | c:_update(
714 | theme, delta, dx, dy,
715 | {
716 | mousePosition = nil,
717 | mouseDown = false,
718 | mouseWheel = 0,
719 | canceled = false,
720 | context = event.context
721 | }
722 | )
723 | end
724 | end
725 | end,
726 | _updateFocus = function (self, delta, x, y)
727 | local HALF_DURATION = 1
728 | self.focusTicks = self.focusTicks + delta
729 | if self.focusTicks >= HALF_DURATION * 2 then
730 | self.focusTicks = self.focusTicks - HALF_DURATION * 2
731 | end
732 | local f = 0
733 | if self.focusTicks < HALF_DURATION then
734 | f = beUtils.clamp(self.focusTicks / HALF_DURATION, 0, 1)
735 | else
736 | f = beUtils.clamp(1 - (self.focusTicks - HALF_DURATION) / HALF_DURATION, 0, 1)
737 | end
738 |
739 | local w, h = self:size()
740 | local col = Color.new(55 + 200 * f, 55 + 200 * f, 55 + 200 * (1 - f))
741 | rect(x, y, x + w - 1, y + h - 1, false, col)
742 | end,
743 |
744 | _touchClip = function (self)
745 | if self.clippingStack == nil then
746 | self.clippingStack = beStack.NonShrinkStack.new(5) -- Change this limit if you really need more than that.
747 | end
748 |
749 | return self
750 | end,
751 | _getClip = function (self)
752 | local clippingStack = self.clippingStack
753 | if not clippingStack then
754 | return nil, nil
755 | end
756 | if clippingStack:empty() then
757 | return nil, nil
758 | end
759 |
760 | local ret1, ret2 = clippingStack:top()
761 |
762 | return ret1, ret2
763 | end,
764 | _beginClip = function (self, event, x, y, w, h)
765 | local clippingStack = event.context and event.context.clippingStack or nil
766 | if not clippingStack then
767 | return false
768 | end
769 |
770 | if clippingStack:empty() then
771 | local canvasWidth, canvasHeight = Canvas.main:size()
772 | if canvasWidth <= 0 or canvasHeight <= 0 then
773 | return false
774 | end
775 | local rect0 = Rect.byXYWH(0, 0, canvasWidth, canvasHeight)
776 | local rect1 = Rect.byXYWH(x, y, w, h)
777 | local rect2 = beUtils.intersected(rect0, rect1)
778 | if not rect2 then
779 | return false
780 | end
781 | local x_, y_, w_, h_ = clip(rect2:xMin(), rect2:yMin(), rect2:width(), rect2:height())
782 | clippingStack:push(
783 | x_ and Rect.byXYWH(x_, y_, w_, h_) or false,
784 | rect2
785 | )
786 |
787 | return true
788 |
789 | -- It is not allowed to clip when an area's border is outside the drawing surface
790 | -- with some graphics backends. If beGUI would never run on those platforms, use
791 | -- the following code to simplify it.
792 | --[[
793 | local x_, y_, w_, h_ = clip(x, y, w, h)
794 | clippingStack:push(
795 | x_ and Rect.byXYWH(x_, y_, w_, h_) or false,
796 | Rect.byXYWH(x, y, w, h)
797 | )
798 |
799 | return true
800 | ]]
801 | else
802 | local _, rect0 = clippingStack:top()
803 | local rect1 = Rect.byXYWH(x, y, w, h)
804 | local rect2 = beUtils.intersected(rect0, rect1)
805 | if not rect2 then
806 | return false
807 | end
808 | local x_, y_, w_, h_ = clip(rect2:xMin(), rect2:yMin(), rect2:width(), rect2:height())
809 | clippingStack:push(
810 | x_ and Rect.byXYWH(x_, y_, w_, h_) or false,
811 | rect2
812 | )
813 |
814 | return true
815 | end
816 | end,
817 | _endClip = function (self, event)
818 | local clippingStack = event.context and event.context.clippingStack or nil
819 | if not clippingStack then
820 | return false
821 | end
822 |
823 | local rect0, _ = clippingStack:pop()
824 | if rect0 then
825 | clip(rect0:xMin(), rect0:yMin(), rect0:width(), rect0:height())
826 | else
827 | clip()
828 | end
829 |
830 | return true
831 | end,
832 |
833 | _navigate = function (self)
834 | if not self.context or not self.context.navigated then
835 | return
836 | end
837 |
838 | if self.context.navigated == 'cancel' then
839 | self:_cancelNavigation()
840 | end
841 |
842 | local lst = { }
843 | self:_fillNavigation(lst)
844 |
845 | local first = #lst > 0 and lst[1] or nil
846 | local last = #lst > 0 and lst[#lst] or nil
847 | local prev = nil
848 | for i, c in ipairs(lst) do
849 | local next = i < #lst and lst[i + 1] or nil
850 | local nav = c:navigatable()
851 | if self.context.navigated == 'prev' then
852 | if self.context.focus == c then
853 | local scrolled = false
854 | if nav == 'content' then
855 | if c:_scroll(nil, -1) then
856 | scrolled = true
857 | end
858 | end
859 | if not scrolled then
860 | self.context.focus = prev or last
861 | end
862 | self.context.navigated = false
863 | end
864 | elseif self.context.navigated == 'next' then
865 | if self.context.focus == c then
866 | local scrolled = false
867 | if nav == 'content' then
868 | if c:_scroll(nil, 1) then
869 | scrolled = true
870 | end
871 | end
872 | if not scrolled then
873 | self.context.focus = next or first
874 | end
875 | self.context.navigated = false
876 | end
877 | end
878 | prev = c
879 | end
880 | if self.context.navigated == 'prev' then
881 | self.context.focus = last
882 | self.context.navigated = false
883 | elseif self.context.navigated == 'next' then
884 | self.context.focus = first
885 | self.context.navigated = false
886 | end
887 | end,
888 | _fillNavigation = function (self, lst)
889 | local nav = self:navigatable()
890 | if not nav then
891 | return
892 | end
893 | if not self:visible() then
894 | return
895 | end
896 | if (nav == 'all' or nav == 'children') and self.children then
897 | for _, c in ipairs(self.children) do
898 | c:_fillNavigation(lst)
899 | end
900 | end
901 | if nav == 'all' or nav == 'content' then
902 | table.insert(lst, self)
903 | end
904 | end,
905 | _cancelNavigation = function (self)
906 | self.context.focus = nil
907 | self.context.navigated = false
908 | end,
909 |
910 | _trigger = function (self, event, ...)
911 | if not event then
912 | return nil
913 | end
914 | if self.events == nil then
915 | return nil
916 | end
917 | if self.events[event] == nil then
918 | return nil
919 | end
920 | if #self.events[event] == 1 then
921 | local ret = self.events[event][1](...)
922 |
923 | return ret
924 | else
925 | local ret = { }
926 | for i, h in ipairs(self.events[event]) do
927 | table.insert(ret, h(...) or false)
928 | end
929 |
930 | return table.unpack(ret)
931 | end
932 | end
933 | })
934 |
935 | --[[
936 | Exporting.
937 | ]]
938 |
939 | return {
940 | Widget = Widget
941 | }
942 |
--------------------------------------------------------------------------------
/src/libs/beGUI/beGUI_Containers.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | The MIT License
3 |
4 | Copyright (C) 2021 - 2022 Tony Wang
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal in
8 | the Software without restriction, including without limitation the rights to
9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10 | the Software, and to permit persons to whom the Software is furnished to do so,
11 | subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | ]]
23 |
24 | local beClass = require 'libs/beGUI/beClass'
25 | local beUtils = require 'libs/beGUI/beGUI_Utils'
26 | local beWidget = require 'libs/beGUI/beGUI_Widget'
27 |
28 | --[[
29 | Widgets.
30 | ]]
31 |
32 | local Group = beClass.class({
33 | ctor = function (self, content)
34 | beWidget.Widget.ctor(self)
35 |
36 | self.content = content
37 | end,
38 |
39 | __tostring = function (self)
40 | return 'Group'
41 | end,
42 |
43 | -- Gets the group name.
44 | getValue = function (self)
45 | return self.content
46 | end,
47 | -- Sets the group name.
48 | setValue = function (self, val)
49 | self.content = val
50 |
51 | return self
52 | end,
53 |
54 | navigatable = function (self)
55 | return 'children'
56 | end,
57 |
58 | _update = function (self, theme, delta, dx, dy, event)
59 | if not self.visibility then
60 | return
61 | end
62 |
63 | local ox, oy = self:offset()
64 | local px, py = self:position()
65 | local x, y = dx + px + ox, dy + py + oy
66 | local w, h = self:size()
67 |
68 | local font_ = theme['font']
69 | local elem = theme['group']
70 | local x_ = x + elem.content_offset[1]
71 | local w_, h_ = measure(self.content, font_.resource, font_.margin or 1, font_.scale or 1)
72 | local black = Color.new(elem.color.r, elem.color.g, elem.color.b, self.transparency or 255)
73 | line(x, y + h_ * 0.5, x, y + h - 1, black)
74 | line(x, y + h - 1, x + w - 1, y + h - 1, black)
75 | line(x + w - 1, y + h_ * 0.5, x + w - 1, y + h - 1, black)
76 | line(x, y + h_ * 0.5, x_ - 2, y + h_ * 0.5, black)
77 | line(x_ + w_ + 2, y + h_ * 0.5, x + w - 1, y + h_ * 0.5, black)
78 | local elem_ = theme['group_title']
79 | beUtils.textCenter(self.content, font_, x_, y, w_, h_, elem_.content_offset, self.transparency)
80 |
81 | beWidget.Widget._update(self, theme, delta, dx, dy, event)
82 | end
83 | }, beWidget.Widget)
84 |
85 | local List = beClass.class({
86 | _withScrollBar = false,
87 | _scrolledTimestamp = nil,
88 | _pressed = false,
89 | _pressedPosition = nil,
90 | _pressingPosition = nil,
91 | _scrolling = nil,
92 | _scrollX = 0, _scrollY = 0,
93 | _scrollSpeed = 16,
94 | _maxX = 0, _maxY = 0,
95 | _scrollDirectionalTimestamp = nil,
96 | _scrollableVertically = true,
97 | _scrollableHorizontally = false,
98 | _inertance = nil,
99 | _inertanceSpeed = nil,
100 | _inertancePosition = nil,
101 | _inertanceDirection = nil,
102 | _childrenCount = 0,
103 | _theme = nil,
104 | _scrollable = true,
105 |
106 | -- Constructs a List.
107 | -- `withScrollBar`: whether to draw scroll bar(s)
108 | ctor = function (self, withScrollBar)
109 | beWidget.Widget.ctor(self)
110 |
111 | self._withScrollBar = withScrollBar
112 | end,
113 |
114 | __tostring = function (self)
115 | return 'List'
116 | end,
117 |
118 | -- Gets whether to allow scrolling vertically.
119 | scrollableVertically = function (self)
120 | return self._scrollableVertically
121 | end,
122 | -- Sets whether to allow scrolling vertically.
123 | setScrollableVertically = function (self, val)
124 | self._scrollableVertically = val
125 |
126 | return self
127 | end,
128 | -- Gets whether to allow scrolling horizontally.
129 | scrollableHorizontally = function (self)
130 | return self._scrollableHorizontally
131 | end,
132 | -- Sets whether to allow scrolling horizontally.
133 | setScrollableHorizontally = function (self, val)
134 | self._scrollableHorizontally = val
135 |
136 | return self
137 | end,
138 |
139 | -- Gets the scroll speed.
140 | scrollSpeed = function (self)
141 | return self._scrollSpeed
142 | end,
143 | -- Sets the scroll speed.
144 | setScrollSpeed = function (self, val)
145 | self._scrollSpeed = val
146 |
147 | return self
148 | end,
149 |
150 | setTheme = function (self, theme)
151 | self._theme = theme
152 |
153 | return self
154 | end,
155 |
156 | -- Gets whether can scroll the widget by mouse wheel.
157 | scrollable = function (self)
158 | return self._scrollable
159 | end,
160 | -- Sets whether can scroll the widget by mouse wheel.
161 | setScrollable = function (self, val)
162 | self._scrollable = val
163 |
164 | return self
165 | end,
166 |
167 | navigatable = function (self)
168 | return 'content'
169 | end,
170 |
171 | _update = function (self, theme, delta, dx, dy, event)
172 | if not self.visibility then
173 | return
174 | end
175 |
176 | local ox, oy = self:offset()
177 | local px, py = self:position()
178 | local x, y = dx + px + ox, dy + py + oy
179 | local w, h = self:size()
180 | local down = false
181 | local intersects = Math.intersects(event.mousePosition, Rect.byXYWH(x, y, w, h))
182 | if event.canceled or event.context.dragging then
183 | self._pressed = false
184 | elseif self._pressed then
185 | down = event.mouseDown
186 | else
187 | down = event.mouseDown and intersects
188 | end
189 | local now = DateTime.ticks()
190 | if down and not self._pressed then
191 | if self._withScrollBar then
192 | self._scrolledTimestamp = now
193 | end
194 | self._pressed = true
195 | self._pressedPosition = event.mousePosition
196 | self._pressingPosition = event.mousePosition
197 | self._scrolling = nil
198 | self._scrollDirectionalTimestamp = now
199 | self._inertancePosition = event.mousePosition
200 | self._inertanceSpeed = 0
201 | elseif not down and self._pressed then
202 | if self._inertanceSpeed ~= nil then
203 | local speed = self._inertanceSpeed
204 | if speed > 5 then
205 | local force = beUtils.clamp(1 - speed / 40, 0, 6) * 20 + 4
206 | local dist = self._pressingPosition - self._pressedPosition
207 | if self._scrollableHorizontally and w < self._maxX then
208 | if math.abs(dist.x) < math.abs(dist.y) then
209 | self._inertance = dist.y * force
210 | self._inertanceDirection = 'y'
211 | else
212 | self._inertance = dist.x * force
213 | self._inertanceDirection = 'x'
214 | end
215 | elseif self._scrollableVertically then
216 | self._inertance = dist.y * force
217 | self._inertanceDirection = 'y'
218 | end
219 | end
220 | end
221 | self._pressed = false
222 | self._pressedPosition = nil
223 | self._pressingPosition = nil
224 | self._scrolling = nil
225 | self._scrollDirectionalTimestamp = nil
226 | self._inertancePosition = nil
227 | self._inertanceSpeed = nil
228 | elseif down and self._pressed then
229 | if self._withScrollBar then
230 | self._scrolledTimestamp = now
231 | end
232 | if self._scrollDirectionalTimestamp ~= nil then
233 | local diff = DateTime.toSeconds(now - self._scrollDirectionalTimestamp)
234 | if diff < 0.3 and self._pressedPosition ~= self._pressingPosition then
235 | local diff = self._pressedPosition - self._pressingPosition
236 | if self._scrollableHorizontally and w < self._maxX then
237 | if math.abs(diff.x) < math.abs(diff.y) then
238 | self._scrolling = 'y'
239 | else
240 | self._scrolling = 'x'
241 | end
242 | elseif self._scrollableVertically then
243 | if math.abs(diff.x) < math.abs(diff.y) then
244 | self._scrolling = 'y'
245 | end
246 | end
247 | self._scrollDirectionalTimestamp = nil
248 | end
249 | end
250 | if self._inertancePosition ~= event.mousePosition then
251 | if self._inertancePosition then
252 | local diff = event.mousePosition - self._inertancePosition
253 | self._inertanceSpeed = diff.length
254 | end
255 | self._inertancePosition = event.mousePosition
256 | end
257 | end
258 |
259 | local elem = theme[self._theme or 'list']
260 | beUtils.tex9Grid(elem, x, y, w, h, nil, self.transparency, nil)
261 |
262 | local scrollBarTransparency = nil
263 | if self._scrolledTimestamp then
264 | local SCROLL_BAR_TIMEOUT_SECONDS = 1
265 | local diff = DateTime.toSeconds(now - self._scrolledTimestamp)
266 | local factor = diff / SCROLL_BAR_TIMEOUT_SECONDS
267 | scrollBarTransparency = factor < 0.7 and 1 or beUtils.clamp(1 - (factor - 0.7) / 0.3, 0, 1)
268 | if factor >= 1 then
269 | self._scrolledTimestamp = nil
270 | end
271 | end
272 | if self._maxX < w then
273 | self._maxX = w
274 | end
275 | if self._maxY < h then
276 | self._maxY = h
277 | end
278 | if self._pressingPosition then
279 | if self._scrolling == 'x' and event.mousePosition then
280 | local diff = event.mousePosition.x - self._pressingPosition.x
281 | if not beUtils.isNaN(diff) then
282 | self._scrollX = self._scrollX + diff
283 | self._scrollX = beUtils.clamp(self._scrollX, w - self._maxX, 0)
284 | end
285 | elseif self._scrolling == 'y' and event.mousePosition then
286 | local diff = event.mousePosition.y - self._pressingPosition.y
287 | if not beUtils.isNaN(diff) then
288 | self._scrollY = self._scrollY + diff
289 | self._scrollY = beUtils.clamp(self._scrollY, h - self._maxY, 0)
290 | end
291 | end
292 | self._pressingPosition = event.mousePosition
293 | elseif intersects and event.mouseWheel < 0 and self._scrollable then
294 | if self._withScrollBar then
295 | self._scrolledTimestamp = now
296 | end
297 | if self._scrollableVertically and not key(beUtils.KeyCodeLShift) and not key(beUtils.KeyCodeRShift) then
298 | self._scrollY = beUtils.clamp(self._scrollY - self._scrollSpeed, h - self._maxY, 0)
299 | elseif self._scrollableHorizontally then
300 | self._scrollX = beUtils.clamp(self._scrollX - self._scrollSpeed, w - self._maxX, 0)
301 | end
302 | elseif intersects and event.mouseWheel > 0 and self._scrollable then
303 | if self._withScrollBar then
304 | self._scrolledTimestamp = now
305 | end
306 | if self._scrollableVertically and not key(beUtils.KeyCodeLShift) and not key(beUtils.KeyCodeRShift) then
307 | self._scrollY = beUtils.clamp(self._scrollY + self._scrollSpeed, h - self._maxY, 0)
308 | elseif self._scrollableHorizontally then
309 | self._scrollX = beUtils.clamp(self._scrollX + self._scrollSpeed, w - self._maxX, 0)
310 | end
311 | end
312 | if not intersects then
313 | event = {
314 | mousePosition = event.mousePosition,
315 | mouseDown = false,
316 | mouseWheel = 0,
317 | canceled = false,
318 | context = event.context
319 | }
320 | elseif self._scrolling then
321 | event = {
322 | mousePosition = event.mousePosition,
323 | mouseDown = false,
324 | mouseWheel = 0,
325 | canceled = true,
326 | context = event.context
327 | }
328 | end
329 | if self._inertance then
330 | if self._withScrollBar then
331 | self._scrolledTimestamp = now
332 | end
333 | if self._inertanceDirection == 'y' then
334 | self._scrollY = beUtils.clamp(self._scrollY + self._inertance * delta, h - self._maxY, 0)
335 | else
336 | self._scrollX = beUtils.clamp(self._scrollX + self._inertance * delta, w - self._maxX, 0)
337 | end
338 | self._inertance = self._inertance * 0.9
339 | if math.abs(self._inertance) < 0.1 then
340 | self._inertance = nil
341 | self._inertanceSpeed = nil
342 | self._inertancePosition = nil
343 | self._inertanceDirection = nil
344 | end
345 | end
346 | if self._scrollX < 0 then
347 | self._scrollX = beUtils.clamp(self._scrollX, w - self._maxX, 0)
348 | end
349 | if self._scrollY < 0 then
350 | self._scrollY = beUtils.clamp(self._scrollY, h - self._maxY, 0)
351 | end
352 |
353 | local clipped = self:_beginClip(event, x + 1, y + 1, w - 2, h - 2)
354 | beWidget.Widget._update(self, theme, delta, dx + self._scrollX, dy + self._scrollY, event)
355 | local count = self:getChildrenCount()
356 | if count ~= self._childrenCount then
357 | self._childrenCount = count
358 | end
359 | if clipped then
360 | if elem.color and scrollBarTransparency then
361 | local col = Color.new(elem.color.r, elem.color.g, elem.color.b, elem.color.a * scrollBarTransparency)
362 | if self._scrollableVertically then
363 | local widgetPos = y + 1
364 | local widgetSize = h - 2
365 | local clientSize = widgetSize
366 | if self._scrollableHorizontally then
367 | clientSize = clientSize - 4
368 | end
369 | local contentSize = self._maxY
370 | local barSize = math.ceil(math.max(math.min((widgetSize / contentSize) * widgetSize, clientSize), 8))
371 | local percent = beUtils.clamp(-self._scrollY / (contentSize - widgetSize), 0, 1)
372 | local slide = clientSize - barSize
373 | local offset = slide * percent
374 | local x_, y_, w_, h_ =
375 | math.floor(x + w - 4), beUtils.round(widgetPos + offset),
376 | math.floor(x + w - 1), math.min(beUtils.round(widgetPos + offset + barSize + 1), widgetPos + clientSize)
377 | rect(x_, y_, w_, h_, true, col)
378 | end
379 | if self._scrollableHorizontally then
380 | local widgetPos = x + 1
381 | local widgetSize = w - 2
382 | local clientSize = widgetSize
383 | if self._scrollableVertically then
384 | clientSize = clientSize - 4
385 | end
386 | local contentSize = self._maxX
387 | local barSize = math.ceil(math.max(math.min((widgetSize / contentSize) * widgetSize, clientSize), 8))
388 | local percent = beUtils.clamp(-self._scrollX / (contentSize - widgetSize), 0, 1)
389 | local slide = clientSize - barSize
390 | local offset = slide * percent
391 | local x_, y_, w_, h_ =
392 | beUtils.round(widgetPos + offset), math.floor(y + h - 4),
393 | math.min(beUtils.round(widgetPos + offset + barSize + 1), widgetPos + clientSize), math.floor(y + h - 1)
394 | rect(x_, y_, w_, h_, true, col)
395 | end
396 | end
397 | end
398 | if clipped then
399 | self:_endClip(event)
400 | end
401 | end,
402 | _updateChildren = function (self, theme, delta, dx, dy, event)
403 | if self.children == nil then
404 | return
405 | end
406 | self._maxX, self._maxY = 0, 0
407 | for _, c in ipairs(self.children) do
408 | c:_update(theme, delta, dx, dy, event)
409 | local px, py = c:position()
410 | local w, h = c:size()
411 | local x, y = px + w, py + h
412 | if x > self._maxX then
413 | self._maxX = x
414 | end
415 | if y > self._maxY then
416 | self._maxY = y
417 | end
418 | end
419 | end,
420 | _updateFocus = function (self, delta, x, y)
421 | local HALF_DURATION = 1
422 | self.focusTicks = self.focusTicks + delta
423 | if self.focusTicks >= HALF_DURATION * 2 then
424 | self.focusTicks = self.focusTicks - HALF_DURATION * 2
425 | end
426 | local f = 0
427 | if self.focusTicks < HALF_DURATION then
428 | f = beUtils.clamp(self.focusTicks / HALF_DURATION, 0, 1)
429 | else
430 | f = beUtils.clamp(1 - (self.focusTicks - HALF_DURATION) / HALF_DURATION, 0, 1)
431 | end
432 |
433 | local w, h = self:size()
434 | local col = Color.new(55 + 200 * f, 55 + 200 * f, 55 + 200 * (1 - f))
435 | rect(x - self._scrollX + 1, y - self._scrollY + 1, x + w - 1 - self._scrollX - 2, y + h - 1 - self._scrollY - 1, false, col)
436 | end,
437 |
438 | _scroll = function (self, dirX, dirY)
439 | local w, h = self:size()
440 | if dirX then
441 | if dirX < 0 then
442 | if self._scrollX < 0 then
443 | self._scrollX = beUtils.clamp(self._scrollX + self._scrollSpeed, w - self._maxX, 0)
444 |
445 | return true
446 | end
447 | elseif dirX > 0 then
448 | if self._scrollX > w - self._maxX then
449 | self._scrollX = beUtils.clamp(self._scrollX - self._scrollSpeed, w - self._maxX, 0)
450 |
451 | return true
452 | end
453 | end
454 | end
455 | if dirY then
456 | if dirY < 0 then
457 | if self._scrollY < 0 then
458 | self._scrollY = beUtils.clamp(self._scrollY + self._scrollSpeed, h - self._maxY, 0)
459 |
460 | return true
461 | end
462 | elseif dirY > 0 then
463 | if self._scrollY > h - self._maxY then
464 | self._scrollY = beUtils.clamp(self._scrollY - self._scrollSpeed, h - self._maxY, 0)
465 |
466 | return true
467 | end
468 | end
469 | end
470 |
471 | return false
472 | end
473 | }, beWidget.Widget)
474 |
475 | local Draggable = beClass.class({
476 | _draggedTimestamp = nil,
477 | _draggedPosition = nil,
478 | _draggingPosition = nil,
479 | _dragging = false,
480 | _dragOffset = nil,
481 | _draggingX = 0, _draggingY = 0,
482 | _draggingEvent = nil,
483 | _draggingTimeout = false,
484 | _dropping = false,
485 |
486 | -- Constructs a Draggable.
487 | ctor = function (self)
488 | beWidget.Widget.ctor(self)
489 |
490 | self._dragOffset = Vec2.new(0, 0)
491 | end,
492 |
493 | __tostring = function (self)
494 | return 'Draggable'
495 | end,
496 |
497 | navigatable = function (self)
498 | return 'children'
499 | end,
500 |
501 | updateDragging = function (self, theme, delta, event)
502 | beWidget.Widget._update(self, theme, delta, self._draggingX, self._draggingY, self._draggingEvent or event)
503 | end,
504 |
505 | _update = function (self, theme, delta, dx, dy, event)
506 | if not self.visibility then
507 | return
508 | end
509 |
510 | local ox, oy = self:offset()
511 | local px, py = self:position()
512 | local x, y = dx + px + ox, dy + py + oy
513 | local w, h = self:size()
514 | local down = false
515 | local intersects = Math.intersects(event.mousePosition, Rect.byXYWH(x, y, w, h))
516 | if event.context.dragging then
517 | down = event.mouseDown
518 | else
519 | down = event.mouseDown and intersects
520 | end
521 | local picking = false
522 | local dropping = false
523 | if self._draggingTimeout then
524 | if not down then
525 | self._draggingTimeout = false
526 | end
527 | elseif down and not event.context.dragging then -- Picked the current widget.
528 | event.context.dragging = self
529 | self._draggedTimestamp = DateTime.ticks()
530 | self._draggedPosition = event.mousePosition
531 | self._draggingPosition = event.mousePosition
532 | self._dragging = false
533 | picking = true
534 | self._draggingEvent = {
535 | mousePosition = event.mousePosition,
536 | mouseDown = false,
537 | mouseWheel = 0,
538 | canceled = false,
539 | context = {
540 | navigated = nil,
541 | focus = nil,
542 | active = nil,
543 | dragging = nil,
544 | popup = nil,
545 | clippingStack = event.clippingStack
546 | }
547 | }
548 | elseif not down and event.context.dragging == self then -- Dropped the current widget.
549 | event.context.dragging = nil
550 | self._draggedTimestamp = nil
551 | self._draggedPosition = nil
552 | self._draggingPosition = nil
553 | self._dragging = false
554 | self._dragOffset = Vec2.new(0, 0)
555 | dropping = true
556 | self._draggingEvent = nil
557 | self._draggingTimeout = false
558 | self._dropping = true
559 | elseif down and event.context.dragging == self then -- Is dragging the current widget.
560 | if self._draggedTimestamp ~= nil then
561 | local diff = DateTime.toSeconds(DateTime.ticks() - self._draggedTimestamp)
562 | if diff < 0.3 and self._draggedPosition ~= self._draggingPosition then
563 | self._dragging = true
564 | self._draggedTimestamp = nil
565 | elseif diff >= 0.3 and self._draggedPosition ~= self._draggingPosition then
566 | event.context.dragging = nil
567 | self._draggedTimestamp = nil
568 | self._draggedPosition = nil
569 | self._draggingPosition = nil
570 | self._dragging = false
571 | self._dragOffset = Vec2.new(0, 0)
572 | self._draggingEvent = nil
573 | self._draggingTimeout = true
574 | end
575 | end
576 | end
577 |
578 | if self._draggingPosition then
579 | if self._dragging then
580 | self._dragOffset = self._dragOffset + (event.mousePosition - self._draggingPosition)
581 | end
582 | self._draggingPosition = event.mousePosition
583 | end
584 | if self._draggingEvent then
585 | if self._dragging then
586 | self._draggingEvent.mousePosition = nil
587 | self._draggingEvent.mouseDown = false
588 | self._draggingEvent.mouseWheel = 0
589 | self._draggingEvent.canceled = true
590 | else
591 | self._draggingEvent.mousePosition = event.mousePosition
592 | self._draggingEvent.mouseDown = event.mouseDown
593 | self._draggingEvent.mouseWheel = event.mouseWheel
594 | self._draggingEvent.canceled = event.canceled
595 | end
596 | end
597 |
598 | self._draggingX, self._draggingY = dx + self._dragOffset.x, dy + self._dragOffset.y
599 | if not picking and not dropping and not self._dragging then
600 | if self._dropping then
601 | self._dropping = false
602 | else
603 | beWidget.Widget._update(self, theme, delta, self._draggingX, self._draggingY, self._draggingEvent or event)
604 | end
605 | end
606 | end
607 | }, beWidget.Widget)
608 |
609 | local Droppable = beClass.class({
610 | _pressed = false,
611 | _dropping = nil,
612 | _dropped = false,
613 |
614 | -- Constructs a Droppable.
615 | ctor = function (self)
616 | beWidget.Widget.ctor(self)
617 | end,
618 |
619 | __tostring = function (self)
620 | return 'Droppable'
621 | end,
622 |
623 | navigatable = function (self)
624 | return 'all'
625 | end,
626 |
627 | _update = function (self, theme, delta, dx, dy, event)
628 | if not self.visibility then
629 | return
630 | end
631 |
632 | local ox, oy = self:offset()
633 | local px, py = self:position()
634 | local x, y = dx + px + ox, dy + py + oy
635 | local w, h = self:size()
636 | local down = false
637 | local intersects = Math.intersects(event.mousePosition, Rect.byXYWH(x, y, w, h))
638 | local dropped = nil
639 | if event.canceled then
640 | self._dropping = nil
641 | else
642 | down = event.mouseDown and intersects
643 | end
644 | if down and not self._dropping then
645 | if event.context.dragging then
646 | self._dropping = event.context.dragging
647 | self._dropped = true
648 | self:_trigger('entered', self, self._dropping)
649 | end
650 | elseif not down and self._dropping then
651 | if event.mouseDown then
652 | self:_trigger('left', self, self._dropping)
653 | else
654 | local droppable = self:_trigger('dropping', self, self._dropping)
655 | if droppable then
656 | dropped = droppable -- Doesn't trigger 'clicked' when has droppable.
657 | if self._dropping.parent then
658 | self._dropping.parent:removeChild(self._dropping)
659 | self:addChild(self._dropping)
660 | end
661 | self:_trigger('dropped', self, self._dropping)
662 | else
663 | self:_trigger('left', self, self._dropping)
664 | end
665 | end
666 | self._dropping = nil
667 | end
668 | if dropped then
669 | self._pressed = false
670 | event.context.focus = self
671 | self._dropped = nil
672 | elseif down and not self._pressed then
673 | self._pressed = true
674 | elseif not event.mouseDown and self._pressed then
675 | self._pressed = false
676 | event.context.focus = self
677 | if not self._dropped then
678 | self:_trigger('clicked', self)
679 | end
680 | self._dropped = nil
681 | elseif event.context.focus == self and event.context.navigated == 'press' then
682 | self:_trigger('clicked', self)
683 | event.context.navigated = false
684 | end
685 |
686 | beWidget.Widget._update(self, theme, delta, dx, dy, event)
687 | end
688 | }, beWidget.Widget)
689 |
690 | local Tab = beClass.class({
691 | _pages = nil,
692 | _pagesInitialized = false,
693 | _tabSize = nil,
694 | _value = 1,
695 | _focusArea = nil,
696 | _headHeight = nil,
697 | _pressed = false,
698 | _scrollable = true,
699 |
700 | -- Constructs a Tab.
701 | ctor = function (self)
702 | beWidget.Widget.ctor(self)
703 |
704 | self.content = { 'Noname' }
705 |
706 | self._pages = { { } }
707 | self._focusArea = { 0, 0, 0, 0 }
708 | end,
709 |
710 | __tostring = function (self)
711 | return 'Tab'
712 | end,
713 |
714 | -- Adds a Tab page with the specific title.
715 | -- `title`: the Tab page title to add
716 | add = function (self, title)
717 | if self._pagesInitialized then
718 | table.insert(self.content, title)
719 | table.insert(self._pages, { })
720 | else
721 | self.content = { title }
722 | self._pagesInitialized = true
723 | end
724 |
725 | return self
726 | end,
727 | -- Gets the page count of the Tab Widget.
728 | count = function (self)
729 | return #self.content
730 | end,
731 |
732 | -- Gets the active page index.
733 | getValue = function (self)
734 | return self._value
735 | end,
736 | -- Sets the active page index.
737 | setValue = function (self, val)
738 | if self._value == val then
739 | return self
740 | end
741 | if val < 1 or val > #self.content then
742 | return self
743 | end
744 | self._value = val
745 | self:_trigger('changed', self, self._value)
746 |
747 | return self
748 | end,
749 |
750 | -- Gets the specified Tab size.
751 | tabSize = function (self)
752 | return self._tabSize
753 | end,
754 | -- Sets the specified Tab size.
755 | setTabSize = function (self, val)
756 | self._tabSize = val
757 |
758 | return self
759 | end,
760 |
761 | addChild = function (self, child)
762 | local result = beWidget.Widget.addChild(self, child)
763 | table.insert(self._pages[self._value], child)
764 |
765 | return result
766 | end,
767 | removeChild = function (self, child)
768 | local result = beWidget.Widget.removeChild(self, child)
769 | self._pages[self._value] = beUtils.filter(self._pages[self._value], function (val)
770 | return val ~= child
771 | end)
772 |
773 | return result
774 | end,
775 |
776 | -- Gets whether can scroll the widget by mouse wheel.
777 | scrollable = function (self)
778 | return self._scrollable
779 | end,
780 | -- Sets whether can scroll the widget by mouse wheel.
781 | setScrollable = function (self, val)
782 | self._scrollable = val
783 |
784 | return self
785 | end,
786 |
787 | navigatable = function (self)
788 | return 'all'
789 | end,
790 |
791 | _update = function (self, theme, delta, dx, dy, event)
792 | if not self.visibility then
793 | return
794 | end
795 |
796 | local ox, oy = self:offset()
797 | local px, py = self:position()
798 | local x, y = dx + px + ox, dy + py + oy
799 | local w, h = self:size()
800 | local down = false
801 | local intersectsHead = Math.intersects(event.mousePosition, Rect.byXYWH(x, y, w, self._headHeight))
802 | if event.context.active and event.context.active ~= self then
803 | self._pressed = false
804 | elseif event.canceled or event.context.dragging then
805 | self._pressed = false
806 | elseif self._pressed then
807 | down = event.mouseDown
808 | else
809 | down = event.mouseDown and Math.intersects(event.mousePosition, Rect.byXYWH(x, y, w, h))
810 | end
811 | local pressed = false
812 | if down and not self._pressed then
813 | self._pressed = true
814 | elseif not down and self._pressed then
815 | self._pressed = false
816 | event.context.focus = self
817 | pressed = true
818 | elseif intersectsHead and event.mouseWheel < 0 and self._scrollable then
819 | if #self.content ~= 0 then
820 | local val = self._value + 1
821 | if val > #self.content then
822 | val = #self.content
823 | end
824 | self:setValue(val)
825 | end
826 | elseif intersectsHead and event.mouseWheel > 0 and self._scrollable then
827 | if #self.content ~= 0 then
828 | local val = self._value - 1
829 | if val < 1 then
830 | val = 1
831 | end
832 | self:setValue(val)
833 | end
834 | elseif event.context.focus == self and event.context.navigated == 'inc' then
835 | local val = self._value + 1
836 | self:setValue(val)
837 | event.context.navigated = false
838 | elseif event.context.focus == self and event.context.navigated == 'dec' then
839 | local val = self._value - 1
840 | self:setValue(val)
841 | event.context.navigated = false
842 | end
843 |
844 | local font_ = theme['font']
845 | local elem = theme['tab']
846 | local paddingX, paddingY = elem.content_offset[1] or 2, elem.content_offset[2] or 2
847 | local x_ = x
848 | for i, v in ipairs(self.content) do
849 | local w_, h_ = nil, nil
850 | if self._tabSize == nil then
851 | if type(v) == 'string' then
852 | w_, h_ = measure(v, font_.resource, font_.margin or 1, font_.scale or 1)
853 | else
854 | w_, h_ = v.area[3], v.area[4]
855 | end
856 | w_ = w_ + paddingX * 2
857 | h_ = h_ + paddingY * 2
858 | else
859 | w_, h_ = self._tabSize.x, self._tabSize.y
860 | end
861 | if self._headHeight == nil then
862 | self._headHeight = h_
863 | end
864 | if pressed then
865 | if Math.intersects(event.mousePosition, Rect.byXYWH(x_, y, w_, h_)) then
866 | local val = i
867 | self:setValue(val)
868 | end
869 | end
870 | local black = Color.new(elem.color.r, elem.color.g, elem.color.b, self.transparency or 255)
871 | if i == self._value then
872 | self._focusArea[1], self._focusArea[2], self._focusArea[3], self._focusArea[4] =
873 | x_ + 1, y + 1, x_ + w_ - 2, y + h_ - 1
874 | -- Tab itself.
875 | line(x_, y, x_ + w_ - 1, y, black)
876 | line(x_ + w_ - 1, y, x_ + w_ - 1, y + h_, black)
877 | -- Border.
878 | line(x, y + h - 1, x + w - 1, y + h - 1, black)
879 | line(x, y, x, y + h - 1, black)
880 | line(x + w - 1, y + h_, x + w - 1, y + h - 1, black)
881 | line(x_ + w_ - 1, y + h_, x + w - 1, y + h_, black)
882 | line(x, y + h_, x_, y + h_, black)
883 | else
884 | line(x_, y, x_ + w_ - 1, y, black)
885 | line(x_ + w_ - 1, y, x_ + w_ - 1, y + h_, black)
886 | end
887 | if type(v) == 'string' then
888 | local elem_ = theme['tab_title']
889 | beUtils.textCenter(v, font_, x_, y, w_, h_, elem_.content_offset, self.transparency)
890 | else
891 | local sx, sy, sw, sh = nil, nil, nil, nil
892 | if v.area then
893 | sx, sy, sw, sh = v.area[1], v.area[2], v.area[3], v.area[4]
894 | end
895 | if self.transparency then
896 | local col = Color.new(255, 255, 255, self.transparency)
897 | tex(v.resource, x_ + paddingX, y + paddingY, sw, sh, sx, sy, sw, sh, 0, Vec2.new(0.5, 0.5), false, false, col)
898 | else
899 | tex(v.resource, x_ + paddingX, y + paddingY, sw, sh, sx, sy, sw, sh)
900 | end
901 | end
902 | x_ = x_ + w_ - 1
903 | end
904 |
905 | beWidget.Widget._update(self, theme, delta, dx, dy, event)
906 | end,
907 | _updateChildren = function (self, theme, delta, dx, dy, event)
908 | if self.children == nil then
909 | return
910 | end
911 | local page = self._pages[self._value]
912 | if not page then
913 | return
914 | end
915 | for _, c in ipairs(page) do
916 | if not self.popup or self.popup == c then
917 | c:_update(theme, delta, dx, dy, event)
918 | else
919 | c:_update(
920 | theme, delta, dx, dy,
921 | {
922 | mousePosition = nil,
923 | mouseDown = false,
924 | mouseWheel = 0,
925 | canceled = false,
926 | context = event.context
927 | }
928 | )
929 | end
930 | end
931 | end,
932 | _updateFocus = function (self, delta, x, y)
933 | local HALF_DURATION = 1
934 | self.focusTicks = self.focusTicks + delta
935 | if self.focusTicks >= HALF_DURATION * 2 then
936 | self.focusTicks = self.focusTicks - HALF_DURATION * 2
937 | end
938 | local f = 0
939 | if self.focusTicks < HALF_DURATION then
940 | f = beUtils.clamp(self.focusTicks / HALF_DURATION, 0, 1)
941 | else
942 | f = beUtils.clamp(1 - (self.focusTicks - HALF_DURATION) / HALF_DURATION, 0, 1)
943 | end
944 |
945 | local w, h = self:size()
946 | local col = Color.new(55 + 200 * f, 55 + 200 * f, 55 + 200 * (1 - f))
947 | rect(self._focusArea[1], self._focusArea[2], self._focusArea[3], self._focusArea[4], false, col)
948 | end,
949 |
950 | _fillNavigation = function (self, lst)
951 | local nav = self:navigatable()
952 | if not nav then
953 | return
954 | end
955 | if not self:visible() then
956 | return
957 | end
958 | if nav == 'all' or nav == 'content' then
959 | table.insert(lst, self)
960 | end
961 | if (nav == 'all' or nav == 'children') and self.children then
962 | for _, c in ipairs(self.children) do
963 | c:_fillNavigation(lst)
964 | end
965 | end
966 | end
967 | }, beWidget.Widget)
968 |
969 | --[[
970 | Exporting.
971 | ]]
972 |
973 | return {
974 | Group = Group,
975 | List = List,
976 | Draggable = Draggable,
977 | Droppable = Droppable,
978 | Tab = Tab
979 | }
980 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | beGUI is a minimal customizable GUI system for Lua, and fully written in Lua.
4 |
5 | Try it [in browser](https://paladin-t.github.io/begui/).
6 |
7 | **Index**
8 |
9 | * [Features](#features)
10 | * [Setup](#setup)
11 | * [Reference](#reference)
12 | * [1. Principles](#1-principles)
13 | * [2. Structures](#2-structures)
14 | * [beStructures.Percent](#bestructurespercent)
15 | * [beGUI.percent](#beguipercent)
16 | * [3. Widget](#3-widget)
17 | * [beGUI.Widget](#beguiwidget)
18 | * [4. Basic Widgets](#4-basic-widgets)
19 | * [beGUI.Label](#beguilabel)
20 | * [beGUI.MultilineLabel](#beguimultilinelabel)
21 | * [beGUI.Url](#beguiurl)
22 | * [beGUI.InputBox](#beguiinputbox)
23 | * [beGUI.Picture](#beguipicture)
24 | * [beGUI.Button](#beguibutton)
25 | * [beGUI.PictureButton](#beguipicturebutton)
26 | * [beGUI.CheckBox](#beguicheckbox)
27 | * [beGUI.RadioBox](#beguiradiobox)
28 | * [beGUI.ComboBox](#beguicombobox)
29 | * [beGUI.NumberBox](#beguinumberbox)
30 | * [beGUI.ProgressBar](#beguiprogressbar)
31 | * [beGUI.Slide](#beguislide)
32 | * [beGUI.Group](#beguigroup)
33 | * [5. Container Widgets](#5-container-widgets)
34 | * [beGUI.List](#beguilist)
35 | * [beGUI.Draggable](#beguidraggable)
36 | * [beGUI.Droppable](#beguidroppable)
37 | * [beGUI.Tab](#beguitab)
38 | * [beGUI.Popup](#beguipopup)
39 | * [beGUI.MessageBox](#beguimessagebox)
40 | * [beGUI.QuestionBox](#beguiquestionbox)
41 | * [6. Custom Widget](#6-custom-widget)
42 | * [beGUI.Custom](#beguicustom)
43 | * [Writing Your Own Widget](#writing-your-own-widget)
44 | * [7. Theme](#7-theme)
45 | * [8. Tweening](#8-tweening)
46 | * [beGUI.Tween](#beguitween)
47 | * [License](#license)
48 |
49 | # Features
50 |
51 | 
52 |
53 | 
54 |
55 | "beGUI" implements:
56 |
57 | * Placable, resizable, anchorable, and nestable `Widget`
58 | * Textual `Label`, `MultilineLabel`, `Url`, `InputBox`
59 | * `Picture`
60 | * Clickable `Button`, `PictureButton`
61 | * `CheckBox`, `RadioBox`
62 | * `ComboBox`
63 | * `NumberBox`
64 | * `ProgressBar`, `Slide`
65 | * `Group`
66 | * Scrollable `List`
67 | * `Draggable` and `Droppable`
68 | * `Tab`
69 | * `Popup`, `MessageBox`, `QuestionBox`
70 | * `Custom` to make your own update function
71 | * And customizable by writing your own widget
72 | * Navigation by key (or custom method)
73 |
74 | Play live demo [in browser](https://paladin-t.github.io/begui/).
75 |
76 | # Setup
77 |
78 | beGUI is originally created to run within the [Bitty Engine](https://github.com/paladin-t/bitty/). The graphics primitives and input API is quite straightforward in Bitty Engine, it's possible to port it to other Lua-based environments with little twist, if that environment does `rect(...)`, `tex(...)`, `text(...)`, `mouse(...)`, etc.
79 |
80 | 1. Clone this repository or download from [releases](https://github.com/paladin-t/begui/releases)
81 | 2. Open "src" directly to run or copy everything under "src" to your own projects for [Bitty Engine](https://github.com/paladin-t/bitty/)
82 |
83 | # Reference
84 |
85 | ## 1. Principles
86 |
87 | beGUI implements a [retained mode](https://en.wikipedia.org/wiki/Retained_mode) GUI system, it separates behaviour and appearance into widget classes and theme preference. Widgets are organized in tree hierarchies, each widget can have none or one parent, and none or multiple children. There are two phases of the lib, first to construct the hierarchy, second to update it. It enumerates from root widget to it's descendents during update, beGUI will handle internal states like visibility, click event, etc. then draw it properly with the theme preference during this procedure.
88 |
89 | Most of the member functions return the widget object itself, it makes it easier to write internal DSL to construct full hierarchy in a tree style code.
90 |
91 | ```lua
92 | require 'libs/beGUI/beGUI'
93 | require 'libs/beGUI/beTheme'
94 |
95 | local widgets = nil
96 | local theme = nil
97 |
98 | function setup()
99 | local P = beGUI.percent -- Alias of percent.
100 | widgets = beGUI.Widget.new()
101 | :put(0, 0)
102 | :resize(P(100), P(100))
103 | :addChild(
104 | beGUI.Label.new('beGUI demo')
105 | :setId('label')
106 | :anchor(0, 0)
107 | :put(10, 10)
108 | :resize(100, 23)
109 | )
110 | :addChild(
111 | beGUI.Button.new('Button')
112 | :setId('button')
113 | :anchor(0, 0)
114 | :put(10, 36)
115 | :resize(100, 23)
116 | :on('clicked', function (sender)
117 | local lbl = widgets:find('label')
118 | lbl:setValue('Clicked ' .. tostring(sender))
119 | end)
120 | )
121 | theme = beTheme.default()
122 | end
123 |
124 | function update(delta)
125 | cls(Color.new(255, 255, 255))
126 |
127 | font(theme['font'].resource)
128 | widgets:update(theme, delta)
129 | font(nil)
130 | end
131 | ```
132 |
133 | Each widget has an anchor property which represents for the locating point in its local space, and a position property for either absolute or percentage position in its parent's space relatively. The final position is calculated according to these two properties. An anchor component is typically in range of values from 0.0 to 1.0, but it could be also less than 0.0 or greater than 1.0. A relative position component is typically in range of values from `Percent(0)` to `Percent(100)`, but it could be also less than `Percent(0)` or greater than `Percent(100)`.
134 |
135 | 
136 |
137 | 
138 |
139 | Resources are splitted into nine grids evenly for flex scaled widgets.
140 |
141 | 
142 |
143 | ## 2. Structures
144 |
145 |
146 | Structures
147 |
148 | These structures are used to help organizing widget layout.
149 |
150 | ### beStructures.Percent
151 |
152 | beStructures.`Percent` denotes relative value instead of absolute for positioning and sizing depending on its parent properties.
153 |
154 | **Model: `require 'libs/beGUI/beGUI_Structures'`**
155 |
156 | * beStructures.`Percent.new(amount)`: constructs a `Percent` object
157 | * `amount`: real number, no limit but often with range of values from 0 to 100
158 | * `p.__mul(num)`: multiply the `Percent` value with another number
159 | * `num`: the number to multiply
160 | * returns result number
161 |
162 | ### beGUI.percent
163 |
164 | Shortcut to create `Percent` object.
165 |
166 | **Model: `require 'libs/beGUI/beGUI'`**
167 |
168 | * beGUI.`percent(amount)`: constructs a `Percent` object
169 | * `amount`: real number, no limit but often with range of values from 0 to 100
170 | * returns `Percent`
171 |
172 |
173 |
174 | ## 3. Widget
175 |
176 |
177 | Widget
178 |
179 | ### beGUI.Widget
180 |
181 | **Model: `require 'libs/beGUI/beGUI'`**
182 |
183 | * beGUI.`Widget.new()`: constructs a `Widget` object
184 |
185 | * `widget:setId(id)`: sets the ID of the `Widget`; an ID is used to identify a `Widget` from others for accessing
186 | * `id`: ID string
187 | * returns `self`
188 | * `widget:get(...)`: gets a child `Widget` with the specific ID sequence
189 | * `...`: ID sequence of the full hierarchy path
190 | * returns the got `Widget` or `nil`
191 | * `widget:find(id)`: finds the first matched `Widget` with the specific ID
192 | * `id`: ID string at any level of the hierarchy path
193 | * returns the found `Widget` or `nil`
194 | * `widget:visible()`: gets the visibility of the `Widget`
195 | * returns boolean for visibility
196 | * `widget:setVisible(val)`: sets the visibility of the `Widget`
197 | * `val`: whether it's visible
198 | * returns `self`
199 | * `widget:capturable()`: gets the capturability of the `Widget`
200 | * returns boolean or string for capturability
201 | * `widget:setCapturable(val)`: sets the capturability of the `Widget`
202 | * `val`: `true`, `false` or `'children'`
203 | * returns `self`
204 | * `widget:anchor(x, y)`: sets the anchor of the `Widget`; anchor is used to calculate the offset when placing `Widget`
205 | * `x`: x position of the anchor in local space as number, typically [0.0, 1.0] for [left, right], but it could be also less than 0.0 or greater than 1.0
206 | * `y`: y position of the anchor in local space as number, typically [0.0, 1.0] for [top, bottom], but it could be also less than 0.0 or greater than 1.0
207 | * returns `self`
208 | * `widget:offset()`: gets the offset of the `Widget`
209 | * returns offset `x`, `y` in world space
210 | * `widget:put(x, y)`: sets the position of the `Widget`
211 | * `x`: number for absolute position; or `Percent` for relative position, typically with range of values from `Percent(0)` to `Percent(100)`, but it could be also less than `Percent(0)` or greater than `Percent(100)`
212 | * `y`: number for absolute position; or `Percent` for relative position, typically with range of values from `Percent(0)` to `Percent(100)`, but it could be also less than `Percent(0)` or greater than `Percent(100)`
213 | * returns `self`
214 | * `widget:position()`: gets the position of the `Widget`
215 | * returns position `x`, `y` in local space
216 | * `widget:worldPosition()`: gets the position of the `Widget` in world space
217 | * returns position `x`, `y` in world space
218 | * `widget:resize(width, height)`: sets the size of the `Widget`
219 | * `width`: number for absolute size; or `Percent` for relative size, typically with range of values from `Percent(0.00...n)` to `Percent(100)`, but it could be also greater than `Percent(100)`
220 | * `height`: number for absolute size; or `Percent` for relative size, typically with range of values from `Percent(0.00...n)` to `Percent(100)`, but it could be also greater than `Percent(100)`
221 | * returns `self`
222 | * `widget:size()`: gets the size of the `Widget`
223 | * returns size `width`, `height`
224 | * `widget:alpha()`: gets the alpha value of the `Widget`
225 | * returns transparency number, with range of values from 0 to 255
226 | * `widget:setAlpha(val)`: sets the alpha value of the `Widget`
227 | * `val`: number with range of value from 0 to 255, or nil for default (255)
228 | * returns `self`
229 | * `widget:getChild(idOrIndex)`: gets the child with the specific ID or index
230 | * `idOrIndex`: ID string, or index number
231 | * returns the found child or nil
232 | * `widget:insertChild(child, index)`: inserts a child before the specific index
233 | * `child`: the child `Widget` to insert
234 | * returns `self`
235 | * `widget:addChild(child)`: adds a child to the end of the children list
236 | * `child`: the child `Widget` to add
237 | * returns `self`
238 | * `widget:removeChild(childOrIdOrIndex)`: removes a child with the specific child, or its ID or index
239 | * `child`: child `Widget`, or its ID string, or index number
240 | * returns `self`
241 | * `widget:foreachChild(handler)`: iterates all children, and calls the specific handler
242 | * `handler`: the children handler in form of `function (child, index) end`
243 | * returns `self`
244 | * `widget:sortChildren(comp)`: sorts all children with the specific comparer
245 | * `comp`: the comparer in form of `function (left, right) end`
246 | * returns `self`
247 | * `widget:getChildrenCount()`: gets the count of all children
248 | * returns children count number
249 | * `widget:clearChildren()`: clears all children
250 | * returns `self`
251 | * `widget:openPopup(content)`: opens a popup
252 | * `content`: the popup to open
253 | * returns `self`
254 | * `widget:closePopup()`: closes any popup
255 | * returns `self`
256 | * `widget:update(theme, delta, event = nil)`: updates the `Widget` and its children recursively
257 | * `theme`: the theme to draw with
258 | * `delta`: elapsed time since previous update
259 | * `event`: omit it for common usage, pass a prefilled event to prevent default event
260 |
261 | * `widget:on(event, handler)`: registers the handler of the specific event
262 | * `event`: event name string
263 | * `handler`: callback function
264 | * returns `self`
265 | * `widget:off(event)`: unregisters the handlers of the specific event
266 | * `event`: event name string
267 | * returns `self`
268 |
269 | * `widget:navigatable()`: gets whether this `Widget` is navigatable
270 | * returns `'all'` for fully navigatable, `nil` for non-navigatable, `'children'` for children only, `'content'` for content only
271 | * `widget:navigate(dir)`: navigates through widgets, call this to perform key navigation, etc.
272 | * `dir`: can be one in `'prev'`, `'next'`, `'press'`, `'cancel'`
273 | * `widget:queriable()`: gets whether this `Widget` is queriable
274 | * returns `true` for queriable, otherwise `false`
275 | * `widget:setQueriable(val)`: sets whether this `Widget` is queriable
276 | * `val`: whether this `Widget` is queriable
277 | * returns `self`
278 | * `widget:query(x, y)`: queries a `Widget` at the specific position
279 | * `x`: the x position to query
280 | * `y`: the y position to query
281 | * returns the queried `Widget` or `nil`
282 | * `widget:captured()`: gets whether this `Widget` has captured mouse event.
283 | * returns `true` for captured, otherwise `false`
284 | * `widget:tween(t)`: schedules a tweening procedure
285 | * `t`: the tweening object
286 | * returns `self`
287 | * `widget:clearTweenings()`: clears all tweening procedures
288 | * returns `self`
289 |
290 |
291 |
292 | ## 4. Basic Widgets
293 |
294 |
295 | Basic Widgets
296 |
297 | ### beGUI.Label
298 |
299 | **Model: `require 'libs/beGUI/beGUI'`, implements beGUI.`Widget`**
300 |
301 | * beGUI.`Label.new(content, alignment = 'left', clip_ = false, theme = nil, shadow = nil)`: constructs a `Label` with the specific content
302 | * `content`: the content string
303 | * `alignment`: one in `nil`, `'left'`, `'right'`, `'center'`
304 | * `clip_`: whether to clip drawing outside this `Widget`'s bounds
305 | * `theme`: custom theme
306 | * `shadow`: shadow theme for shadowed drawing
307 |
308 | * `label:getValue()`: gets the content text
309 | * returns the content string
310 | * `label:setValue(val)`: sets the content text
311 | * `val`: the specific content string
312 | * returns `self`
313 | * `label:alignment()`: gets the alignment
314 | * returns the alignment preference string
315 | * `label:setAlignment(val)`: sets the alignment preference
316 | * `val`: the specific alignment preference string
317 | * returns `self`
318 | * `label:clipping()`: gets whether to clip drawing outside the `Widget`'s bounds
319 | * returns `true` for clipping, otherwise `false`
320 | * `label:setClipping(val)`: sets whether to clip drawing outside the `Widget`'s bounds
321 | * `val`: `true` to clip
322 | * returns `self`
323 | * `label:setTheme(theme, shadow = nil)`: sets the theme
324 | * `theme`: the custom theme
325 | * `shadow`: the custom shadow theme
326 | * returns `self`
327 |
328 | ### beGUI.MultilineLabel
329 |
330 | **Model: `require 'libs/beGUI/beGUI'`, implements beGUI.`Widget`**
331 |
332 | * beGUI.`MultilineLabel.new(content, lineHeight = nil, alighment = 'left')`: constructs a `MultilineLabel` with the specific content
333 | * `content`: the content string
334 | * `lineHeight`: the custom line height
335 | * `alignment`: one in `nil`, `'left'`, `'right'`, `'center'`
336 |
337 | * `multilinelabel:getValue()`: gets the content text
338 | * returns the content string
339 | * `multilinelabel:setValue(val)`: sets the content text
340 | * `val`: the specific content string
341 | * returns `self`
342 | * `multilinelabel:lineHeight()`: gets the line height
343 | * returns the line height
344 | * `multilinelabel:setLineHeight(val)`: sets the line height
345 | * `val`: the specific line height
346 | * returns `self`
347 | * `multilinelabel:alignment()`: gets the alignment
348 | * returns the alignment preference string
349 | * `multilinelabel:setAlignment(val)`: sets the alignment preference, the preference falls to `'left'` if had set flex width to `true`
350 | * `val`: the specific alignment preference string
351 | * returns `self`
352 | * `multilinelabel:setTheme(theme, widgetTheme)`: sets the theme
353 | * `theme`: the custom font theme
354 | * `widgetTheme`: the custom widget theme
355 | * returns `self`
356 | * `multilinelabel:flexWidth()`: gets whether to calculate `Widget` width automatically
357 | * returns `true` for calculating automatically, otherwise `false`
358 | * `multilinelabel:setFlexWidth(val)`: sets whether to calculate `Widget` width automatically
359 | * `val`: `true` to calculate automatically
360 | * returns `self`
361 | * `multilinelabel:flexHeight()`: gets whether to calculate `Widget` height automatically
362 | * returns `true` for calculating automatically, otherwise `false`
363 | * `multilinelabel:setFlexHeight(val)`: sets whether to calculate `Widget` height automatically
364 | * `val`: `true` to calculate automatically
365 | * returns `self`
366 | * `multilinelabel:pattern()`: gets the word split pattern
367 | * returns the word split pattern string
368 | * `multilinelabel:setPattern(val)`: sets the word split pattern
369 | * `val`: the specific word split pattern
370 | * returns `self`
371 | * `multilinelabel:translator()`: gets the word translator
372 | * returns the word translator
373 | * `multilinelabel:setTranslator(val)`: sets the word translator
374 | * `val`: the specific word translator
375 | * returns `self`
376 |
377 | ### beGUI.Url
378 |
379 | **Model: `require 'libs/beGUI/beGUI'`, implements beGUI.`Widget`**
380 |
381 | * beGUI.`Url.new(content, alighment = 'left', clip_ = false, theme = nil)`: constructs a `Url` with the specific content
382 | * `content`: the content string
383 | * `alignment`: one in `nil`, `'left'`, `'right'`, `'center'`
384 | * `clip_`: whether to clip drawing outside this `Widget`'s bounds
385 | * `theme`: custom theme
386 |
387 | * `url:getValue()`: gets the content text
388 | * returns the content string
389 | * `url:setValue(val)`: sets the content text
390 | * `val`: the specific content string
391 | * returns `self`
392 | * `url:alignment()`: gets the alignment
393 | * returns the alignment preference string
394 | * `url:setAlignment(val)`: sets the alignment preference
395 | * `val`: the specific alignment preference string
396 | * returns `self`
397 | * `url:clipping()`: gets whether to clip drawing outside the `Widget`'s bounds
398 | * returns `true` for clipping, otherwise `false`
399 | * `url:setClipping(val)`: sets whether to clip drawing outside the `Widget`'s bounds
400 | * `val`: `true` to clip
401 | * returns `self`
402 | * `url:setTheme(theme)`: sets the theme
403 | * `theme`: the custom theme
404 | * returns `self`
405 |
406 | * `url:on('clicked', function (sender) end)`: registers an event which will be triggered when the `Widget` has been clicked
407 | * returns `self`
408 |
409 | ### beGUI.InputBox
410 |
411 | **Model: `require 'libs/beGUI/beGUI'`, implements beGUI.`Widget`**
412 |
413 | * beGUI.`InputBox.new(content placeholder)`: constructs an InputBox with the specific content
414 | * `content`: the content string
415 | * `placeholder`: the placeholder string when there's no input yet
416 |
417 | * `inputbox:getValue()`: gets the content text
418 | * returns the content string
419 | * `inputbox:setValue(val)`: sets the content text
420 | * `val`: the specific content string
421 | * `inputbox:setTheme(theme, placeholderTheme)`: sets the theme
422 | * `theme`: the custom font theme
423 | * `placeholderTheme`: the custom placeholder theme
424 | * returns `self`
425 | * `inputbox:placeholder()`: gets the placeholder text
426 | * returns the placeholder string
427 | * `inputbox:setPlaceholder(val)`: sets the placeholder text
428 | * `val`: the specific placeholder string
429 | * returns `self`
430 |
431 | * `inputbox:on('changed', function (sender, value) end)`: registers an event which will be triggered when the `Widget` content text has been changed
432 | * returns `self`
433 |
434 | ### beGUI.Picture
435 |
436 | **Model: `require 'libs/beGUI/beGUI'`, implements beGUI.`Widget`**
437 |
438 | * beGUI.`Picture.new(content, stretched = false, permeation = false)`: constructs a Picture with the specific content
439 | * `content`: the content `Texture`
440 | * `stretched`: whether to use 9-grid-based splitting for stretching
441 | * `permeation`: whether to use permeation correction
442 |
443 | * `picture:setValue(content, stretched = false, permeation = false)`: sets the content `Texture`
444 | * `content`: the content `Texture`
445 | * returns `self`
446 | * `picture:stretched()`: gets whether to use 9-grid-based splitting for stretching
447 | * returns `true` for 9-grid-based splitting, otherwise `false`
448 | * `picture:setStretched(val)`: sets whether to use 9-grid-based splitting for stretching
449 | * `val`: `true` to use 9-grid-based splitting for stretching
450 | * returns `self`
451 | * `picture:permeation()`: gets whether to use permeation correction
452 | * returns `true` for permeation correction, otherwise `false`
453 | * `picture:setPermeation(val)`: sets whether to use permeation correction
454 | * `val`: `true` to use permeation correction
455 | * returns `self`
456 | * `picture:color()`: gets the mask color of the `Picture`
457 | * returns the mask color or `nil`
458 | * `picture:setColor(val)`: sets the mask color of the `Picture`
459 | * `val`: the specific mask color
460 | * returns `self`
461 |
462 | ### beGUI.Button
463 |
464 | **Model: `require 'libs/beGUI/beGUI'`, implements beGUI.`Widget`**
465 |
466 | * beGUI.`Button.new(content)`: constructs a Button with the specific content
467 | * `content`: the content string
468 |
469 | * `button:setValue(content)`: sets the content text
470 | * `val`: the specific content string
471 | * `button:setTheme(theme, themeNormal, themeDown, themeDisabled)`: sets the theme
472 | * `theme`: the custom font theme
473 | * `themeNormal`: the custom theme for normal state
474 | * `themeDown`: the custom theme for pressed state
475 | * `themeDisabled`: the custom theme for disabled state
476 | * returns `self`
477 | * `button:enabled()`: gets whether this `Widget` is enabled
478 | * returns `true` for enabled, otherwise `false`
479 | * `button:setEnabled(val)`: sets whether this `Widget` is enabled
480 | * `val`: `true` for enabled, otherwise `false`
481 | * returns `self`
482 |
483 | * `button:on('clicked', function (sender) end)`: registers an event which will be triggered when the `Widget` has been clicked
484 | * returns `self`
485 |
486 | ### beGUI.PictureButton
487 |
488 | **Model: `require 'libs/beGUI/beGUI'`, implements beGUI.`Widget`**
489 |
490 | * beGUI.`PictureButton.new(content, repeat_ = false, theme = nil, background = nil)`: constructs a `PictureButton` with the specific content
491 | * `content`: the content `Texture`
492 | * `repeat_`: whether to enable repeating event
493 | * `theme`: the custom theme
494 | * `background`: optional, the custom background `Texture`
495 |
496 | * `picturebutton:setTheme(theme, widgetTheme)`: sets the theme
497 | * `theme`: the custom font theme
498 | * `widgetTheme`: the custom widget theme
499 | * returns `self`
500 | * `picturebutton:enabled()`: gets whether this `Widget` is enabled
501 | * returns `true` for enabled, otherwise `false`
502 | * `picturebutton:setEnabled(val)`: sets whether this `Widget` is enabled
503 | * `val`: `true` for enabled, otherwise `false`
504 | * returns `self`
505 |
506 | * `picturebutton:on('clicked', function (sender) end)`: registers an event which will be triggered when the `Widget` has been clicked
507 | * returns `self`
508 |
509 | ### beGUI.CheckBox
510 |
511 | **Model: `require 'libs/beGUI/beGUI'`, implements beGUI.`Widget`**
512 |
513 | * beGUI.`CheckBox.new(content, value = false)`: constructs a `CheckBox` with the specific content
514 | * `content`: the content string
515 | * `value`: the initial checked state
516 |
517 | * `checkbox:getValue()`: gets whether this `Widget` is checked
518 | * returns `true` for checked, otherwise `false`
519 | * `checkbox:setValue(val)`: sets whether this `Widget` is checked
520 | * `val`: `true` for checked, otherwise `false`
521 | * returns `self`
522 | * `checkbox:setContent(val)`: sets the text content of this `Widget`
523 | * `val`: the content string
524 | * returns `self`
525 | * `checkbox:enabled()`: gets whether this `Widget` is enabled
526 | * returns `true` for enabled, otherwise `false`
527 | * `checkbox:setEnabled(val)`: sets whether this `Widget` is enabled
528 | * `val`: `true` for enabled, otherwise `false`
529 | * returns `self`
530 |
531 | * `checkbox:on('changed', function (sender, value) end)`: registers an event which will be triggered when the `Widget` checked state has been changed
532 | * returns `self`
533 |
534 | ### beGUI.RadioBox
535 |
536 | **Model: `require 'libs/beGUI/beGUI'`, implements beGUI.`Widget`**
537 |
538 | * beGUI.`RadioBox.new(content, value = false)`: constructs a `RadioBox` with the specific content
539 | * `content`: the content string
540 | * `value`: the initial checked state
541 |
542 | * `radiobox:getValue()`: gets whether this `Widget` is checked
543 | * returns `true` for checked, otherwise `false`
544 | * `radiobox:setValue(val)`: sets whether this `Widget` is checked; not recommended to call this manually
545 | * `val`: `true` for checked, otherwise `false`
546 | * `radiobox:setContent(val)`: sets the text content of this `Widget`
547 | * `val`: the content string
548 | * returns `self`
549 |
550 | * `radiobox:on('changed', function (sender, value) end)`: registers an event which will be triggered when the `Widget` checked state has been changed
551 | * returns `self`
552 |
553 | ### beGUI.ComboBox
554 |
555 | **Model: `require 'libs/beGUI/beGUI'`, implements beGUI.`Widget`**
556 |
557 | * beGUI.`ComboBox.new(content, value = nil)`: constructs a `ComboBox` with the specific content
558 | * `content`: list of string
559 | * `value`: the selected index number
560 |
561 | * `combobox:getItemAt(index)`: gets the item text at the specific index
562 | * `index`: the specific index to get
563 | * returns got item string or `nil`
564 | * `combobox:addItem(item)`: adds an item string with the specific content text
565 | * `item` the specific item text to add
566 | * returns `self`
567 | * `combobox:removeItemAt(index)`: removes the item at the specific index
568 | * `index` the specific index to remove
569 | * returns `true` for success, otherwise `false`
570 | * `combobox:clearItems()`: clears all items
571 | * returns `self`
572 | * `combobox:getValue()`: gets the selected index
573 | * returns the selected index number
574 | * `combobox:setValue(val)`: sets the selected index
575 | * `val`: the specific selected index
576 | * returns `self`
577 | * `combobox:scrollable()`: gets whether can scroll the widget by mouse wheel
578 | * returns `true` for scrollable, otherwise `false`
579 | * `combobox:setScrollable(val)`: sets whether can scroll the widget by mouse wheel
580 | * `val`: `true` for allowing scrolling with a mouse wheel, otherwise `false`
581 | * returns `self`
582 |
583 | * `combobox:on('changed', function (sender, value) end)`: registers an event which will be triggered when the `Widget` selection state has been changed
584 | * returns `self`
585 |
586 | ### beGUI.NumberBox
587 |
588 | **Model: `require 'libs/beGUI/beGUI'`, implements beGUI.`Widget`**
589 |
590 | * beGUI.`NumberBox.new(value, step, min = nil, max = nil, trim = nil, format = nil)`: constructs a `NumberBox` with the specific value
591 | * `value`: the initial value number
592 | * `step`: the changing step
593 | * `min`: the minumum limit
594 | * `max`: the maximum limit
595 | * `trim`: optional, used to trim before value setting
596 | * `format`: optional, used to format value for output
597 |
598 | * `numberbox:getValue()`: gets the value number
599 | * returns the value number
600 | * `numberbox:setValue(val)`: sets the value number
601 | * `val`: the specific value number
602 | * returns `self`
603 | * `numberbox:getMinValue()`: gets the minimum limit number
604 | * returns the minimum limit number
605 | * `numberbox:setMinValue(val)`: sets the minimum limit number
606 | * `val`: the specific minimum limit number
607 | * returns `self`
608 | * `numberbox:getMaxValue()`: gets the maximum limit number
609 | * returns the maximum limit number
610 | * `numberbox:setMaxValue(val)`: sets the maximum limit number
611 | * `val`: the specific maximum limit number
612 | * returns `self`
613 | * `numberbox:step()`: gets the changing step
614 | * returns the changing step
615 | * `numberbox:setStep(val)`: sets the changing step
616 | * `val`: the specific changing step number
617 | * returns `self`
618 | * `numberbox:trim()`: gets the trim function
619 | * returns the trim function
620 | * `numberbox:setTrim(val)`: sets the trim function
621 | * `val`: the specific trim function
622 | * returns `self`
623 | * `numberbox:format()`: gets the format function
624 | * returns the format function
625 | * `numberbox:setFormat(val)`: sets the format function
626 | * `val`: the specific format function
627 | * returns `self`
628 | * `numberbox:setValueTheme(val)`: sets the value theme
629 | * `val`: the specific theme
630 | * returns `self`
631 | * `numberbox:scrollable()`: gets whether can scroll the widget by mouse wheel
632 | * returns `true` for scrollable, otherwise `false`
633 | * `numberbox:setScrollable(val)`: sets whether can scroll the widget by mouse wheel
634 | * `val`: `true` for allowing scrolling with a mouse wheel, otherwise `false`
635 | * returns `self`
636 |
637 | * `numberbox:on('changed', function (sender, value) end)`: registers an event which will be triggered when the `Widget` value has been changed
638 | * returns `self`
639 |
640 | ### beGUI.ProgressBar
641 |
642 | **Model: `require 'libs/beGUI/beGUI'`, implements beGUI.`Widget`**
643 |
644 | * beGUI.`ProgressBar.new(max, color, increasing = 'right')`: constructs a `ProgressBar`
645 | * `max`: the maximum value
646 | * `color`: the color for the completed bar
647 | * `increasing`: indicates whether to increase from left to right, or reversed, one in `'left'`, `'right'`
648 |
649 | * `progressbar:getValue()`: gets the value number
650 | * returns the value number
651 | * `progressbar:setValue(val)`: sets the value number
652 | * `val`: the specific value number
653 | * returns `self`
654 | * `progressbar:getMaxValue()`: gets the maximum limit number
655 | * returns the maximum limit number
656 | * `progressbar:setMaxValue(val)`: sets the maximum limit number
657 | * `val`: the specific maximum limit number
658 | * returns `self`
659 | * `progressbar:getShadowValue()`: gets the shadow value number
660 | * returns the value number
661 | * `progressbar:setShadowValue(val)`: sets the shadow value number
662 | * `val`: the specific shadow value number
663 | * returns `self`
664 | * `progressbar:setTheme(theme)`: sets the theme
665 | * `theme`: the custom theme
666 | * returns `self`
667 |
668 | * `progressbar:on('changed', function (sender, value, maxValue, shadowValue) end)`: registers an event which will be triggered when the `Widget` value has been changed
669 | * returns `self`
670 |
671 | ### beGUI.Slide
672 |
673 | **Model: `require 'libs/beGUI/beGUI'`, implements beGUI.`Widget`**
674 |
675 | * beGUI.`Slide.new(value, min, max)`: constructs a `Slide` with the specific value
676 | * `value`: the initial value number
677 | * `min`: the minimum limit number
678 | * `max`: the maximum limit number
679 |
680 | * `slide:getValue()`: gets the value number
681 | * returns the value number
682 | * `slide:setValue(val)`: sets the value number
683 | * `val`: the specific value number
684 | * returns `self`
685 | * `slide:getMinValue()`: gets the minimum limit number
686 | * returns the minimum limit number
687 | * `slide:setMinValue(val)`: sets the minimum limit number
688 | * `val`: the specific minimum limit number
689 | * returns `self`
690 | * `slide:getMaxValue()`: gets the maximum limit number
691 | * returns the maximum limit number
692 | * `slide:setMaxValue(val)`: sets the maximum limit number
693 | * `val`: the specific maximum limit number
694 | * returns `self`
695 | * `slide:scrollable()`: gets whether can scroll the widget by mouse wheel
696 | * returns `true` for scrollable, otherwise `false`
697 | * `slide:setScrollable(val)`: sets whether can scroll the widget by mouse wheel
698 | * `val`: `true` for allowing scrolling with a mouse wheel, otherwise `false`
699 | * returns `self`
700 |
701 | * `slide:on('changed', function (sender, value) end)`: registers an event which will be triggered when the `Widget` value has been changed
702 | * returns `self`
703 |
704 | ### beGUI.Group
705 |
706 | **Model: `require 'libs/beGUI/beGUI'`, implements beGUI.`Widget`**
707 |
708 | * beGUI.`Group.new(content)`: constructs a `Group`
709 | * `content`: the content string
710 |
711 | * `group:getValue()`: gets the content text
712 | * returns the content string
713 | * `group:setValue(val)`: sets the content text
714 | * `val`: the specific content string
715 | * returns `self`
716 |
717 |
718 |
719 | ## 5. Container Widgets
720 |
721 |
722 | Container Widgets
723 |
724 | ### beGUI.List
725 |
726 | **Model: `require 'libs/beGUI/beGUI'`, implements beGUI.`Widget`**
727 |
728 | * beGUI.`List.new(withScrollBar = false)`: constructs a `List`
729 | * `withScrollBar`: whether to draw scroll bar(s)
730 |
731 | * `list:scrollableVertically()`: gets whether to allow scrolling vertically
732 | * returns `true` for allowing scrolling vertically, otherwise `false`
733 | * `list:setScrollableVertically(val)`: sets whether to allow scrolling vertically
734 | * `val`: `true` for allowing scrolling vertically, otherwise `false`
735 | * returns `self`
736 | * `list:scrollableHorizontally()`: gets whether to allow scrolling horizontally
737 | * returns `true` for allowing scrolling horizontally, otherwise `false`
738 | * `list:setScrollableHorizontally(val)`: sets whether to allow scrolling horizontally
739 | * `val`: `true` for allowing scrolling horizontally, otherwise `false`
740 | * returns `self`
741 | * `list:scrollSpeed()`: gets the scroll speed
742 | * returns the scroll speed
743 | * `list:setScrollSpeed(val)`: sets the scroll speed
744 | * `val`: the specific scroll speed
745 | * returns `self`
746 | * `list:setTheme(theme)`: sets the theme
747 | * `theme`: the custom theme
748 | * returns `self`
749 | * `list:scrollable()`: gets whether can scroll the widget by mouse wheel
750 | * returns `true` for scrollable, otherwise `false`
751 | * `list:setScrollable(val)`: sets whether can scroll the widget by mouse wheel
752 | * `val`: `true` for allowing scrolling with a mouse wheel, otherwise `false`
753 | * returns `self`
754 |
755 | ### beGUI.Draggable
756 |
757 | **Model: `require 'libs/beGUI/beGUI'`, implements beGUI.`Widget`**
758 |
759 | * beGUI.`Draggable.new()`: constructs a `Draggable`
760 |
761 | ### beGUI.Droppable
762 |
763 | **Model: `require 'libs/beGUI/beGUI'`, implements beGUI.`Widget`**
764 |
765 | * beGUI.`Droppable.new()`: constructs a `Droppable`
766 |
767 | * `droppable:on('entered', function (sender, draggable) end)`: registers an event which will be triggered when the `Widget` has been entered by a `Draggable`
768 | * returns `self`
769 | * `droppable:on('left', function (sender, draggable) end)`: registers an event which will be triggered when the `Widget` has been left by a `Draggable`
770 | * returns `self`
771 | * `droppable:on('dropping', function (sender, draggable) return droppable end)`: registers an event which will be triggered when the `Widget` has been hovering by a `Draggable`
772 | * returns `self`
773 | * `droppable:on('dropped', function (sender, draggable) end)`: registers an event which will be triggered when the `Widget` has been dropped by a `Draggable`
774 | * returns `self`
775 | * `droppable:on('clicked', function (sender) end)`: registers an event which will be triggered when the `Widget` has been clicked
776 | * returns `self`
777 |
778 | ### beGUI.Tab
779 |
780 | **Model: `require 'libs/beGUI/beGUI'`, implements beGUI.`Widget`**
781 |
782 | * beGUI.`Tab.new()`: constructs a `Tab`
783 |
784 | * `tab:add(title)`: adds a `Tab` page with the specific title
785 | * `title`: the `Tab` page title to add
786 | * returns `self`
787 | * `tab:count()`: gets the page count of the `Tab` `Widget`
788 | * returns the `Tab` page count
789 | * `tab:getValue()`: gets the active page index
790 | * returns the active page index number
791 | * `tab:setValue(val)`: sets the active page index
792 | * `val`: the specific page index
793 | * returns `self`
794 | * `tab:tabSize()`: gets the specified `Tab` size
795 | * returns the specified `Tab` size
796 | * `tab:setTabSize(val)`: sets the specified `Tab` size
797 | * `val`: the specified `Tab` size
798 | * returns `self`
799 | * `tab:scrollable()`: gets whether can scroll the widget by mouse wheel
800 | * returns `true` for scrollable, otherwise `false`
801 | * `tab:setScrollable(val)`: sets whether can scroll the widget by mouse wheel
802 | * `val`: `true` for allowing scrolling with a mouse wheel, otherwise `false`
803 | * returns `self`
804 |
805 | * `tab:on('changed', function (sender, value) end)`: registers an event which will be triggered when the `Widget` page has been switched
806 | * returns `self`
807 |
808 | ### beGUI.Popup
809 |
810 | **Model: `require 'libs/beGUI/beGUI'`, implements beGUI.`Widget`**
811 |
812 | * beGUI.`Popup.new()`: constructs a `Popup`
813 |
814 | ### beGUI.MessageBox
815 |
816 | **Model: `require 'libs/beGUI/beGUI'`, implements beGUI.`Popup`**
817 |
818 | * beGUI.`MessageBox.new(closable, title, message, confirm = 'OK')`: constructs a `MessageBox`
819 | * `closable`: `true` to enable the close button, `false` to disable
820 | * `title`: the title text
821 | * `message`: the message text
822 | * `confirm`: the text for the confirm button
823 |
824 | * `messagebox:on('canceled', function (sender) end)`: registers an event which will be triggered when the `Popup` has been canceled
825 | * returns `self`
826 | * `messagebox:on('confirmed', function (sender) end)`: registers an event which will be triggered when the `Popup` has been confirmed
827 | * returns `self`
828 |
829 | ### beGUI.QuestionBox
830 |
831 | **Model: `require 'libs/beGUI/beGUI'`, implements beGUI.`Popup`**
832 |
833 | * beGUI.`QuestionBox.new(closable, title, messsage, confirm, deny)`: constructs a `QuestionBox`
834 | * `closable`: `true` to enable the close button, `false` to disable
835 | * `title`: the title text
836 | * `message`: the message text
837 | * `confirm`: the text for the confirm button
838 | * `deny`: the text for the deny button
839 |
840 | * `questionbox:on('canceled', function (sender) end)`: registers an event which will be triggered when the `Popup` has been canceled
841 | * returns `self`
842 | * `questionbox:on('confirmed', function (sender) end)`: registers an event which will be triggered when the `Popup` has been confirmed
843 | * returns `self`
844 | * `questionbox:on('denied', function (sender) end)`: registers an event which will be triggered when the `Popup` has been denied
845 | * returns `self`
846 |
847 |
848 |
849 | ## 6. Custom Widget
850 |
851 |
852 | Custom Widget
853 |
854 | There are two ways to customize your own `Widget`, one is to use the beWidget.`Custom` `Widget`, the other is to write your own `Widget` class.
855 |
856 | ### beGUI.Custom
857 |
858 | The `Custom` `Widget` exposes a `'updated'` event to let you write short customized update routine in the callback.
859 |
860 | **Model: `require 'libs/beGUI/beGUI'`, implements beGUI.`Widget`**
861 |
862 | * beGUI.`Custom.new(name = 'Custom')`: constructs a `Custom` `Widget`
863 | * `name`: the custom `Widget` name used to perform `__tostring`
864 |
865 | * `custom:name()`: gets the custom `Widget` name
866 | * returns the custom `Widget` name
867 | * `custom:setName(val)`: sets the custom `Widget` name
868 | * `val`: the specific custom `Widget` name
869 | * returns `self`
870 |
871 | * `custom:on('updated', function (sender, x, y, w, h, delta, event) end)`: registers an event which will be triggered when the `Widget` has been updated per frame
872 | * returns `self`
873 |
874 | ### Writing Your Own Widget
875 |
876 | You can also write your own `Widget` inheriting from beWidget.`Widget`.
877 |
878 | ```lua
879 | local beClass = require 'libs/beGUI/beClass'
880 | local beUtils = require 'libs/beGUI/beGUI_Utils'
881 | local beWidget = require 'libs/beGUI/beGUI_Widget'
882 |
883 | local MyWidget = beClass.class({
884 | _value = nil, -- Define your fields.
885 | _pressed = false,
886 |
887 | ctor = function (self, ...)
888 | beWidget.Widget.ctor(self)
889 |
890 | -- Customize your constructor.
891 | end,
892 |
893 | __tostring = function (self)
894 | return 'MyWidget'
895 | end,
896 |
897 | getValue = function (self) -- Define your properties.
898 | return self._value
899 | end,
900 | setValue = function (self, val)
901 | if self._value == val then
902 | return self
903 | end
904 | self._value = val
905 | self:_trigger('changed', self, self._value)
906 |
907 | return self
908 | end,
909 |
910 | _update = function (self, theme, delta, dx, dy, event)
911 | -- Ignore if invisible.
912 | if not self.visibility then
913 | return
914 | end
915 |
916 | -- Get the offset x, y, which is calculated by this widget's size and its anchor.
917 | local ox, oy = self:offset()
918 | -- Get the position x, y in local space.
919 | local px, py = self:position()
920 | -- Calculate the final position with the delta position, offset and local position,
921 | -- where the delta position (`dx`, `dy`) is from this widget's parent, or 0, 0 for root widget.
922 | local x, y = dx + px + ox, dy + py + oy
923 | -- Get the size width, height of this widget.
924 | local w, h = self:size()
925 |
926 | -- The following code detects clicking.
927 | local down = false
928 | if event.context.active and event.context.active ~= self then
929 | self._pressed = false
930 | elseif event.canceled or event.context.dragging then
931 | event.context.active = nil
932 | self._pressed = false
933 | elseif self._pressed then
934 | down = event.mouseDown
935 | else
936 | -- Intersection detection.
937 | down = event.mouseDown and Math.intersects(event.mousePosition, Rect.byXYWH(x, y, w, h))
938 | end
939 | if down and not self._pressed then
940 | event.context.active = self
941 | self._pressed = true
942 | elseif not down and self._pressed then
943 | event.context.active = nil
944 | self._pressed = false
945 | event.context.focus = self
946 | self:_trigger('clicked', self) -- Trigger 'clicked' event by clicking.
947 | elseif event.context.focus == self and event.context.navigated == 'press' then
948 | self:_trigger('clicked', self) -- Trigger 'clicked' event by key navigation.
949 | event.context.navigated = false
950 | end
951 |
952 | -- Draw the widget.
953 | local elem = down and theme['button_down'] or theme['button'] -- Using the button theme.
954 | beUtils.tex9Grid(elem, x, y, w, h, nil, self.transparency, nil) -- Draw texture.
955 | beUtils.textCenter(self._value, theme['font'], x, y, w, h, elem.content_offset, self.transparency) -- Draw text.
956 |
957 | -- Call base update to update its children.
958 | beWidget.Widget._update(self, theme, delta, dx, dy, event)
959 | end
960 | }, beWidget.Widget)
961 | ```
962 |
963 |
964 |
965 | ## 7. Theme
966 |
967 |
968 | Theme
969 |
970 | Defined in "src/libs/beGUI/beTheme.lua". Widget classes will lookup for image resources, client area, content offset, fonts, colors and all other appearance preferences from it.
971 |
972 |
973 |
974 | ## 8. Tweening
975 |
976 |
977 | Tweening
978 |
979 | beGUI is integrated with a tweening lib adapted from [kikito/tween.lua](https://github.com/kikito/tween.lua), which allows to create tweening animations.
980 |
981 | ### beGUI.Tween
982 |
983 | **Model: `require 'libs/beGUI/beGUI'`**
984 |
985 | * beGUI.`Tween.new(duration, subject, target, easing, loop)`: constructs a `Tween` object
986 | * `duration`: the duration in seconds
987 | * `subject`: the tweening subject
988 | * `target`: the tweening target
989 | * `easing`: the easing function
990 | * `loop`: whether to loop the tweening
991 |
992 | * `tween:reset()`: resets the `Tween` object
993 | * returns `self`
994 | * `tween:set(clock)`: sets the `Tween` object to a specific clock point
995 | * `clock`: the click time point
996 | * returns `true` for success, otherwise `false`
997 | * `tween:update(delta)`: updates the `Tween` object with a specific delta time in seconds
998 | * returns `true` for success, otherwise `false`
999 |
1000 | * `tween:on('changed', function (sender) end)`: registers an event which will be triggered when the `Tween` has been updated
1001 | * returns `self`
1002 | * `tween:on('completed', function (sender) end)`: registers an event which will be triggered when the `Tween` has completed or looped
1003 | * returns `self`
1004 | * `tween:off(event)`: unregisters the handlers of the specific event
1005 | * `event`: event name string
1006 | * returns `self`
1007 |
1008 |
1009 |
1010 | # License
1011 |
1012 | beGUI is distributed under the MIT license.
1013 |
--------------------------------------------------------------------------------