Compare commits

..

221 Commits

Author SHA1 Message Date
Gabriel Tofvesson
4ba1932138 Propagate keyboard color changes properly 2025-05-25 17:05:31 +02:00
Gabriel Tofvesson
3282023edd Implement on-screen keyboard element 2025-05-25 16:35:52 +02:00
Gabriel Tofvesson
8d3b351083 Simplify attaching multiple chests after initializing storage 2024-12-07 02:05:38 +01:00
Gabriel Tofvesson
6b78404821 Fix path separator 2024-12-04 22:52:19 +01:00
Gabriel Tofvesson
0ff69b53f6 Implement post-deploy execution from latest version of reload script 2024-12-04 22:51:03 +01:00
Gabriel Tofvesson
4997a94cfb Fix variable naming 2024-12-04 22:31:28 +01:00
Gabriel Tofvesson
8a4e99aa13 Implement workaround for illegal nil-return value from inventory.getItemLimit 2024-12-04 20:53:56 +01:00
Gabriel Tofvesson
cd80a9af22 Fix bug in object equality check 2024-12-04 17:54:53 +01:00
Gabriel Tofvesson
1d4500a4d4 Move down 3 blocks instead of 2 2024-12-03 23:17:57 +01:00
Gabriel Tofvesson
f6b76a522f Fix nil check assertions 2024-12-03 23:10:09 +01:00
Gabriel Tofvesson
408fed1ad2 Dig down twice after a panel 2024-12-03 23:00:39 +01:00
Gabriel Tofvesson
d80ff4e679 Add more options for placing chests 2024-12-03 22:58:38 +01:00
Gabriel Tofvesson
147dc40365 Try to enforce better encapsulation with getters 2024-12-03 22:36:23 +01:00
Gabriel Tofvesson
7dca84b128 Don't use chest slot as fuel 2024-12-03 22:04:52 +01:00
Gabriel Tofvesson
65f6555f44 Select chest slot 2024-12-01 05:04:52 +01:00
Gabriel Tofvesson
b10bc73833 Fix quarry boundary conditions 2024-12-01 04:37:58 +01:00
Gabriel Tofvesson
9177394fd0 Implement quarry command 2024-12-01 04:09:55 +01:00
Gabriel Tofvesson
8db86ee45c Remove debug statements 2024-11-30 00:20:21 +01:00
Gabriel Tofvesson
3d293d3e68 Implement default debugger REPL loop 2024-11-30 00:20:10 +01:00
Gabriel Tofvesson
98018e4a8c Remove incorrect nil-assignment for maxCount 2024-11-30 00:11:46 +01:00
Gabriel Tofvesson
c0d65f7f38 Update debugger 2024-11-30 00:00:18 +01:00
Gabriel Tofvesson
739149d852 Add better error handling for debugger 2024-11-30 00:00:05 +01:00
Gabriel Tofvesson
4c2e732607 Repeat debug calls in failure 2024-11-29 23:40:24 +01:00
Gabriel Tofvesson
c053a792a4 Retry debug line load 2024-11-29 23:38:47 +01:00
Gabriel Tofvesson
3f372d4674 Implement debug context args 2024-11-28 23:11:28 +01:00
Gabriel Tofvesson
e98014d29b Add locals to debug context 2024-11-28 23:11:18 +01:00
Gabriel Tofvesson
686c1e5808 Hook debugger on error condition 2024-11-28 22:50:58 +01:00
Gabriel Tofvesson
0ff986a4f2 Attach debugger on nil return value 2024-11-28 22:42:39 +01:00
Gabriel Tofvesson
407082dd2c Implement debugger hook 2024-11-28 22:41:06 +01:00
Gabriel Tofvesson
226190b1f2 Fix repeat-print of indexed values 2024-11-28 01:43:25 +01:00
Gabriel Tofvesson
cd73609a2e Catch all return values from pcall 2024-11-28 00:32:41 +01:00
Gabriel Tofvesson
30b49215a6 Fix reload script 2024-11-28 00:32:21 +01:00
Gabriel Tofvesson
91583b1ca2 Add logging statement for item transfer bug 2024-11-28 00:23:03 +01:00
Gabriel Tofvesson
a2f8f000db Remove TRACE logs 2024-11-27 23:49:58 +01:00
Gabriel Tofvesson
e62ab1d4f8 Fix bug from merge conflict 2024-11-27 23:47:07 +01:00
Gabriel Tofvesson
8533d837cd Add simple reload script for itemcontroller 2024-11-27 23:44:38 +01:00
Gabriel Tofvesson
71406dfe11 Trace itemStack nil countDelta value 2024-11-27 23:39:51 +01:00
Gabriel Tofvesson
3d01e66364 Rename graphics buffer 2024-11-22 01:17:57 +01:00
Gabriel Tofvesson
609013a9fa Specify loglevel by sink 2024-11-22 01:17:32 +01:00
Gabriel Tofvesson
b0e5877417 Implement buffered windows to support transparancy 2024-11-19 18:57:36 +01:00
Gabriel Tofvesson
86c23b8433 Add bounds check to utils module 2024-11-19 18:57:11 +01:00
4175ea2c8c Call getWidth on instance 2024-11-14 04:05:22 +00:00
7837ed573a Enter key clears search filter 2024-11-14 03:47:12 +00:00
3ef0bd371d Add more request buttons 2024-11-14 03:37:09 +00:00
d45d2acd95 Bind click listeners for request buttons 2024-11-14 03:35:05 +00:00
7f1f2eeae3 Increase increment/decrement buttons 2024-11-14 03:33:58 +00:00
f2b6f737da Only refresh if filter is not empty 2024-11-14 03:29:27 +00:00
da30f475c9 Unpack event object when it is not a click event 2024-11-14 03:25:24 +00:00
dff74ce8b4 Add key event handler for keyboard 2024-11-14 03:11:06 +00:00
fc121adcb8 Add more event handling 2024-11-14 03:05:28 +00:00
5eafe25b1a Add char event utils 2024-11-14 03:02:31 +00:00
1456dc332f Use terminal as default display 2024-11-14 02:41:13 +00:00
2f72ef4904 Add key click handler to Element 2024-11-14 02:38:49 +00:00
14153522b6 Get key event values 2024-11-14 02:35:07 +00:00
f3f600892d Add util function for key events 2024-11-14 02:32:27 +00:00
Gabriel Tofvesson
d294355a73 Show used space instead of empty 2024-10-26 19:55:36 +02:00
Gabriel Tofvesson
a0a7c96093 Call parent MT of Text 2024-10-26 19:50:13 +02:00
Gabriel Tofvesson
b5fd24740b Fix LogPlain parsing 2024-10-26 19:45:58 +02:00
Gabriel Tofvesson
569a4370c0 Fix blit implementation 2024-10-26 19:41:12 +02:00
Gabriel Tofvesson
6a4d2d0375 Simplify Text implementation 2024-10-26 19:26:25 +02:00
Gabriel Tofvesson
f24d33eb77 Implement text on storage progress bar 2024-10-26 19:22:35 +02:00
Gabriel Tofvesson
7430c24798 Implement text-progress bar 2024-10-26 01:49:19 +02:00
Gabriel Tofvesson
c826598ea0 Fix keyboard click handling 2024-10-26 01:16:16 +02:00
Gabriel Tofvesson
9dbc45e96e Update filter text color based on result 2024-10-26 01:10:05 +02:00
Gabriel Tofvesson
457d6d775c Offset is absolute 2024-10-26 01:03:14 +02:00
Gabriel Tofvesson
0f18f97cfa Align filter text to end of text 2024-10-26 01:00:53 +02:00
Gabriel Tofvesson
f759cfe229 Prevent click 'transparency' 2024-10-26 00:46:05 +02:00
Gabriel Tofvesson
27d1bf5a31 Fix colors 2024-10-26 00:41:54 +02:00
Gabriel Tofvesson
421c9b04b9 Update item list display 2024-10-26 00:40:02 +02:00
Gabriel Tofvesson
3e69183f78 Add vpad to keyboard 2024-10-26 00:38:29 +02:00
Gabriel Tofvesson
da7bbe21c2 Fix possible race-condition 2024-10-26 00:31:47 +02:00
Gabriel Tofvesson
3e654df2ac Fix hpad issue in keyboard 2024-10-26 00:28:18 +02:00
Gabriel Tofvesson
3ed702a1b5 Fix typo 2024-10-26 00:26:26 +02:00
Gabriel Tofvesson
abbe22f949 Set filter text line colors 2024-10-26 00:23:52 +02:00
Gabriel Tofvesson
b219cebb32 Update keyboard background color 2024-10-26 00:14:47 +02:00
Gabriel Tofvesson
cbb6f57daf Update keyboard background color 2024-10-26 00:13:59 +02:00
Gabriel Tofvesson
f533382022 Fix filter text display 2024-10-26 00:11:04 +02:00
Gabriel Tofvesson
e788002b37 Fix keyboard padding issue 2024-10-25 23:51:41 +02:00
Gabriel Tofvesson
45112a5d66 Search by display name too 2024-10-25 19:08:29 +02:00
Gabriel Tofvesson
798c3e4993 Fix item name search filter 2024-10-25 19:05:37 +02:00
Gabriel Tofvesson
a0a5829d84 Fix handling of inverting flags 2024-10-25 19:03:26 +02:00
Gabriel Tofvesson
0b31ec58da Invert sense of count sort 2024-10-25 18:37:15 +02:00
Gabriel Tofvesson
e86c43ac72 Fix list element sizing 2024-10-25 18:33:31 +02:00
Gabriel Tofvesson
0d096aecde Fix text trimming 2024-10-25 18:25:16 +02:00
Gabriel Tofvesson
9c4605cda2 Fix tab action button id generation 2024-10-25 18:21:54 +02:00
Gabriel Tofvesson
726667399f Fix ID names for tab nav buttons 2024-10-25 18:18:52 +02:00
Gabriel Tofvesson
e193427e4c Fix reverse indexed iterations 2024-10-16 00:41:04 +02:00
Gabriel Tofvesson
ccb2134c2f Handle child events in reverse order 2024-10-15 04:06:24 +02:00
Gabriel Tofvesson
a0b995c288 Style keyboard elements 2024-10-15 04:02:14 +02:00
Gabriel Tofvesson
c4493e349a Toggle keyboard visibility 2024-10-15 03:44:59 +02:00
Gabriel Tofvesson
4bfe62633d Use rolling log file 2024-10-15 03:35:03 +02:00
Gabriel Tofvesson
73aa371813 Fix Prop application order 2024-10-14 16:36:19 +02:00
Gabriel Tofvesson
7c122d6e29 Initialize logging first 2024-10-14 16:16:32 +02:00
Gabriel Tofvesson
dd0d215b1c Re-index log levels 2024-10-14 15:09:41 +02:00
Gabriel Tofvesson
29adfecea6 Fix table formatting 2024-10-14 14:05:23 +02:00
Gabriel Tofvesson
f185e40f49 Fix odd persistent global state bug 2024-10-14 14:03:51 +02:00
Gabriel Tofvesson
6bb6c0e822 Fix global reference 2024-10-14 13:44:38 +02:00
Gabriel Tofvesson
ce0f82c778 Remove metatable info from logging 2024-10-14 13:25:12 +02:00
Gabriel Tofvesson
0d47cfcf41 Track LogPlain in clone 2024-10-14 13:17:57 +02:00
Gabriel Tofvesson
9185d1dad4 Fix recursive tracking 2024-10-14 13:03:44 +02:00
Gabriel Tofvesson
053b59cf00 Fix recursive tracking 2024-10-14 12:59:37 +02:00
Gabriel Tofvesson
5b11cb12f5 use pop() instead of remove() 2024-10-14 12:52:42 +02:00
Gabriel Tofvesson
2e9eefb240 Plain log element data 2024-10-14 12:50:38 +02:00
Gabriel Tofvesson
b9ac063a35 Improve formatting options for Logger 2024-10-14 12:49:41 +02:00
Gabriel Tofvesson
f5ea075a37 Simplify logging more 2024-10-14 11:35:33 +02:00
Gabriel Tofvesson
a8652adafe Remove very excessive logs 2024-10-14 11:33:24 +02:00
Gabriel Tofvesson
33e0e54477 Fix substring indexing 2024-10-14 11:27:59 +02:00
Gabriel Tofvesson
3e5e46cb74 Ignore global variables 2024-10-14 06:44:02 +02:00
Gabriel Tofvesson
6f7775f602 Fix stringify spacing 2024-10-14 06:06:07 +02:00
Gabriel Tofvesson
3496346583 Print newlines to log file 2024-10-14 06:00:26 +02:00
Gabriel Tofvesson
3c339a372a Empty sentinel list for root cloneNonRecursive 2024-10-14 05:59:29 +02:00
Gabriel Tofvesson
ba057b20c6 Implement logging 2024-10-14 05:54:26 +02:00
Gabriel Tofvesson
cedf602e89 Implement logging 2024-10-14 05:53:11 +02:00
Gabriel Tofvesson
ea4de6e988 Allow Element._reload in Children prop 2024-10-13 17:42:24 +02:00
Gabriel Tofvesson
86f378b5f4 Clean up code 2024-10-13 17:39:50 +02:00
Gabriel Tofvesson
3943bbcc19 Fix Children reload order 2024-10-13 16:53:58 +02:00
Gabriel Tofvesson
86e251152c Fix Children reload order 2024-10-13 16:52:59 +02:00
Gabriel Tofvesson
0d8c7ab16b Nil guard for window visibility 2024-10-13 16:07:12 +02:00
Gabriel Tofvesson
9ba0ada978 Simplify lists and visibility 2024-10-13 16:05:58 +02:00
Gabriel Tofvesson
ab65943ee0 Base bottomBar width on state max width 2024-10-13 16:04:14 +02:00
Gabriel Tofvesson
6f606d3758 Set minimum height of 1 for padding 2024-10-13 00:17:02 +02:00
Gabriel Tofvesson
0574e0ab4b Fix group list padding issue 2024-10-13 00:08:14 +02:00
Gabriel Tofvesson
343aa69a46 Fix group list padding issue 2024-10-13 00:03:49 +02:00
Gabriel Tofvesson
04be409a29 Fix action spacing 2024-10-12 23:56:27 +02:00
Gabriel Tofvesson
c18ee0e1f8 Decrease nav button count 2024-10-12 23:41:09 +02:00
Gabriel Tofvesson
c51a46135a Set item group list colors 2024-10-12 23:38:36 +02:00
Gabriel Tofvesson
97b29a30a5 Reload list on reload state 2024-10-12 23:36:55 +02:00
Gabriel Tofvesson
3a7b2cd252 Ignore events for invisible elements 2024-10-12 23:28:47 +02:00
Gabriel Tofvesson
17a6d0f9fa Set visibility for keyboard 2024-10-12 23:26:27 +02:00
Gabriel Tofvesson
22301dfe77 Fix item group rendering 2024-10-12 23:24:41 +02:00
Gabriel Tofvesson
6777df634d Fix sort mode overflow 2024-10-12 23:21:29 +02:00
Gabriel Tofvesson
8ed9e48dfe Include sortByType and inverses in sorting types 2024-10-12 23:15:30 +02:00
Gabriel Tofvesson
70c55a42f0 Fix state mismanagement 2024-10-12 23:07:15 +02:00
Gabriel Tofvesson
27f87b4bae Reload Container children 2024-10-12 23:04:26 +02:00
Gabriel Tofvesson
f7754f4609 Reload root on renderDefault 2024-10-12 20:40:02 +02:00
Gabriel Tofvesson
f79b615ff9 Test padding values 2024-10-12 20:27:14 +02:00
Gabriel Tofvesson
f8121a0727 Use proper element array 2024-10-12 20:18:32 +02:00
Gabriel Tofvesson
d606eccc15 Use proper setPos syntax 2024-10-12 20:13:12 +02:00
Gabriel Tofvesson
a07a801cab Removed commented code 2024-10-12 19:49:54 +02:00
Gabriel Tofvesson
e9a68f4614 Pad keyboard horizontal borders 2024-10-12 18:17:31 +02:00
Gabriel Tofvesson
b12510a4ac Fix sorting by missing damage 2024-10-12 18:04:41 +02:00
Gabriel Tofvesson
ad81301ef1 Use proper state for filter 2024-10-12 18:02:09 +02:00
Gabriel Tofvesson
5f8cf98b8a Read keyboard line from field 2024-10-12 17:59:38 +02:00
Gabriel Tofvesson
2afa27bdac Add missing element field 2024-10-12 17:58:01 +02:00
Gabriel Tofvesson
fdfa8d21bf Redesign main menu 2024-10-12 17:56:16 +02:00
Gabriel Tofvesson
220db8a867 Remove faulty _addStack call 2024-10-12 10:12:11 +02:00
Gabriel Tofvesson
0e86863c34 Rescan access node before transaction 2024-10-12 09:56:27 +02:00
Gabriel Tofvesson
2e1f54c56f Limit stack transfer properly 2024-10-12 08:27:14 +02:00
Gabriel Tofvesson
3a6cb08707 Limit stack transfer 2024-10-12 08:17:14 +02:00
Gabriel Tofvesson
40213b6b18 Rescan access node after transfer 2024-10-12 08:10:09 +02:00
Gabriel Tofvesson
a938ed6133 Limit stack group transfer count 2024-10-12 07:57:11 +02:00
Gabriel Tofvesson
e05cc0594e Limit transfer count 2024-10-12 07:53:15 +02:00
Gabriel Tofvesson
b3fdbb898b Fix state management 2024-10-12 07:51:58 +02:00
Gabriel Tofvesson
1c2f50a3e0 Fix button args 2024-10-12 07:43:52 +02:00
Gabriel Tofvesson
9e2e36bcd9 Fix call to getCount 2024-10-12 07:42:24 +02:00
Gabriel Tofvesson
7fa0861084 Place buttons directly in wrapper 2024-10-12 07:40:37 +02:00
Gabriel Tofvesson
6112a3561b Fix positioning 2024-10-12 07:38:51 +02:00
Gabriel Tofvesson
fd25c3ee19 Fix Sentinel tagging 2024-10-12 07:37:44 +02:00
Gabriel Tofvesson
40728b4860 Fix Sentinel tagging 2024-10-12 07:37:07 +02:00
Gabriel Tofvesson
40cf649c16 Implement itemstack fetching 2024-10-12 07:35:45 +02:00
Gabriel Tofvesson
3a46e08f37 Allow lenient Container dimensions 2024-10-12 07:35:29 +02:00
Gabriel Tofvesson
9ac62319fd Implement stack group transfers 2024-10-12 07:34:57 +02:00
Gabriel Tofvesson
43bfca07c6 Pad progress bar 2024-10-12 05:40:44 +02:00
Gabriel Tofvesson
4ee78dd7c2 Remove ugly padding 2024-10-12 05:38:17 +02:00
Gabriel Tofvesson
bd2f688914 Fix padding 2024-10-12 05:35:46 +02:00
Gabriel Tofvesson
d623d338d7 Move identifier to correct element 2024-10-12 05:34:49 +02:00
Gabriel Tofvesson
065940ff2c Fix rendering issues in children 2024-10-12 05:33:04 +02:00
Gabriel Tofvesson
8483ffeaaf Always call draw for children 2024-10-12 05:30:20 +02:00
Gabriel Tofvesson
059f9f2938 Find list by id 2024-10-12 05:25:47 +02:00
Gabriel Tofvesson
ee1ba9585a Reload correct list 2024-10-12 05:23:33 +02:00
Gabriel Tofvesson
c084dc55aa Adjust positions after element update 2024-10-12 05:22:21 +02:00
Gabriel Tofvesson
df3810cce7 Implement more complex state-reset conditions 2024-10-12 05:08:19 +02:00
Gabriel Tofvesson
5da99116ce Fix bounds check on request buttons 2024-10-12 04:59:04 +02:00
Gabriel Tofvesson
2e622295b8 Simplify layout setup 2024-10-12 04:52:40 +02:00
Gabriel Tofvesson
82e0044ba0 Invalidate root on GUI update 2024-10-12 04:46:43 +02:00
Gabriel Tofvesson
f6a6357c48 Set orientation for root element in REQUEST 2024-10-12 04:40:13 +02:00
Gabriel Tofvesson
fe7ed25d74 Add request buttons to graphics tree 2024-10-12 04:18:13 +02:00
Gabriel Tofvesson
bb5f246687 Add default return values for Padding params 2024-10-12 04:17:57 +02:00
Gabriel Tofvesson
26ecd2a90d Remove extra braces around elements 2024-10-12 04:14:23 +02:00
Gabriel Tofvesson
c2996cf1ff Add request increment/decrement 2024-10-12 04:05:57 +02:00
Gabriel Tofvesson
dd80c013c8 Reposition child on padding resize 2024-10-12 01:31:41 +02:00
Gabriel Tofvesson
8197d0a6f6 Test click event limits 2024-10-12 01:28:46 +02:00
Gabriel Tofvesson
05116679d2 Allow resizing padding 2024-10-12 01:28:37 +02:00
Gabriel Tofvesson
233d3119e5 Fix button offset 2024-10-12 01:05:08 +02:00
Gabriel Tofvesson
c01b258e51 Return proper value from click handler 2024-10-12 01:04:38 +02:00
Gabriel Tofvesson
4425e1b0c7 Add request page button to item detail 2024-10-12 01:02:57 +02:00
Gabriel Tofvesson
3095b399b2 Use plural for text label 2024-10-12 00:52:22 +02:00
Gabriel Tofvesson
6bbe23983d Set nice background colors for item details 2024-10-12 00:51:28 +02:00
Gabriel Tofvesson
145bc90eae Redesign simple item info 2024-10-12 00:48:31 +02:00
Gabriel Tofvesson
1a63a7054f Handle events in children before parents 2024-10-12 00:41:26 +02:00
Gabriel Tofvesson
63e1e54462 Center item detail title 2024-10-12 00:37:11 +02:00
Gabriel Tofvesson
2b4baf7b76 Fix issue where max-durability items report 0 damage 2024-10-12 00:26:02 +02:00
Gabriel Tofvesson
4acde58d5c Fix resize call for horizontal inactive field 2024-10-12 00:17:03 +02:00
Gabriel Tofvesson
7cd370e8ef Fix setX -> getX 2024-10-12 00:15:39 +02:00
Gabriel Tofvesson
0bd63f6804 Set default Progress orientation to HORIZONTAL 2024-10-12 00:11:39 +02:00
Gabriel Tofvesson
7248f777a0 Set reasonable Progress color defaults 2024-10-11 23:51:07 +02:00
Gabriel Tofvesson
685303e4e5 Use bgColor instead of fgColor 2024-10-11 23:48:59 +02:00
Gabriel Tofvesson
0a8209d3fe Guard window color assignments in element setters 2024-10-11 23:47:43 +02:00
Gabriel Tofvesson
b816a81f3b Apply colors to children of Progress 2024-10-11 23:45:37 +02:00
Gabriel Tofvesson
99c9158488 Propogate setDirty for Progress children 2024-10-11 23:39:25 +02:00
Gabriel Tofvesson
3ab4c97fae Define size for Progress 2024-10-11 23:30:35 +02:00
Gabriel Tofvesson
a483a319e6 Pass args to resize() by name 2024-10-11 23:27:02 +02:00
Gabriel Tofvesson
b9cb08bff5 Fix removed function calls 2024-10-11 23:19:23 +02:00
Gabriel Tofvesson
658b69dbf8 Fix orientation prop in Progress 2024-10-11 23:18:19 +02:00
Gabriel Tofvesson
4071e380b1 Conditionally display item damage in detail 2024-10-11 23:15:57 +02:00
Gabriel Tofvesson
bad85564b1 Add damage getters to ItemGroup 2024-10-11 23:15:47 +02:00
Gabriel Tofvesson
fece7532c2 Pad info list 2024-10-11 23:05:25 +02:00
Gabriel Tofvesson
31f53ef521 Add more status info to details page 2024-10-11 22:49:03 +02:00
Gabriel Tofvesson
f6c9801635 Add constants for orientations 2024-10-11 22:48:52 +02:00
Gabriel Tofvesson
25a998a542 Fix orientation bug 2024-10-11 22:01:33 +02:00
Gabriel Tofvesson
2b24904c6f Iterate over children as normal 2024-10-09 23:18:37 +02:00
Gabriel Tofvesson
dd04c0c449 Just use default ipairs 2024-10-09 23:14:08 +02:00
Gabriel Tofvesson
705ad20481 Fix child iterator 2024-10-09 23:12:09 +02:00
Gabriel Tofvesson
655ac6f4b2 Index children by UID 2024-10-09 06:36:18 +02:00
Gabriel Tofvesson
3960eb0cd2 Specify unique identifier for props 2024-10-09 06:35:54 +02:00
Gabriel Tofvesson
fe5f08f62f Fix iterator nil deref 2024-10-09 06:22:39 +02:00
Gabriel Tofvesson
f06a979536 Place text in freeform container 2024-10-09 06:21:07 +02:00
Gabriel Tofvesson
3bb7a69577 Lift common functionality to prop 2024-10-09 06:17:23 +02:00
Gabriel Tofvesson
a96102dc48 Implement freeform container 2024-10-09 06:00:32 +02:00
Gabriel Tofvesson
bbc528988f Implement progress bar 2024-10-09 06:00:02 +02:00
Gabriel Tofvesson
4721410240 Implement abstract element properties 2024-10-09 05:59:45 +02:00
25 changed files with 2803 additions and 457 deletions

60
debugger.lua Normal file
View File

@@ -0,0 +1,60 @@
local function copyObject(o)
local obj = {}
for k,v in pairs(o) do
obj[k] = v
end
setmetatable(obj, getmetatable(o))
return obj
end
local function execDebug(env)
local input = io.input()
local result = { pcall(input.read, input, "*l") }
if not result[1] then
return false, result[2]
end
local func = load("return " .. result[2], "debug", "bt", env)
if type(func) ~= "function" then
func = load(result[2], "debug", "bt", env)
if type(func) ~= "function" then
return false, func
end
end
return pcall(func)
end
local function debugREPL(onResult, debugArgs)
local globalEnv = copyObject(_ENV)
globalEnv._debug = debugArgs
local shouldExit = false
globalEnv.exit = function()
shouldExit = true
end
local result, retval
repeat
result, retval = execDebug(globalEnv)
if result and type(onResult) == "function" then
onResult(retval)
end
until shouldExit or not result
local success = result and shouldExit
return success, (not success and retval) or nil
end
local function hookDebugger(context, Logger)
Logger = Logger or require("logging").getGlobalLogger()
local result, retval
repeat
result, retval = debugREPL(function(r)
Logger:error("->", r)
end, context)
if not result then
Logger:error("x>", retval)
end
until result
end
return { debugREPL = debugREPL, execDebug = execDebug, hookDebugger = hookDebugger }

41
gfx/container.lua Normal file
View File

@@ -0,0 +1,41 @@
local Prop = require("gfx.prop")
local Children = require("gfx.prop.children")
local Element = require("gfx.element")
local Container = Element:new()
function Container:getHeight()
if self:isStrict() then
return Element.getHeight(self)
end
local max = 0
for _,child in self:_iterateChildren() do
max = math.max(max, child:getY() + child:getHeight())
end
return max
end
function Container:getWidth()
if self:isStrict() then
return Element.getWidth(self)
end
local max = 0
for _,child in self:_iterateChildren() do
max = math.max(max, child:getX() + child:getWidth())
end
return max
end
function Container:isStrict()
return self.strict == true
end
function Container:setStrict(strict)
local needReload = (not not self.strict) ~= strict
self.strict = strict
if needReload then
self:_reload()
end
end
return Prop.attach(Container, Children)

View File

@@ -1,4 +1,5 @@
local Event = require("gfx.event")
local Logger = require("logging").getGlobalLogger()
local Element = {
x = 1,
@@ -12,7 +13,10 @@ local Element = {
visible = true,
dirty = true,
id = "",
onClick = nil
onClick = nil,
onKey = nil,
onChar = nil,
onEvent = nil
}
Element.__index = Element
@@ -31,6 +35,7 @@ function Element:new(o)
end
function Element:draw()
Logger:trace("draw")
local dirty = self:_isDirty()
if dirty then
local win = self:_getWindow()
@@ -51,52 +56,62 @@ function Element:getY()
end
function Element:setPos(x, y)
Logger:trace("setPos", x, y)
if (x ~= nil and self.x ~= x) or (y ~= nil and self.y ~= y) then
self:setDirty()
self.x = x or self.x
self.y = y or self.y
self:setDirty()
self:_reload()
end
end
function Element:setX(x)
if self.x ~= x then
self:setDirty()
self.x = x
self:setDirty()
self:_reload()
end
end
function Element:setY(y)
if self.y ~= y then
self:setDirty()
self.y = y
self:setDirty()
self:_reload()
end
end
function Element:setParent(parent)
Logger:trace("setParent", self:getId(), parent, self.parent ~= parent)
if self.parent ~= parent then
self:setDirty()
self.parent = parent
self:setDirty()
self:_reload()
end
end
function Element:setFgColor(color)
if color ~= self.fgColor then
self:setDirty()
self.fgColor = color
self:_getWindow().setTextColor(color)
self:setDirty()
local win = self:_getWindow()
if win ~= nil then
win.setTextColor(color)
end
end
end
function Element:setBgColor(color)
if color ~= self.bgColor then
self:setDirty()
self.bgColor = color
self:_getWindow().setBackgroundColor(color)
self:setDirty()
local win = self:_getWindow()
if win ~= nil then
win.setBackgroundColor(color)
end
end
end
@@ -117,7 +132,13 @@ function Element:_setWindow(window)
end
function Element:setVisible(visible)
self:_getWindow().setVisible(visible)
self.visible = visible
self:setDirty()
local win = self:_getWindow()
if win ~= nil then
win.setVisible(visible)
end
end
function Element:_isDirty()
@@ -126,25 +147,25 @@ end
function Element:resize(opts)
if (opts.width ~= nil and self.x ~= opts.width) or (opts.height ~= nil and self.y ~= opts.height) then
self:setDirty()
self.width = opts.width or self.width
self.height = opts.height or self.height
self:setDirty()
self:_reload()
end
end
function Element:setWidth(width)
if width ~= self.width then
self:setDirty()
self.width = width
self:setDirty()
self:_reload()
end
end
function Element:setHeight(height)
if height ~= self.height then
self:setDirty()
self.height = height
self:setDirty()
self:_reload()
end
end
@@ -181,16 +202,18 @@ function Element:getId()
return self.id
end
function Element:redraw()
self:_getWindow().redraw()
end
function Element:handleEvent(evt)
if Event.isClickEvent(evt) and Event.containsClick(self, evt) and self.onClick ~= nil then
local x, y, source = Event.getClickParams(evt)
return not not self.onClick(self, x, y, source)
elseif Event.isKeyEvent(evt) and self.onKey ~= nil then
local key, held = Event.getKeyParams(evt)
return not not self.onKey(self, key, held)
elseif Event.isCharEvent(evt) and self.onChar ~= nil then
local keyChar = Event.getCharValue(evt)
return not not self.onChar(self, keyChar)
end
return false
return self.onEvent ~= nil and self.onEvent(self, evt)
end
function Element:setOnClick(onClick)
@@ -198,11 +221,14 @@ function Element:setOnClick(onClick)
end
function Element:_reload()
Logger:trace("_reload", self:getId(), self:getX(), self:getY(), self:getWidth(), self:getHeight(), self:isVisible(), Logger.plain({bg=self:getBgColor(), fg=self:getFgColor()}))
if self.parent ~= nil then
local win = window.create(self.parent, self:getX(), self:getY(), self:getWidth(), self:getHeight(), self:isVisible())
win.setBackgroundColor(self:getBgColor())
win.setTextColor(self:getFgColor())
win.setVisible(self.visible)
self:_setWindow(win)
self:setDirty(true)
end
end

View File

@@ -1,5 +1,21 @@
local Event = {}
function Event.isKeyEvent(evt, key)
return evt[1] == "key" and (key == nil or evt[2] == key)
end
function Event.getKeyParams(evt)
return evt[2], evt[3]
end
function Event.isCharEvent(evt, char)
return evt[1] == "char" and (char == nil or evt[2] == char)
end
function Event.getCharValue(evt)
return evt[2]
end
function Event.isClickEvent(evt)
return evt[1] == "monitor_touch" or evt[1] == "mouse_click"
end
@@ -12,7 +28,7 @@ function Event.repositionEvent(evt, dX, dY)
if Event.isClickEvent(evt) then
return evt[1], evt[2], evt[3] + dX, evt[4] + dY
else
return evt
return table.unpack(evt)
end
end

232
gfx/keyboard.lua Normal file
View File

@@ -0,0 +1,232 @@
-- On-screen keyboard with hooks for standard key input events
local List = require("gfx.list")
local Padding = require("gfx.padding")
local Text = require("gfx.text")
local Orientation = require("gfx.prop.orientation")
local Children = require("gfx.prop.children")
local DEFAULT_COLOR_BG = colors.gray
local DEFAULT_COLOR_KEY = colors.gray
local DEFAULT_PADDING_H = 1
local DEFAULT_PADDING_V = 1
local HANDLER_IGNORE_CLICK = function() return true end
local ID_KEY_LIST = "$Keyboard$List"
local ID_PADDED_KEY = "$Keyboard$List$Padding"
local Keyboard = Padding:new{
bgColor = DEFAULT_COLOR_BG,
keyColor = DEFAULT_COLOR_KEY,
left = DEFAULT_PADDING_H,
right = DEFAULT_PADDING_H,
top = DEFAULT_PADDING_V,
bottom = DEFAULT_PADDING_V,
onKeyPress = function(key) end,
onBackspace = function() end
}
function Keyboard:new(o)
local template = o or {}
if type(template.layout) == "function" then
template.layout = template.layout()
elseif type(template.layout) ~= "table" then
template.layout = Keyboard.Layout.English()
end
template.colorKey = template.colorKey or DEFAULT_COLOR_KEY
template.keySlop = not not template.keySlop
template.onClick = HANDLER_IGNORE_CLICK
template.onKey = function(_, keyCode, _)
if keyCode == keys.backspace then
template.onBackspace()
return true
elseif keyCode == keys.enter then
template.onKeyPress("\n")
return true
end
return false
end
template.onChar = function(_, charCode)
template.onKeyPress(charCode)
return true
end
template.element = List:new{
bgColor = template.bgColor,
[Orientation:getId()] = Orientation.VERTICAL,
[Children:getId()] = {}
}
local obj = Padding.new(self, template)
self:setLayout(self.layout)
return obj
end
function Keyboard:setLayout(layout)
self.layout = layout
local this = self
local KEY_BACKSPACE = "backspace"
local KEY_ENTER = "enter"
local function charInputKeyList(chars, backspace, enter, spacebar)
local keysEntries = { }
for i=1,#chars do
local key = chars:sub(i, i)
local keyFunc = function()
this.onKeyPress(key)
return true
end
local keySlopFunc = this.keySlop and keyFunc or HANDLER_IGNORE_CLICK
-- ((not backspace) and i == #keys and 0) or 1
table.insert(keysEntries, Padding:new{ onClick = keySlopFunc, bgColor = this.bgColor, right = 1, element = Text:new{
id = key,
text = key,
bgColor = this.colorKey,
onClick = keyFunc
}})
end
if enter then
table.insert(keysEntries, Text:new{
id = KEY_ENTER,
text = "[<]",
bgColor = this.colorKey,
onClick = function()
this.onKeyPress("\n")
return true
end
})
end
if backspace then
table.insert(keysEntries, Text:new{
id = KEY_BACKSPACE,
text = "[x]",
bgColor = this.colorKey,
onClick = function()
this.onBackspace()
return true
end
})
end
if spacebar then
table.insert(keysEntries, Text:new{
id = " ",
text = "[SPACE]",
bgColor = this.colorKey,
onClick = function()
this.onKeyPress(" ")
return true
end
})
end
return List:new{
id = ID_KEY_LIST,
bgColor = this.colorBg,
[Orientation:getId()] = Orientation.HORIZONTAL,
[Children:getId()] = keysEntries
}
end
local keyboardLines = {}
for _,line in ipairs(this.layout) do
local keyLineList = charInputKeyList(line[1], line.backspace, line.enter)
table.insert(keyboardLines, keyLineList)
end
self.element[Children:getId()] = keyboardLines
self:setDirty()
end
function Keyboard:setKeyColor(color)
if color ~= self.keyColor then
self.keyColor = color
self:setDirty()
end
end
function Keyboard:setDirty(fullInvalidate)
local keyList = self:findById(ID_KEY_LIST)
for _,key in pairs(keyList[Children:getId()]) do
if key:getId() == ID_PADDED_KEY then
key:setBgColor(self.bgColor)
key.element:setBgColor(self.keyColor)
else
key:setBgColor(self.keyColor)
end
end
Padding.setDirty(self, fullInvalidate)
end
local function layoutNumbers(layout)
table.insert(layout, 1, { "1234567890" })
return layout
end
local function layoutEnter(rowIndex, layout)
for index,layoutRow in pairs(layout) do
if index == rowIndex then
layoutRow.enter = true
else
layoutRow.enter = nil
end
end
return layout
end
local function layoutBackspace(rowIndex, layout)
for index,layoutRow in pairs(layout) do
if index == rowIndex then
layoutRow.backspace = true
else
layoutRow.backspace = nil
end
end
return layout
end
local function layoutSpacebar(layout)
table.insert(layout, { spacebar = true, "" })
return layout
end
local function appendNumberLayout(cond, layout)
return cond and layoutNumbers(layout) or layout
end
Keyboard.Layout = {
ItemSearch = function()
return layoutBackspace(1, layoutNumbers({
{ "qwertyuiop" },
{ "asdfghjkl" },
{ "zxcvbnm_:" }
}))
end,
English = function(numberRow)
return layoutSpacebar(layoutBackspace(1, appendNumberLayout(numberRow, layoutEnter(2, {
{ "qwertyuiop" },
{ "asdfghjkl" },
{ "zxcvbnm" }
}))))
end,
Swedish = function(numberRow)
return layoutSpacebar(layoutBackspace(1, appendNumberLayout(numberRow, layoutEnter(2, {
{ "qwertyuiopå" },
{ "asdfghjklöä" },
{ "zxcvbnm" }
}))))
end,
Special = function(numberRow)
return layoutSpacebar(layoutBackspace(1, appendNumberLayout(numberRow, {
{ "!\"#¤%&/()=?" },
{ "@£${[]}\\+^" },
{ "§<>|;:,.-_'*" }
})))
end
}
return Keyboard

View File

@@ -1,102 +1,47 @@
-- TODO: Rename to "Column" to better represent functionality
local Event = require("gfx.event")
local Element = require("gfx.element")
local List = Element:new{
children = {},
vertical = false
}
local Prop = require("gfx.prop")
local Orientation = require("gfx.prop.orientation")
local Children = require("gfx.prop.children")
local List = Element:new()
local function adjustPositions(elements, vertical, from)
local newDims = 1
local getDim = vertical and function(e) return e:getHeight() end or function(e) return e:getWidth() end
for i=1,from-1 do
newDims = newDims + getDim(elements[i])
end
function List:adjustPositions(from)
from = from or 1
local vertical = self:isVertical()
local newDims = 1
local getDim = vertical and function(e) return e:getHeight() end or function(e) return e:getWidth() end
local children = self:_children()
for i=1,from-1 do
newDims = newDims + getDim(children[i])
end
local setDim = vertical and function(e, dim) e:setY(dim) end or function(e, dim) e:setX(dim) end
for i=from,#elements do
setDim(elements[i], newDims)
newDims = newDims + getDim(elements[i])
end
local setDim = vertical and function(e, dim) e:setY(dim) end or function(e, dim) e:setX(dim) end
for i=from,#children do
setDim(children[i], newDims)
newDims = newDims + getDim(children[i])
end
end
function List:insertChild(child, atIndex)
local index = math.min(math.max(1, atIndex or #self.children), #self.children)
table.insert(self.children, index, child)
-- Update window references
self:_reload()
self:_addChild(child, atIndex)
end
function List:removeChild(child)
local index
local searchType = type(child)
if searchType == "string" then
for i,v in ipairs(self.children) do
if v:getId() == child then
index = i
break
end
end
return false, nil
elseif searchType == "table" then
for i,v in ipairs(self.children) do
if v == child then
index = i
end
end
return false, nil
else
index = child
end
if index <= 0 or index > #self.children then
return false, nil
end
local removed = table.remove(self.children, index)
self:_reload()
return true, removed
return self:_removeChild(child)
end
function List:isVertical()
return self.vertical
end
function List:isHorizontal()
return not self:isVertical()
end
function List:setDirty(fullInvalidate)
Element.setDirty(self, fullInvalidate)
if fullInvalidate then
for _,child in self.children do
child:setDirty(fullInvalidate)
end
end
end
function List:draw()
local dirty = Element.draw(self)
if dirty then
self:_getWindow().clear()
for _,child in ipairs(self.children) do
child:draw()
end
end
return dirty
end
local function maxOrSum(shouldSum, values, getValue)
local function maxOrSum(shouldSum, iter, getValue)
if shouldSum then
local sum = 0
for _,v in ipairs(values) do
for _,v in iter() do
sum = sum + getValue(v)
end
return sum
else
local max = 0
for _,v in ipairs(values) do
for _,v in iter() do
max = math.max(max, getValue(v))
end
return max
@@ -104,82 +49,18 @@ local function maxOrSum(shouldSum, values, getValue)
end
function List:getHeight()
return maxOrSum(self:isVertical(), self.children, function(e) return e:getHeight() end)
local lSelf = self
return maxOrSum(self:isVertical(), function() return lSelf:_iterateChildren() end, function(e) return e:getHeight() end)
end
function List:getWidth()
return maxOrSum(self:isHorizontal(), self.children, function(e) return e:getWidth() end)
end
function List:findById(id)
local find = Element.findById(self, id)
if find then
return find
end
for _,v in ipairs(self.children) do
local result = v:findById(id)
if result then
return result
end
end
return nil
end
function List:handleEvent(evt)
if Element.handleEvent(self, evt) then
return true
end
local evtLocalCoords = Event.toElementLocalPosition(evt, self)
if Event.isClickEvent(evt) then
-- If click is not inside list bounds, we can safely ignore it
if not Event.containsClick(self, evt) then
return false
end
for _,child in ipairs(self.children) do
if child:handleEvent(evtLocalCoords) then
return true
end
end
return false
else
for _,child in ipairs(self.children) do
if child:handleEvent(evtLocalCoords) then
return true
end
end
return false
end
end
function List:_isDirty()
if Element._isDirty(self) then
return true
end
for _,child in ipairs(self.children) do
if child:_isDirty() then
return true
end
end
return false
local lSelf = self
return maxOrSum(self:isHorizontal(), function() return lSelf:_iterateChildren() end, function(e) return e:getWidth() end)
end
function List:_reload()
Element._reload(self)
-- Reload child windows
local win = self:_getWindow()
for _,child in ipairs(self.children) do
if child:_getWindow() ~= win then
child:setParent(win)
end
end
adjustPositions(self.children, self:isVertical(), 1)
self:adjustPositions()
end
return List
return Prop.attach(Prop.attach(List, Orientation), Children)

View File

@@ -8,13 +8,17 @@ local Padding = Element:new{
element = nil
}
function Padding:_repositionElement()
self.element:setPos(self:getPaddingLeft() + 1, self:getPaddingTop() + 1)
self:resize{
width = self:getWidth(),
height = self:getHeight()
}
end
function Padding:new(opts)
local obj = Element.new(self, opts)
obj.element:setPos(obj:getPaddingLeft() + 1, obj:getPaddingTop() + 1)
obj:resize{
width = obj:getWidth(),
height = obj:getHeight()
}
obj:_repositionElement()
obj.element:setParent(obj:_getWindow())
return obj
end
@@ -45,19 +49,40 @@ function Padding:setDirty(fullInvalidate)
end
function Padding:getPaddingLeft()
return self.left
return self.left or 0
end
function Padding:getPaddingRight()
return self.right
return self.right or 0
end
function Padding:getPaddingTop()
return self.top
return self.top or 0
end
function Padding:getPaddingBottom()
return self.bottom
return self.bottom or 0
end
local function compareSet(value, default, didChange)
return value or default, didChange or (value ~= nil and value ~= default)
end
function Padding:setPadding(opts)
if type(opts) ~= "table" then
return
end
local changed = false
self.left, changed = compareSet(opts.left, self.left, changed)
self.right, changed = compareSet(opts.right, self.right, changed)
self.top, changed = compareSet(opts.top, self.top, changed)
self.bottom, changed = compareSet(opts.bottom, self.bottom, changed)
if changed then
self:_reload()
self:_repositionElement()
end
end
function Padding:getInnerWidth()
@@ -68,6 +93,10 @@ function Padding:getInnerHeight()
return self.element:getHeight()
end
function Padding:getInnerElement()
return self.element
end
function Padding:getWidth()
return self:getInnerWidth() + self:getPaddingLeft() + self:getPaddingRight()
end
@@ -81,16 +110,18 @@ function Padding:findById(id)
end
function Padding:handleEvent(evt)
if Element.handleEvent(self, evt) then
return true
end
local evtLocalCoords = Event.toElementLocalPosition(evt, self)
if Event.isClickEvent(evt) then
return Event.containsClick(self, evt) and self.element:handleEvent(evtLocalCoords)
if Event.containsClick(self, evt) and self.element:handleEvent(evtLocalCoords) then
return true
end
else
return self.element:handleEvent(evtLocalCoords)
if self.element:handleEvent(evtLocalCoords) then
return true
end
end
return Element.handleEvent(self, evt)
end
function Padding:_reload()

116
gfx/progress.lua Normal file
View File

@@ -0,0 +1,116 @@
local Element = require("gfx.element")
local Prop = require("gfx.prop")
local Orientation = require("gfx.prop.orientation")
local BlankElement = Element:new()
function BlankElement:draw()
local dirty = Element.draw(self)
if dirty then
self:_getWindow().clear()
end
end
local function round(value, biasDown)
local decimal = value % 1
return (((not biasDown) and decimal < 0.5) or ((not not biasDown) and decimal <= 0.5)) and math.floor(value) or math.ceil(value)
end
local Progress = Element:new{
progress = 0.0,
active = nil,
inactive = nil
}
function Progress:new(o)
local template = o or {}
template.fgColor = template.fgColor or colors.red
template.bgColor = template.bgColor or colors.gray
local obj = Element.new(self, template)
obj.active = BlankElement:new()
obj.inactive = BlankElement:new()
obj:_refreshColors()
return obj
end
function Progress:setFgColor(color)
self.active:setBgColor(color)
end
function Progress:setBgColor(color)
self.inactive:setBgColor(color)
end
function Progress:_updateProgress()
local width, height = self:getWidth(), self:getHeight()
if self:isVertical() then
self.active:resize{
width = width,
height = round(self:getProgress() * height)
}
self.inactive:resize{
width = width,
height = round((1 - self:getProgress()) * height, true)
}
self.inactive:setY(self.active:getY() + self.active:getHeight())
else
self.active:resize{
width = round(self:getProgress() * width),
height = height
}
self.inactive:resize{
width = round((1 - self:getProgress()) * width, true),
height = height
}
self.inactive:setX(self.active:getX() + self.active:getWidth())
end
self:_refreshColors()
end
function Progress:_refreshColors()
self:setFgColor(self:getFgColor())
self:setBgColor(self:getBgColor())
end
function Progress:setProgress(progress)
self:setDirty()
self.progress = math.min(1, math.max(0, progress))
self:_updateProgress()
end
function Progress:getProgress()
return self.progress
end
function Progress:draw()
local dirty = Element.draw(self)
if dirty then
self.inactive:draw()
self.active:draw()
end
return dirty
end
function Progress:_isDirty()
return Element._isDirty(self) or self.active:_isDirty() or self.inactive:_isDirty()
end
function Progress:setDirty(fullInvalidate)
Element.setDirty(self, fullInvalidate)
self.active:setDirty(self, fullInvalidate)
self.inactive:setDirty(self, fullInvalidate)
end
function Progress:_reload()
Element._reload(self)
-- Reload child windows
local win = self:_getWindow()
self.active:setParent(win)
self.inactive:setParent(win)
self:_updateProgress()
end
return Prop.attach(Progress, Orientation, Orientation.HORIZONTAL)

176
gfx/prop/children.lua Normal file
View File

@@ -0,0 +1,176 @@
local Prop = require("gfx.prop")
local Element = require("gfx.element")
local Event = require("gfx.event")
local Children = Prop:new{ defaultState = {}, uid = "CHILDREN" }
function Children:with(elementType)
local propSelf = self
local defaultReload = elementType._reload
function elementType:_iterateChildren()
return ipairs(self:_children())
end
function elementType:_iterateChildrenReversed()
local children = self:_children()
return function(tbl, idx)
if idx <= 1 then
return nil, nil
end
return idx - 1, tbl[idx - 1]
end, children, #children + 1
end
function elementType:_children()
return propSelf:getState(self)
end
function elementType:childCount()
return #self:_children()
end
function elementType:_childAt(index)
return self:_children()[elementType:_validChildIndex(index)]
end
function elementType:_validChildIndex(from)
return math.max(1, math.min((from or self:childCount()) + 1, self:childCount() + 1))
end
function elementType:_triggerChildChanged(index)
if type(self.onChildrenChanged) == "function" then
self:onChildrenChanged(index)
end
self:_reload()
end
function elementType:_addChild(child, index)
index = self:_validChildIndex(index)
table.insert(propSelf:getState(self), index, child)
self:_triggerChildChanged(index)
end
function elementType:_removeChild(locator)
local index = nil
local locatorType = type(locator)
if locatorType == "table" then
for i,v in self:_iterateChildren() do
if v == locator then
index = i
break
end
end
elseif locatorType == "string" then
for i,v in self:_iterateChildren() do
if v:getId() == locator then
index = i
break
end
end
else--if locatorType == "number" then
index = self:_validChildIndex(locator)
end
-- Child not found
if index == nil then
return false, nil
end
local result = table.remove(propSelf:getState(self), index)
self:_triggerChildChanged(index)
return true, result
end
function elementType:setDirty(fullInvalidate)
Element.setDirty(self, fullInvalidate)
for _,child in self:_iterateChildren() do
child:setDirty(fullInvalidate)
end
end
function elementType:_isDirty()
if Element._isDirty(self) then
return true
end
for _,child in self:_iterateChildren() do
if child:_isDirty() then
return true
end
end
return false
end
function elementType:draw()
local wasDirty = Element._isDirty(self);
local dirty = Element.draw(self)
if wasDirty then
self:_getWindow().clear()
end
if dirty then
for _,child in self:_iterateChildren() do
dirty = child:draw() or dirty
end
end
return dirty
end
function elementType:findById(id)
local find = Element.findById(self, id)
if find then
return find
end
return self:findChildById(id)
end
function elementType:findChildById(id)
for _,child in self:_iterateChildrenReversed() do
local result = child:findById(id)
if result then
return result
end
end
return nil
end
function elementType:handleEvent(evt)
local evtLocalCoords = Event.toElementLocalPosition(evt, self)
if Event.isClickEvent(evt) then
-- If click is not inside list bounds, we can safely ignore it
if not Event.containsClick(self, evt) then
return false
end
for _,child in self:_iterateChildrenReversed() do
if child:handleEvent(evtLocalCoords) then
return true
end
end
else
for _,child in self:_iterateChildrenReversed() do
if child:handleEvent(evtLocalCoords) then
return true
end
end
end
return Element.handleEvent(self, evt)
end
function elementType:_reload()
if defaultReload ~= nil then
defaultReload(self)
end
-- Reload child windows
local win = self:_getWindow()
for _,child in self:_iterateChildren() do
child:setParent(win)
end
end
return elementType
end
return Children

30
gfx/prop/init.lua Normal file
View File

@@ -0,0 +1,30 @@
local Prop = {}
Prop.__index = Prop
function Prop:getState(element)
return element[self:getId()]
end
function Prop:setState(element, state)
element[self:getId()] = state
end
function Prop:getId()
return self.uid
end
function Prop.attach(elementType, prop, defaultState)
prop:setState(elementType, defaultState or prop.defaultState)
return prop:with(elementType)
end
function Prop:new(o)
if o.uid == nil then
error("Property must carry a unique identifier")
end
setmetatable(o, Prop)
o.__index = o
return o
end
return Prop

28
gfx/prop/orientation.lua Normal file
View File

@@ -0,0 +1,28 @@
local Prop = require("gfx.prop")
local Orientation = Prop:new{ defaultState = false, uid = "ORIENTATION" }
function Orientation:with(elementType)
local propSelf = self
function elementType:setVertical(vertical)
local state = self:isVertical()
if state ~= vertical then
self:setDirty()
propSelf:setState(self, vertical)
end
end
function elementType:isVertical()
return propSelf:getState(self)
end
function elementType:isHorizontal()
return not self:isVertical()
end
return elementType
end
Orientation.VERTICAL = true
Orientation.HORIZONTAL = false
return Orientation

0
gfx/render/init.lua Normal file
View File

243
gfx/render/windowbuffer.lua Normal file
View File

@@ -0,0 +1,243 @@
local Util = require("util")
local DEFAULT_C = " "
local DEFAULT_FG = colors.white
local DEFAULT_BG = colors.black
local function ensureC(cVal)
return (cVal ~= nil and #cVal == 1 and cVal) or DEFAULT_C
end
local WindowBuffer = {}
-- Create a shallow wrapper for window object
function WindowBuffer.wrapWindow(w, defaultC, defaultFg, defaultBg, clearColor)
defaultC = ensureC(defaultC)
defaultFg = defaultFg or DEFAULT_FG
defaultBg = defaultBg or DEFAULT_BG
clearColor = clearColor or DEFAULT_BG
local obj = {}
function obj.blit(text, fg, bg)
w.blit(text, fg, bg)
end
function obj.flush() end
function obj.flushPartial() end
function obj.getSize()
return w.getSize()
end
function obj._setPixel(x, y, c, fg, bg)
w.setCursorPos(x, y)
w.setFgColor(fg or defaultFg)
w.setBgColor(bg or defaultBg)
w.write(c or defaultC)
end
function obj.setPixel(x, y, c, fg, bg)
local width, height = obj.getSize()
Util.boundCheck(x, y, width, height)
obj._setPixel(x, y, c, fg, bg)
end
-- Partial writes are not possible for native window access
obj._writePixel = obj._setPixel
obj.writePixel = obj.setPixel
function obj.setCursorPos(x, y)
w.setCursorPos(x, y)
end
function obj.getCursorPos()
return w.getCursorPos()
end
function obj.clear()
w.setBgColor(clearColor)
--w.setFgColor(clearColor) -- Shouldn't matter for native clear
w.clear()
end
return obj
end
function WindowBuffer.new(width, height, clearC, clearFg, clearBg)
clearC = ensureC(clearC)
clearFg = clearFg or DEFAULT_FG
clearBg = clearBg or DEFAULT_BG
local obj = {}
local function setState(o, c, fg, bg)
o.c = c
o.bg = bg
o.fg = fg
return o
end
local function writeState(o, c, fg, bg)
o.c = c or o.c
o.bg = bg or o.bg
o.fg = fg or o.fg
return o
end
local buffer = {}
for i=1,height do
local line = {}
for j=1,width do
line[j] = setState({})
end
buffer[i] = line
end
local pX, pY = 1, 1
function obj.blit(text, fg, bg)
assert(#text == #fg, "Foreground color blit length must match text length")
assert(#text == #bg, "Background color blit length must match text length")
local line = buffer[pY]
local baseX = pX
for i=1,math.min(#text, width - baseX - 1) do
local blitChar = line[i]
blitChar.c = text:sub(i, i)
blitChar.fg = fg:sub(i, i)
blitChar.bg = bg:sub(i, i)
end
pX = pX + #text
end
function obj._setPixel(x, y, c, fg, bg)
setState(buffer[y][x], c, fg, bg)
end
function obj.setPixel(x, y, c, fg, bg)
Util.boundCheck(x, y, width, height)
obj._setPixel(x, y, c, fg, bg)
end
function obj._writePixel(x, y, c, fg, bg)
writeState(buffer[y][x], c, fg, bg)
end
function obj.writePixel(x, y, c, fg, bg)
Util.boundCheck(x, y, width, height)
obj._writePixel(x, y, c, fg, bg)
end
function obj.setCursorPos(x, y)
pX, pY = x, y
end
function obj.getCursorPos()
return pX, pY
end
function obj.clear()
for i=1,height do
local line = buffer[i]
for j=1,width do
setState(line[j], clearC, clearFg, clearBg)
end
end
end
function obj.flush(win, offsetX, offsetY)
for i=1,height do
local line = buffer[i]
for j=1,width do
local entry = line[j]
win._writePixel(j + offsetX, i + offsetY, entry.c, entry.fg, entry.bg)
end
end
end
function obj.flushPartial(win, winOffsetX, winOffsetY, baseX, baseY, flushWidth, flushHeight)
for i=baseY,baseY+flushHeight do
local line = buffer[i]
for j=baseX,baseX+flushWidth do
local entry = line[j]
win._writePixel(j + winOffsetX, i + winOffsetY, entry.c, entry.fg, entry.bg)
end
end
end
return obj
end
function WindowBuffer.shallowChild(windowBuffer, width, height, offsetX, offsetY, clearC, clearFg, clearBg)
-- Assume static for now
local parentWidth, parentHeight = windowBuffer.getSize()
Util.boundCheck(offsetX, offsetY, parentWidth, parentHeight)
Util.boundCheck(offsetX + width, offsetY + height, parentWidth, parentHeight)
clearC = ensureC(clearC)
clearFg = clearFg or DEFAULT_FG
clearBg = clearBg or DEFAULT_BG
local obj = {}
local pX, pY = 1, 1
local function setCursorPos()
windowBuffer.setCursorPos(pX + offsetX, pY + offsetY)
end
function obj.blit(text, fg, bg)
setCursorPos()
windowBuffer.blit(text, fg, bg)
pX = pX + #text
end
function obj._setPixel(c, fg, bg)
setCursorPos()
windowBuffer._setPixel(c, fg, bg)
end
function obj.setPixel(c, fg, bg)
setCursorPos()
windowBuffer.setPixel(c, fg, bg)
end
function obj._writePixel(c, fg, bg)
setCursorPos()
windowBuffer._writePixel(c, fg, bg)
end
function obj.writePixel(c, fg, bg)
setCursorPos()
windowBuffer.writePixel(c, fg, bg)
end
function obj.setCursorPos(x, y)
pX, pY = x, y
end
function obj.getCursorPos()
return pX, pY
end
function obj.getSize()
return width, height
end
function obj.clear()
for i=1,height do
for j=1,width do
windowBuffer._setPixel(j + offsetX, i + offsetY, clearC, clearFg, clearBg)
end
end
end
-- Reconsider implementation?
obj.flush = windowBuffer.flush
obj.flushPartial = windowBuffer.flushPartial
return obj
end
return WindowBuffer

View File

@@ -1,13 +1,6 @@
local Element = require("gfx.element")
local Text = Element:new{ text = "" }
function Text:new(o)
o.width = nil
o.height = nil
return Element.new(self, o)
end
function Text:setText(text)
local current = self:getText()
if current ~= text then

33
gfx/textprogress.lua Normal file
View File

@@ -0,0 +1,33 @@
local Text = require("gfx.text")
local TextProgress = Text:new()
function TextProgress:getProgress()
return math.max(0, math.min(self.progress or 0, 1))
end
function TextProgress:setProgress(progress)
self.progress = progress
self:setDirty()
end
function TextProgress:draw()
getmetatable(getmetatable(getmetatable(self))).draw(self)
local text = self:getText()
if #text == 0 then
return
end
local activePart = math.ceil(#text * self:getProgress())
local fg = { }
local bg = { }
for i=1,#text do
table.insert(fg, colors.toBlit(i > activePart and self:getFgColor() or self:getBgColor()))
table.insert(bg, colors.toBlit(i > activePart and self:getBgColor() or self:getFgColor()))
end
self:_getWindow().blit(text, table.concat(fg), table.concat(bg))
end
return TextProgress

File diff suppressed because it is too large Load Diff

339
logging.lua Normal file
View File

@@ -0,0 +1,339 @@
local Logging = {}
local LogLevel = {
TRACE = 1,
DEBUG = 2,
INFO = 3,
WARNING = 4,
CRITICAL = 5,
ERROR = 6
}
LogLevel._DEFAULT = LogLevel.TRACE
for k,v in pairs(LogLevel) do
LogLevel[v] = k
end
function LogLevel.isValid(value)
return LogLevel[LogLevel[value]] ~= nil
end
function LogLevel.asNumber(value)
return type(value) == "number" and value or LogLevel[value]
end
function LogLevel.isGreaterOrEqual(a, b)
return a >= b
end
local Logger = {
ignoreGlobals = true
}
Logger.__index = Logger
local function combineOutputs(...)
local args = {...}
if #args == 0 then
return function() end
elseif #args == 1 then
return args[1]
end
return function(text, level)
for _,v in ipairs(args) do
v(text, level)
end
end
end
-- TODO: Split logger outputs and specify LogLevel for each
function Logger:new(...)
local logger = { output = combineOutputs(...) or function() end }
setmetatable(logger, self)
logger.__index = logger
return logger
end
function Logger:setIgnoreGlobals(ignoreGlobals)
self.ignoreGlobals = ignoreGlobals
end
local LogPlain = {}
function LogPlain.of(value, deep)
local obj = { value, deep = deep }
setmetatable(obj, LogPlain)
return obj
end
function LogPlain.is(value)
return type(value) == "table" and getmetatable(value) == LogPlain
end
function LogPlain.isDeep(value)
return LogPlain.is(value) and value.deep
end
function LogPlain.getValue(value)
return LogPlain.is(value) and value[1] or value
end
local RecursionSentinel = {}
function RecursionSentinel.make(table, index)
local obj = { table = table, index = index }
setmetatable(obj, RecursionSentinel)
return obj
end
function RecursionSentinel.isSentinel(value)
return type(value) == "table" and getmetatable(value) == RecursionSentinel
end
function RecursionSentinel.isKnown(knownTables, value)
return knownTables[value] ~= nil
end
function RecursionSentinel.getSentinel(knownTables, value)
return knownTables[value] or RecursionSentinel.add(knownTables, value)
end
function RecursionSentinel.add(knownTables, value)
local sentinel = RecursionSentinel.make(value, #knownTables)
knownTables[value] = sentinel
return sentinel
end
function RecursionSentinel.remove(knownTables, value)
local sentinel = knownTables[value]
if sentinel then
sentinel.index = nil
end
knownTables[value] = nil
end
local function cloneNonRecursive(inValue, sentinels)
local value = LogPlain.getValue(inValue)
if type(value) == "table" then
local wrapLogPlain = LogPlain.is(inValue) and function(v) return LogPlain.of(v, inValue.deep) end or function(v) return v end
if RecursionSentinel.isKnown(sentinels, value) then
return wrapLogPlain(RecursionSentinel.getSentinel(sentinels, value))
end
local sentinel = RecursionSentinel.getSentinel(sentinels, value)
local clone = {}
for i,v in ipairs(value) do
clone[i] = cloneNonRecursive(v, sentinels)
end
for k,v in pairs(value) do
clone[cloneNonRecursive(k, sentinels)] = cloneNonRecursive(v, sentinels)
end
RecursionSentinel.remove(sentinels, sentinel.table)
sentinel.value = clone
return wrapLogPlain(sentinel.value)
else
return inValue
end
end
local G_ = {}
for k,v in pairs(_G) do
G_[v] = k
end
local function _simpleStringify(inValue, builder, ignoreGlobals, skipFunctions, plain)
local value = LogPlain.getValue(inValue)
local isPlain = plain or LogPlain.is(inValue)
local isDeep = plain or LogPlain.isDeep(inValue)
if type(value) == "table" then
if not isPlain then
table.insert(builder, "<")
end
if ignoreGlobals and G_[value] ~= nil then
table.insert(builder, "_G.")
table.insert(builder, G_[value])
elseif RecursionSentinel.isSentinel(value) then
if isPlain then
table.insert(builder, "<recurse_")
table.insert(builder, tostring(value.index))
else
table.insert(builder, "recurse ")
table.insert(builder, tostring(value.value))
end
table.insert(builder, ">")
else
if not isPlain then
table.insert(builder, tostring(value))
table.insert(builder, ">")
end
table.insert(builder, "{")
local first = true
for _,v in ipairs(value) do
if not first then
table.insert(builder, ",")
else
first = false
end
_simpleStringify(v, builder, ignoreGlobals, skipFunctions, isDeep)
end
first = #value == 0
local len = #value
for k,v in pairs(value) do
-- Array elements already printed
if type(k) == "number" and k <= len then
goto continue
end
if not first then
table.insert(builder, ",")
else
first = false
end
-- String key values are always plain-printed
_simpleStringify(k, builder, ignoreGlobals, skipFunctions, isDeep or type(k) == "string")
table.insert(builder, "=")
_simpleStringify(v, builder, ignoreGlobals, skipFunctions, isDeep)
::continue::
end
table.insert(builder, "}")
end
elseif type(value) == "string" then
if not isPlain then
table.insert(builder, "\"")
end
table.insert(builder, tostring(value))
if not isPlain then
table.insert(builder, "\"")
end
elseif type(value) == "function" then
if not skipFunctions then
local info = debug.getinfo(value, "u")
if not isPlain then
table.insert(builder, tostring(value))
end
table.insert(builder, "(")
table.insert(builder, tostring(info.nparams))
if info.isvararg then
table.insert(builder, "*")
end
table.insert(builder, ")")
end
elseif not skipFunctions or type(value) ~= "function" then
table.insert(builder, tostring(value))
end
end
function Logging.deepStringify(value)
local collect = {}
_simpleStringify(cloneNonRecursive(value, {}), collect, false, false)
return table.concat(collect)
end
function Logger:_doPrint(level, message, ...)
local result = { tostring(LogLevel[level]), Logging.deepStringify(message) }
for _,v in ipairs({...}) do
table.insert(result, Logging.deepStringify(v))
end
self.output(table.concat(result, " "), level)
end
function Logger:trace(message, ...)
self:_doPrint(LogLevel.TRACE, message, ...)
end
function Logger:debug(message, ...)
self:_doPrint(LogLevel.DEBUG, message, ...)
end
function Logger:info(message, ...)
self:_doPrint(LogLevel.INFO, message, ...)
end
function Logger:warn(message, ...)
self:_doPrint(LogLevel.WARNING, message, ...)
end
function Logger:critical(message, ...)
self:_doPrint(LogLevel.CRITICAL, message, ...)
end
function Logger:error(message, ...)
self:_doPrint(LogLevel.ERROR, message, ...)
end
function Logger:setOutput(...)
self.output = combineOutputs(...)
end
function Logger.plain(value, deep)
return LogPlain.of(value, deep)
end
Logging.Logger = Logger
Logging.LogLevel = LogLevel
function Logging.getGlobalLogger()
if _G._GLOBAL_LOGGER == nil then
_G._GLOBAL_LOGGER = Logger:new{ output = function() end }
end
return _G._GLOBAL_LOGGER
end
function Logging.firstLoad(...)
Logging.resetAll()
local logger = Logging.getGlobalLogger()
logger:setOutput(...)
return logger
end
Logging.OUTPUTS = {}
function Logging.OUTPUTS.file(name, logLevel)
logLevel = logLevel or LogLevel._DEFAULT
local doWrite = function(file, text)
file.write(text)
file.write("\n")
file.flush()
end
local file = fs.open(name, "w+")
return function(text, level)
if level < logLevel then
return
end
if not pcall(doWrite, file, text) then
local dRes, dRet = pcall(fs.delete, name)
if not dRes then
print("Error deleting logfile ("..name.."): "..tostring(dRet))
else
file = fs.open(name, "w+")
local wRes, wRet = pcall(doWrite, file, text)
if not wRes then
print("Error writing to logfile ("..name.."): "..tostring(wRet))
end
end
end
end
end
function Logging.OUTPUTS.transmit(modem, channel, logLevel)
logLevel = logLevel or LogLevel._DEFAULT
end
function Logging.OUTPUTS.stdout(logLevel)
logLevel = logLevel or LogLevel._DEFAULT
return function(text, level)
if level < logLevel then
return
end
print(text)
end
end
function Logging.resetAll()
_G._GLOBAL_LOGGER = nil
end
return Logging

185
quarry.lua Normal file
View File

@@ -0,0 +1,185 @@
local args = {...}
local Logging = require("logging")
local Logger = Logging.firstLoad(
Logging.OUTPUTS.file("tunnel.log"),
Logging.OUTPUTS.stdout()
)
local CHEST_SLOT = #args == 1 and tonumber(args[1]) or 1
if type(CHEST_SLOT) ~= "number" or CHEST_SLOT < 1 or CHEST_SLOT > 16 then
Logger:error("Slot number is not valid:", CHEST_SLOT)
end
local CHEST_PICKUP = #args == 2 and (args[2]:lower() )
local CHEST_DETAIL = turtle.getItemDetail(CHEST_SLOT)
if CHEST_DETAIL == nil then
Logger:error("No chest in slot! Quitting...")
return
end
local CHEST_NAME = CHEST_DETAIL.name
local function refuel(minFuel)
local fuelLevel = turtle.getFuelLevel()
while fuelLevel < minFuel do
Logger:debug("Checking fuel level:", fuelLevel)
for i=1,16 do
if i == CHEST_SLOT then
goto continue
end
if turtle.getItemCount(i) > 0 then
turtle.select(i)
turtle.refuel()
end
::continue::
end
fuelLevel = turtle.getFuelLevel()
end
Logger:debug("Fuel level is sufficient:", fuelLevel)
turtle.select(1)
end
local function isFull(minEmpty)
local emptyCount = 0
for i=1,16 do
if i == CHEST_SLOT then
goto continue
end
if turtle.getItemCount(i) == 0 then
emptyCount = emptyCount + 1
if emptyCount >= minEmpty then
return false
end
end
::continue::
end
return true
end
local function placeChest()
if not turtle.select(CHEST_SLOT) then
Logger:error("Cannot select chest slot", CHEST_SLOT)
return false, nil, nil, nil
end
if turtle.placeUp() or (turtle.digUp() and turtle.placeUp()) then
return true, turtle.dropUp, turtle.digUp, function() end
end
if turtle.turnLeft() and turtle.turnLeft() and (turtle.place() or (turtle.dig() and turtle.place())) then
return true, turtle.drop, turtle.dig, function()
turtle.turnRight()
turtle.turnRight()
end
end
return false, nil, nil, nil
end
local function handleFullInv(minEmpty)
local didPlace = false
local result, drop, dig, onComplete = false, nil, nil, nil
-- Empty inventory
while isFull(minEmpty) do
if not didPlace then
local detail = turtle.getItemDetail(CHEST_SLOT)
if type(detail) ~= "table" or detail.name ~= CHEST_NAME then
Logger:error("Can't find chest :(")
os.sleep(5)
goto continue
end
-- Try: place, check block above is empty or dig it, place
-- If all fails, print error, wait and repeat
result, drop, dig, onComplete = placeChest()
if not result then
Logger:error("Can't place chest :(")
os.sleep(5)
goto continue
end
didPlace = true
end
assert(drop ~= nil, "Placed chest, but drop operation is nil")
for i=1,16 do
if i == CHEST_SLOT then
goto continue_SLOT
end
if turtle.getItemCount(i) > 0 and not (turtle.select(i) and drop()) then
Logger:error("Couldn't drop items into chest!")
goto continue
end
::continue_SLOT::
end
::continue::
end
if result then
assert(dig ~= nil, "Placed chest, but dig operation is nil")
if didPlace and CHEST_PICKUP then
turtle.select(CHEST_SLOT)
dig()
end
assert(onComplete ~= nil, "Placed chest, but onComplete operation is nil")
onComplete()
end
end
local function dig(checkRefuel)
while not turtle.forward() do
turtle.dig()
checkRefuel()
end
turtle.digUp()
turtle.digDown()
end
local function line(length, turn, checkRefuel)
turtle.digDown()
for _=2,length do
dig(checkRefuel)
end
turn()
end
local function panel(width, length, leftFirst, checkRefuel, checkFullInv)
Logger:trace("Panel:", width, length)
local turn, otherTurn = leftFirst and turtle.turnLeft or turtle.turnRight, leftFirst and turtle.turnRight or turtle.turnLeft
for _=2,width do
checkFullInv()
line(length, turn, checkRefuel)
dig(checkRefuel)
turn()
turn, otherTurn = otherTurn, turn
end
line(length, turn, checkRefuel)
turn()
end
local function rectPrism(depth, width, length, leftFirst)
local refuelTarget = width * length * 1.5
local function checkRefuel()
refuel(refuelTarget)
end
local invEmptyTarget = 3
local function checkFullInv()
Logger:debug("Handling full inventory with target:", invEmptyTarget, " handled:", handleFullInv(invEmptyTarget))
end
Logger:trace("RectPrism:", depth, width, length)
for _=1,depth do
panel(width, length, leftFirst, checkRefuel, checkFullInv)
for __=1,3 do
while not turtle.down() do
turtle.digDown()
checkRefuel()
end
end
leftFirst = (not not leftFirst) ~= (width % 2 == 0)
end
end
rectPrism(200, 16, 16)

27
reload.lua Normal file
View File

@@ -0,0 +1,27 @@
local CC_UTILS_DIR = "/cc-utilities"
local function onResume(scriptArgs)
if #scriptArgs > 0 then
scriptArgs[1] = CC_UTILS_DIR .. "/" .. scriptArgs[1]
shell.run(table.unpack(scriptArgs))
end
end
if pcall(debug.getlocal, 4, 1) then
return { onResume = onResume }
end
local result, retval = pcall(fs.delete, CC_UTILS_DIR)
if fs.exists(CC_UTILS_DIR) then
if result then
print("Wat? Delete succeeded but dir persists???")
else
print(retval)
end
return
end
shell.run("clone https://gitea.tofvesson.se/GabrielTofvesson/cc-utilities.git")
local nextVersion = require(CC_UTILS_DIR .. "/reload")
local _ = ((type(nextVersion) == "table" and type(nextVersion.onResume) == "function") and nextVersion.onResume or onResume)({...})

View File

@@ -1,6 +1,7 @@
local ItemStack = require("storage.itemstack")
local Sentinel = require("storage.sentinel")
local Chest = {}
local Chest = Sentinel:tag({}, Sentinel.CHEST)
Chest.__index = Chest
-- Homogeneity allows chest scan to clone empty itemDetail slot to all empty slots in chest

View File

@@ -1,7 +1,13 @@
local Chest = require("storage.chest")
local Storage = {}
local Sentinel = require("storage.sentinel")
local Storage = Sentinel:tag({}, Sentinel.STORAGE)
Storage.__index = Storage
local function _attach(stor, name, homogeneous)
table.insert(stor, Chest:fromPeripheral(name, homogeneous))
end
function Storage.assumeHomogeneous(names)
local mappings = {}
for _,name in ipairs(names) do
@@ -10,12 +16,13 @@ function Storage.assumeHomogeneous(names)
return mappings
end
function Storage:fromPeripherals(names)
function Storage:fromPeripherals(peripheralDefs)
local obj = {
count = #names
count = #peripheralDefs
}
for name,homogeneous in pairs(names) do
table.insert(obj, Chest:fromPeripheral(name, homogeneous))
for name,homogeneous in pairs(peripheralDefs) do
_attach(obj, name, homogeneous)
end
setmetatable(obj, self)
@@ -28,6 +35,7 @@ function Storage:toSerializable()
local ser = {
count = self.count
}
for _,chest in ipairs(self) do
table.insert(ser, chest:toSerializable())
end
@@ -41,7 +49,7 @@ function Storage:fromSerializable(ser)
for _,chestSer in ipairs(ser) do
local chest = Chest:fromSerializable(chestSer)
if chest ~= nil then
table.insert(obj, chest)
table.insert(obj, chest)
end
end
@@ -65,7 +73,14 @@ function Storage:attach(name, homogeneous)
return
end
table.insert(self, Chest:fromPeripheral(name, homogeneous))
_attach(self, name, homogeneous)
end
function Storage:attachAll(peripheralDefs)
for name,homogeneous in pairs(peripheralDefs) do
self:attach(name, homogeneous)
end
return self
end
function Storage:detach(name)

View File

@@ -1,4 +1,6 @@
local ItemGroup = {}
local Sentinel = require("storage.sentinel")
local ItemGroup = Sentinel:tag({}, Sentinel.ITEMGROUP)
ItemGroup.__index = ItemGroup
--- Create a group of managed itemstacks
@@ -53,6 +55,14 @@ function ItemGroup:getNBT()
return self:_getIdentityStack():getNBT()
end
function ItemGroup:getDamage()
return self:_getIdentityStack():getDamage()
end
function ItemGroup:getMaxDamage()
return self:_getIdentityStack():getMaxDamage()
end
function ItemGroup:_getIdentityStack()
return self[1]
end
@@ -85,7 +95,6 @@ function ItemGroup:canAddStack(stack)
return true
end
end
self:_addStack(stack)
return true
end
return false
@@ -99,6 +108,56 @@ function ItemGroup:addStack(stack)
return false
end
function ItemGroup:transferTo(target, itemCount)
local targetGroup = nil
if Sentinel:is(target, Sentinel.CHEST) or Sentinel:is(target, Sentinel.STORAGE) then
local identity = self:_getIdentityStack()
local find = target:find(function(stack) return identity:canTransfer(stack) end)
if #find == 0 then
return itemCount == nil or itemCount == 0, 0
end
targetGroup = ItemGroup:from(find[1])
for i=2,#find do
targetGroup:_addStack(find[i])
end
elseif Sentinel:is(target, Sentinel.ITEMSTACK) then
targetGroup = ItemGroup:from(target)
elseif Sentinel:is(target, Sentinel.ITEMGROUP) then
targetGroup = target
end
if targetGroup == nil then
error("Unexpected transfer target for ItemGroup:transferTo")
end
local targetCap = 0
for _,stack in targetGroup:_iterateStacks() do
targetCap = targetCap + (stack:getMaxCount() - stack:getCount())
end
-- TODO: Not efficient
local transferMax = math.min(itemCount or targetCap, targetCap, self:getItemCount())
local transfer = 0
for _,stack in targetGroup:_iterateStacks() do
for _,from in self:_iterateStacks() do
if transfer >= transferMax then
goto complete
end
if stack:getCount() == stack:getMaxCount() then
goto continue
end
local _, xfer = from:transferTo(stack, math.min(stack:getMaxCount() - stack:getCount(), transferMax - transfer))
transfer = transfer + xfer
end
::continue::
end
::complete::
return itemCount == nil or (itemCount == transfer), transfer
end
function ItemGroup:getItemCount()
if self:_getIdentityStack():isEmpty() then
return 0

View File

@@ -1,6 +1,10 @@
local Inventory = require("storage.inventory")
local Sentinel = require("storage.sentinel")
local Logging = require("logging")
local Util = require("util")
local Logger = Logging.getGlobalLogger()
local ItemStack = {}
local ItemStack = Sentinel:tag({}, Sentinel.ITEMSTACK)
ItemStack.__index = ItemStack
function ItemStack:fromDetail(inv, detail, slot)
@@ -10,13 +14,13 @@ function ItemStack:fromDetail(inv, detail, slot)
}
if detail == nil then
obj.maxCount = inv.getItemLimit(slot)
obj.maxCount = Util.getItemLimit(inv, slot)
else
obj.name = detail.name
obj.damage = detail.damage
obj.maxDamage = detail.maxDamage
obj.count = detail.count
obj.maxCount = detail.maxCount
obj.maxCount = detail.maxCount or 64
obj.enchantments = detail.enchantments
obj.displayName = detail.displayName
obj.nbt = detail.nbt
@@ -30,16 +34,16 @@ end
function ItemStack:clone(withSlot)
local obj = {
slot = withSlot or self.slot,
inv = self.inv,
name = self.name,
damage = self.damage,
maxDamage = self.maxDamage,
count = self.count,
maxCount = self.maxCount,
enchantments = self.enchantments,
displayName = self.displayName,
nbt = self.nbt
slot = withSlot or self:getSlot(),
inv = self:getInventory(),
name = self:getName(),
damage = self:getDamage(),
maxDamage = self:getMaxDamage(),
count = self:getCount(),
maxCount = self:getMaxCount(),
enchantments = self:getEnchantments(),
displayName = self:getDisplayName(),
nbt = self:getNBT()
}
setmetatable(obj, self)
@@ -50,19 +54,19 @@ end
function ItemStack:toSerializable()
local ser = {
slot = self.slot,
maxCount = self.maxCount
slot = self:getSlot(),
maxCount = self:getMaxCount()
}
if not self:isEmpty() then
-- Not empty
ser.name = self.name
ser.damage = self.damage
ser.maxDamage = self.maxDamage
ser.count = self.count
ser.enchantments = self.enchantments
ser.displayName = self.displayName
ser.nbt = self.nbt
ser.name = self:getName()
ser.damage = self:getDamage()
ser.maxDamage = self:getMaxDamage()
ser.count = self:getCount()
ser.enchantments = self:getEnchantments()
ser.displayName = self:getDisplayName()
ser.nbt = self:getNBT()
end
return ser
@@ -114,7 +118,8 @@ function ItemStack:getNBT()
end
function ItemStack:isEmpty()
return self.count == nil or self.count == 0
local count = self:getCount()
return count == nil or count == 0
end
function ItemStack:getDisplayName()
@@ -146,12 +151,14 @@ function ItemStack:getSimpleName()
end
function ItemStack:hasChanged(listObj, thorough)
local listItem = listObj[self.slot]
if listItem == nil or listItem.name ~= self.name or listItem.count ~= self.count then
local listItem = listObj[self:getSlot()]
if listItem == nil or listItem.name ~= self:getName() or listItem.count ~= self:getCount() then
return true
end
return thorough and (self ~= self:fromDetail(self.inv, self.inv.getItemDetail(self.slot), self.slot))
local inv = self:getInventory()
local slot = self:getSlot()
return thorough and (self ~= self:fromDetail(inv, inv.getItemDetail(slot), slot))
end
function ItemStack:_modify(countDelta, stack)
@@ -162,26 +169,24 @@ function ItemStack:_modify(countDelta, stack)
if newCount == 0 then
-- Clear data
self.maxCount = self.inv.getItemLimit(self.slot)
self.maxCount = Util.getItemLimit(self:getInventory(), self:getSlot())
self.name = nil
self.damage = nil
self.maxDamage = nil
self.count = nil
self.maxCount = nil
self.enchantments = nil
self.displayName = nil
self.nbt = nil
else
-- If stack is empty, copy stack data from source
if self:isEmpty() then
self.maxCount = stack.maxCount
self.name = stack.name
self.damage = stack.damage
self.maxDamage = stack.maxDamage
self.maxCount = stack.maxCount
self.enchantments = stack.enchantments
self.displayName = stack.displayName
self.nbt = stack.nbt
self.name = stack:getName()
self.damage = stack:getDamage()
self.maxDamage = stack:getMaxDamage()
self.maxCount = stack:getMaxCount()
self.enchantments = stack:getEnchantments()
self.displayName = stack:getDisplayName()
self.nbt = stack:getNBT()
end
self.count = newCount
@@ -196,16 +201,21 @@ function ItemStack:transferTo(target, count)
return count == 0, 0
end
local result, xfer = pcall(self:getInventory().pushItems, peripheral.getName(target:getInventory()), self:getSlot(), cap, target:getSlot())
local errC = 0
local result
repeat
result = { pcall(self:getInventory().pushItems, peripheral.getName(target:getInventory()), self:getSlot(), cap, target:getSlot()) }
errC = errC + 1
until (not result[1]) or (result[2] ~= nil) or (errC > 8)
if not result then
return false, xfer
if not result[1] then
return false, result[2]
end
target:_modify(xfer, self)
self:_modify(-xfer, self)
target:_modify(result[2], self)
self:_modify(-result[2], self)
return xfer == count, xfer
return result[2] == count, result[2]
end
local function objEquals(o1, o2)
@@ -237,23 +247,45 @@ local function objEquals(o1, o2)
end
end
function ItemStack:__eq(other)
return type(other) == "table" and
self.count == other.count and
self.maxCount == other.maxCount and
self:canTransfer(other)
local function runGetter(obj, name)
local fun = obj[name]
if type(fun) ~= "function" then
return false, ("Function '%s' cannot be found in object"):format(name)
end
return pcall(fun, obj)
end
local function checkEqual(obj1, obj2, funcName)
local result1 = { runGetter(obj1, funcName) }
local result2 = { runGetter(obj2, funcName) }
return result1[1] and result2[1] and (result1[2] == result2[2])
end
local function checkEnchantments(obj1, obj2)
local GETTER_FUNC_NAME = "getEnchantments"
local result1 = { runGetter(obj1, GETTER_FUNC_NAME) }
local result2 = { runGetter(obj2, GETTER_FUNC_NAME) }
return result1[1] and result2[1] and objEquals(result1[2], result2[2])
end
function ItemStack:__eq(other)
return type(other) == "table" and
checkEqual(self, other, "getCount") and
checkEqual(self, other, "getMaxCount") and
self:canTransfer(other)
end
-- Determines if two stacks can be transferred to eachother
-- Empty stacks are always valid transfer nodes
function ItemStack:canTransfer(stack)
return self:isEmpty() or stack:isEmpty() or (self.name == stack.name and
self.damage == stack.damage and
self.maxDamage == stack.maxDamage and
self.displayName == stack.displayName and
self.nbt == stack.nbt and
objEquals(self.enchantments, stack.enchantments))
return self:isEmpty() or stack:isEmpty() or (
checkEqual(self, stack, "getName") and
checkEqual(self, stack, "getDamage") and
checkEqual(self, stack, "getMaxDamage") and
checkEqual(self, stack, "getDisplayName") and
checkEqual(self, stack, "getNBT") and
checkEnchantments(self, stack)
)
end
local function queryField(query, field)
@@ -276,14 +308,14 @@ function ItemStack:matches(query)
return query(self)
end
return queryField(query.name, self.name) and
queryField(query.damage, self.damage) and
queryField(query.count, self.count) and
queryField(query.maxDamage, self.maxDamage) and
queryField(query.enchantments, self.enchantments) and
queryField(query.maxCount, self.maxCount) and
queryField(query.nbt, self.nbt) and
queryField(query.displayName, self.displayName)
return queryField(query.name, self:getName()) and
queryField(query.damage, self:getDamage()) and
queryField(query.count, self:getCount()) and
queryField(query.maxDamage, self:getMaxDamage()) and
queryField(query.enchantments, self:getEnchantments()) and
queryField(query.maxCount, self:getMaxCount()) and
queryField(query.nbt, self:getNBT()) and
queryField(query.displayName, self:getDisplayName())
end
return ItemStack

19
storage/sentinel.lua Normal file
View File

@@ -0,0 +1,19 @@
local TABLE_KEY = "!__SENTINEL"
local Sentinel = {
CHEST = "CHEST",
ITEMSTACK = "ITEMSTACK",
ITEMGROUP = "ITEMGROUP",
STORAGE = "STORAGE"
}
function Sentinel:tag(table, sentinel)
table[TABLE_KEY] = sentinel
return table
end
function Sentinel:is(table, sentinel)
return table[TABLE_KEY] == sentinel
end
return Sentinel

34
util/init.lua Normal file
View File

@@ -0,0 +1,34 @@
local Util = {}
function Util.toHexChar(v)
return ("0123456789ABCDEF"):sub(v, v)
end
function Util.fromHexChar(c)
local index = ({("0123456789ABCDEF"):find(c)})[1]
if index ~= nil then
return index - 1
end
return nil
end
function Util.boundCheck(x, y, w, h)
if x < 1 or x > w or y < 1 or y > h then
error(("Index out of range: (%d %d) is not within ([1,%d], [1,%d])"):format(x, y, w, h))
end
end
-- Workaround for a bug in CC: Tweaked causing inventory.getItemLimit() to return nil immediately after item transfers
function Util.getItemLimit(inv, slot, maxTries)
local i = 1
while not maxTries or i <= maxTries do
local result = inv.getItemLimit(slot)
if result ~= nil then
return result
end
i = i + 1
end
return nil
end
return Util